am 66c4138f: am 82a45527: Merge "SeccompTest: add note about kernels between 3.5 and 3.8"

* commit '66c4138fbd1fc6ec59e82916b79df610ee551064':
  SeccompTest: add note about kernels between 3.5 and 3.8
diff --git a/Android.mk b/Android.mk
index 4251262..dbcc97b 100644
--- a/Android.mk
+++ b/Android.mk
@@ -14,6 +14,7 @@
 # limitations under the License.
 #
 
+include cts/suite/pts/PtsBenchmarkingList.mk
 include cts/CtsBuild.mk
 include cts/CtsCoverage.mk
 include $(call all-subdir-makefiles)
diff --git a/CtsBuild.mk b/CtsBuild.mk
index d396006..6e57086 100644
--- a/CtsBuild.mk
+++ b/CtsBuild.mk
@@ -36,6 +36,10 @@
 	$(foreach lib,$(1),$(HOST_OUT_JAVA_LIBRARIES)/$(lib).jar)
 endef
 
+define cts-get-ui-lib-paths
+	$(foreach lib,$(1),$(CTS_TESTCASES_OUT)/$(lib).jar)
+endef
+
 define cts-get-native-paths
 	$(foreach exe,$(1),$(call intermediates-dir-for,EXECUTABLES,$(exe))/$(exe))
 endef
diff --git a/CtsCoverage.mk b/CtsCoverage.mk
index 61ad9c9..8d67a99 100644
--- a/CtsCoverage.mk
+++ b/CtsCoverage.mk
@@ -24,7 +24,7 @@
 
 coverage_out := $(HOST_OUT)/cts-api-coverage
 
-api_text_description := $(SRC_API_DIR)/current.txt
+api_text_description := frameworks/base/api/current.txt
 api_xml_description := $(coverage_out)/api.xml
 $(api_xml_description) : $(api_text_description) $(APICHECK)
 	$(hide) echo "Converting API file to XML: $@"
diff --git a/CtsTestCaseList.mk b/CtsTestCaseList.mk
index 5511ebd..1e794f9 100644
--- a/CtsTestCaseList.mk
+++ b/CtsTestCaseList.mk
@@ -29,6 +29,7 @@
 	CtsMultiUserStorageApp
 
 cts_support_packages := \
+	$(PTS_SUPPORT_PACKAGES) \
 	CtsAccelerationTestStubs \
 	CtsDelegatingAccessibilityService \
 	CtsDeviceAdmin \
@@ -38,6 +39,7 @@
 	CtsTestStubs \
 	SignatureTest \
 	TestDeviceSetup \
+	CtsUiAutomatorApp \
 	$(cts_security_apps_list)
 
 cts_external_packages := \
@@ -51,6 +53,7 @@
 
 # Test packages that require an associated test package XML.
 cts_test_packages := \
+	$(PTS_TEST_PACKAGES) \
 	CtsAccelerationTestCases \
 	CtsAccountManagerTestCases \
 	CtsAccessibilityServiceTestCases \
@@ -62,8 +65,10 @@
 	CtsCalendarcommon2TestCases \
 	CtsContentTestCases \
 	CtsDatabaseTestCases \
+	CtsDisplayTestCases \
 	CtsDpiTestCases \
 	CtsDpiTestCases2 \
+	CtsDreamsTestCases \
 	CtsDrmTestCases \
 	CtsEffectTestCases \
 	CtsExampleTestCases \
@@ -76,6 +81,7 @@
 	CtsLocationTestCases \
 	CtsMediaStressTestCases \
 	CtsMediaTestCases \
+	CtsNativeOpenGLTestCases \
 	CtsNdefTestCases \
 	CtsNetTestCases \
 	CtsOpenGLTestCases \
@@ -107,6 +113,7 @@
 
 # Host side only tests
 cts_host_libraries := \
+	$(PTS_HOST_CASES) \
 	CtsAppSecurityTests \
 	CtsMonkeyTestCases
 
@@ -115,17 +122,23 @@
 	NativeMediaTest_SL \
 	NativeMediaTest_XA
 
+cts_ui_tests := \
+        CtsUiAutomatorTests
+
 # All the files that will end up under the repository/testcases
 # directory of the final CTS distribution.
 CTS_TEST_CASES := $(call cts-get-lib-paths,$(cts_host_libraries)) \
 		$(call cts-get-package-paths,$(cts_test_packages)) \
-		$(call cts-get-native-paths,$(cts_native_exes))
+		$(call cts-get-native-paths,$(cts_native_exes)) \
+		$(call cts-get-ui-lib-paths,$(cts_ui_tests))
 
 # All the XMLs that will end up under the repository/testcases
 # and that need to be created before making the final CTS distribution.
 CTS_TEST_XMLS := $(call cts-get-test-xmls,$(cts_host_libraries)) \
 		$(call cts-get-test-xmls,$(cts_test_packages)) \
-		$(call cts-get-test-xmls,$(cts_native_exes))
+		$(call cts-get-test-xmls,$(cts_native_exes)) \
+		$(call cts-get-test-xmls,$(cts_ui_tests))
+
 
 # The following files will be placed in the tools directory of the CTS distribution
 CTS_TOOLS_LIST :=
diff --git a/apps/CtsVerifier/Android.mk b/apps/CtsVerifier/Android.mk
index aa3f117..c14728e 100644
--- a/apps/CtsVerifier/Android.mk
+++ b/apps/CtsVerifier/Android.mk
@@ -36,7 +36,7 @@
 # Builds and launches CTS Verifier on a device.
 .PHONY: cts-verifier
 cts-verifier: CtsVerifier adb
-	adb install -r $(ANDROID_PRODUCT_OUT)/data/app/CtsVerifier.apk \
+	adb install -r $(PRODUCT_OUT)/data/app/CtsVerifier.apk \
 		&& adb shell "am start -n com.android.cts.verifier/.CtsVerifierActivity"
 
 #
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 7bf8387..9b8d5b1 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -18,7 +18,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
       package="com.android.cts.verifier"
       android:versionCode="1"
-      android:versionName="4.2_r4">
+      android:versionName="1337">
 
     <!-- Using 10 for more complete NFC support... -->
     <uses-sdk android:minSdkVersion="10"></uses-sdk>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/features/FeatureSummaryActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/features/FeatureSummaryActivity.java
index ca3c2a9..afbf97e 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/features/FeatureSummaryActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/features/FeatureSummaryActivity.java
@@ -172,10 +172,6 @@
             new Feature(PackageManager.FEATURE_TELEVISION, false),
     };
 
-    public static final Feature[] ALL_JELLY_BEAN_MR1_FEATURES = {
-            new Feature(PackageManager.FEATURE_CAMERA_ANY, false),
-    };
-
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -206,9 +202,6 @@
 
         // add features from latest to last so that the latest requirements are put in the set first
         int apiVersion = Build.VERSION.SDK_INT;
-        if (apiVersion >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
-            Collections.addAll(features, ALL_JELLY_BEAN_MR1_FEATURES);
-        }
         if (apiVersion >= Build.VERSION_CODES.JELLY_BEAN) {
             Collections.addAll(features, ALL_JELLY_BEAN_FEATURES);
         }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/location/GpsTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/location/GpsTestActivity.java
index bb12b2b..da1ee88 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/location/GpsTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/location/GpsTestActivity.java
@@ -95,25 +95,25 @@
             case 1:
                 // Test GPS with minTime = 0
                 mLocationVerifier = new LocationVerifier(this, mLocationManager,
-                        LocationManager.GPS_PROVIDER, 0, 8);
+                        LocationManager.GPS_PROVIDER, 0, 8, false);
                 mLocationVerifier.start();
                 break;
             case 2:
                 // Test GPS with minTime = 1s
                 mLocationVerifier = new LocationVerifier(this, mLocationManager,
-                        LocationManager.GPS_PROVIDER, 1 * 1000, 8);
+                        LocationManager.GPS_PROVIDER, 1 * 1000, 8, false);
                 mLocationVerifier.start();
                 break;
             case 3:
                 // Test GPS with minTime = 5s
                 mLocationVerifier = new LocationVerifier(this, mLocationManager,
-                        LocationManager.GPS_PROVIDER, 5 * 1000, 8);
+                        LocationManager.GPS_PROVIDER, 5 * 1000, 8, false);
                 mLocationVerifier.start();
                 break;
             case 4:
                 // Test GPS with minTime = 15s
                 mLocationVerifier = new LocationVerifier(this, mLocationManager,
-                        LocationManager.GPS_PROVIDER, 15 * 1000, 8);
+                        LocationManager.GPS_PROVIDER, 15 * 1000, 8, false);
                 mLocationVerifier.start();
                 break;
             case 5:
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/location/LocationVerifier.java b/apps/CtsVerifier/src/com/android/cts/verifier/location/LocationVerifier.java
index 3315ba3..ee4068f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/location/LocationVerifier.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/location/LocationVerifier.java
@@ -66,6 +66,7 @@
     private long mLastPassiveTimestamp = -1;
     private int mNumActiveUpdates = 0;
     private int mNumPassiveUpdates = 0;
+    private boolean mIsMockProvider = false;
     private boolean mRunning = false;
     private boolean mActiveLocationArrive = false;
 
@@ -89,6 +90,11 @@
                 mCb.log("(ignored) active " + mProvider + " update (" + delta + "ms)");
                 return;
             }
+            if (location.isFromMockProvider() != mIsMockProvider) {
+                fail("location coming from \"" + mProvider +
+                        "\" provider reports isFromMockProvider() to be " +
+                        location.isFromMockProvider());
+            }
 
             mActiveDeltas.add(delta);
             mCb.log("active " + mProvider + " update (" + delta + "ms)");
@@ -171,6 +177,11 @@
                 mCb.log("(ignored) passive " + mProvider + " update (" + delta + "ms)");
                 return;
             }
+            if (location.isFromMockProvider() != mIsMockProvider) {
+                fail("location coming from \"" + mProvider +
+                        "\" provider reports isFromMockProvider() to be " +
+                        location.isFromMockProvider());
+            }
 
             mPassiveDeltas.add(delta);
             mCb.log("passive " + mProvider + " update (" + delta + "ms)");
@@ -185,7 +196,7 @@
     }
 
     public LocationVerifier(PassFailLog cb, LocationManager locationManager,
-            String provider, long requestedInterval, int numUpdates) {
+            String provider, long requestedInterval, int numUpdates, boolean isMockProvider) {
         mProvider = provider;
         mInterval = requestedInterval;
         // timeout at 60 seconds after interval time
@@ -196,6 +207,7 @@
         mHandler = new Handler(this);
         mActiveListener = new ActiveListener();
         mPassiveListener = new PassiveListener();
+        mIsMockProvider = isMockProvider;
     }
 
     public void start() {
diff --git a/build/config.mk b/build/config.mk
index c9fa709..b737ef7 100644
--- a/build/config.mk
+++ b/build/config.mk
@@ -14,4 +14,6 @@
 
 BUILD_CTS_EXECUTABLE := cts/build/test_executable.mk
 BUILD_CTS_PACKAGE := cts/build/test_package.mk
+BUILD_CTS_GTEST_PACKAGE := cts/build/test_gtest_package.mk
 BUILD_CTS_HOST_JAVA_LIBRARY := cts/build/test_host_java_library.mk
+BUILD_CTS_UI_JAVA_LIBRARY := cts/build/test_uiautomator.mk
diff --git a/build/test_gtest_package.mk b/build/test_gtest_package.mk
new file mode 100644
index 0000000..2f4c9bb
--- /dev/null
+++ b/build/test_gtest_package.mk
@@ -0,0 +1,49 @@
+# Copyright 2012 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.
+
+#
+# Builds a package and defines a rule to generate the associated test
+# package XML needed by CTS.
+#
+# Replace "include $(BUILD_PACKAGE)" with "include $(BUILD_CTS_GTEST_PACKAGE)"
+#
+
+# Disable by default so "m cts" will work in emulator builds
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_PACKAGE)
+
+cts_package_apk := $(CTS_TESTCASES_OUT)/$(LOCAL_PACKAGE_NAME).apk
+cts_package_xml := $(CTS_TESTCASES_OUT)/$(LOCAL_PACKAGE_NAME).xml
+
+$(cts_package_apk): PRIVATE_PACKAGE := $(LOCAL_PACKAGE_NAME)
+$(cts_package_apk): $(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME))/package.apk | $(ACP)
+	$(hide) mkdir -p $(CTS_TESTCASES_OUT)
+	$(hide) $(ACP) -fp $(call intermediates-dir-for,APPS,$(PRIVATE_PACKAGE))/package.apk $@
+
+$(cts_package_xml): PRIVATE_PATH := $(LOCAL_PATH)
+$(cts_package_xml): PRIVATE_TEST_PACKAGE := android.$(notdir $(LOCAL_PATH))
+$(cts_package_xml): PRIVATE_EXECUTABLE := $(LOCAL_MODULE)
+$(cts_package_xml): PRIVATE_MANIFEST := $(LOCAL_PATH)/AndroidManifest.xml
+$(cts_package_xml): $(addprefix $(LOCAL_PATH)/,$(LOCAL_SRC_FILES))  $(CTS_NATIVE_TEST_SCANNER) $(CTS_XML_GENERATOR)
+	$(hide) echo Generating test description for wrapped native package $(PRIVATE_EXECUTABLE)
+	$(hide) mkdir -p $(CTS_TESTCASES_OUT)
+	$(hide) $(CTS_NATIVE_TEST_SCANNER) -s $(PRIVATE_PATH) \
+						-t $(PRIVATE_TEST_PACKAGE) | \
+			$(CTS_XML_GENERATOR) -t wrappednative \
+                                                -m $(PRIVATE_MANIFEST) \
+						-n $(PRIVATE_EXECUTABLE) \
+						-p $(PRIVATE_TEST_PACKAGE) \
+						-e $(CTS_EXPECTATIONS) \
+						-o $@
diff --git a/build/test_package.mk b/build/test_package.mk
index 071acee..aa9a4dd 100644
--- a/build/test_package.mk
+++ b/build/test_package.mk
@@ -35,7 +35,12 @@
 $(cts_package_xml): PRIVATE_PATH := $(LOCAL_PATH)
 $(cts_package_xml): PRIVATE_INSTRUMENTATION := $(LOCAL_INSTRUMENTATION_FOR)
 $(cts_package_xml): PRIVATE_PACKAGE := $(LOCAL_PACKAGE_NAME)
-$(cts_package_xml): PRIVATE_TEST_PACKAGE := android.$(notdir $(LOCAL_PATH))
+ifneq ($(filter cts/suite/pts/%, $(LOCAL_PATH)),) # PTS
+PRIVATE_CTS_TEST_PACKAGE_NANE_ := com.android.pts.$(notdir $(LOCAL_PATH))
+else # CTS
+PRIVATE_CTS_TEST_PACKAGE_NANE_ := android.$(notdir $(LOCAL_PATH))
+endif # PTS
+$(cts_package_xml): PRIVATE_TEST_PACKAGE := $(PRIVATE_CTS_TEST_PACKAGE_NANE_)
 $(cts_package_xml): PRIVATE_MANIFEST := $(LOCAL_PATH)/AndroidManifest.xml
 $(cts_package_xml): PRIVATE_TEST_TYPE := $(if $(LOCAL_CTS_TEST_RUNNER),$(LOCAL_CTS_TEST_RUNNER),'')
 $(cts_package_xml): $(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME))/package.apk $(CTS_EXPECTATIONS) $(CTS_JAVA_TEST_SCANNER_DOCLET) $(CTS_JAVA_TEST_SCANNER) $(CTS_XML_GENERATOR)
diff --git a/build/test_uiautomator.mk b/build/test_uiautomator.mk
new file mode 100644
index 0000000..255f70b
--- /dev/null
+++ b/build/test_uiautomator.mk
@@ -0,0 +1,49 @@
+# Copyright (C) 2012 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.
+
+#
+# Builds a uiautomator library and defines a rule to generate the associated test
+# package XML needed by CTS.
+#
+
+include $(BUILD_JAVA_LIBRARY)
+
+cts_library_xml := $(CTS_TESTCASES_OUT)/$(LOCAL_MODULE).xml 
+cts_library_jar := $(CTS_TESTCASES_OUT)/$(LOCAL_MODULE).jar
+
+$(cts_library_jar): PRIVATE_MODULE := $(LOCAL_MODULE)
+$(cts_library_jar): $(call intermediates-dir-for,JAVA_LIBRARIES,$(LOCAL_MODULE))/javalib.jar | $(ACP)
+	$(hide) mkdir -p $(CTS_TESTCASES_OUT)
+	$(hide) $(ACP) -fp $(call intermediates-dir-for,JAVA_LIBRARIES,$(PRIVATE_MODULE))/javalib.jar $@
+
+$(cts_library_xml): PRIVATE_PATH := $(LOCAL_PATH)/src
+$(cts_library_xml): PRIVATE_TEST_APP_PACKAGE := $(LOCAL_CTS_TEST_APP_PACKAGE)
+$(cts_library_xml): PRIVATE_TEST_PACKAGE := $(LOCAL_CTS_TEST_PACKAGE)
+$(cts_library_xml): PRIVATE_TEST_APK := $(LOCAL_CTS_TEST_APK)
+$(cts_library_xml): PRIVATE_LIBRARY := $(LOCAL_MODULE)
+$(cts_library_xml): PRIVATE_JAR_PATH := $(LOCAL_MODULE).jar
+$(cts_library_xml): $(call intermediates-dir-for,JAVA_LIBRARIES,$(LOCAL_MODULE))/javalib.jar $(CTS_EXPECTATIONS) $(CTS_JAVA_TEST_SCANNER_DOCLET) $(CTS_JAVA_TEST_SCANNER) $(CTS_XML_GENERATOR)
+	$(hide) echo Generating test description for uiautomator library $(PRIVATE_LIBRARY)
+	$(hide) mkdir -p $(CTS_TESTCASES_OUT)
+	$(hide) $(CTS_JAVA_TEST_SCANNER) -s $(PRIVATE_PATH) \
+						-d $(CTS_JAVA_TEST_SCANNER_DOCLET) | \
+			$(CTS_XML_GENERATOR) -t uiAutomator \
+						-i $(PRIVATE_TEST_APK) \
+						-j $(PRIVATE_JAR_PATH) \
+						-a $(PRIVATE_TEST_PACKAGE) \
+						-n $(PRIVATE_LIBRARY) \
+						-p $(PRIVATE_TEST_PACKAGE) \
+						-r $(PRIVATE_TEST_APP_PACKAGE) \
+						-e $(CTS_EXPECTATIONS) \
+						-o $@
diff --git a/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/AmbiguousContentProvider.java b/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/AmbiguousContentProvider.java
index 3536979..09ddbc2 100644
--- a/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/AmbiguousContentProvider.java
+++ b/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/AmbiguousContentProvider.java
@@ -20,6 +20,10 @@
 import android.content.ContentValues;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+
+import java.io.File;
+import java.io.FileNotFoundException;
 
 /**
  * Empty content provider, manifest did not declare exported=true nor exported=false.
@@ -58,4 +62,10 @@
             String[] selectionArgs) {
         return 0;
     }
+
+    @Override
+    public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
+        return ParcelFileDescriptor.open(
+                new File("/dev/null"), ParcelFileDescriptor.MODE_READ_ONLY);
+    }
 }
diff --git a/hostsidetests/appsecurity/test-apps/PermissionDeclareAppCompat/src/com/android/cts/permissiondeclareappcompat/AmbiguousContentProvider.java b/hostsidetests/appsecurity/test-apps/PermissionDeclareAppCompat/src/com/android/cts/permissiondeclareappcompat/AmbiguousContentProvider.java
index 8665b70..9727047 100644
--- a/hostsidetests/appsecurity/test-apps/PermissionDeclareAppCompat/src/com/android/cts/permissiondeclareappcompat/AmbiguousContentProvider.java
+++ b/hostsidetests/appsecurity/test-apps/PermissionDeclareAppCompat/src/com/android/cts/permissiondeclareappcompat/AmbiguousContentProvider.java
@@ -20,6 +20,10 @@
 import android.content.ContentValues;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+
+import java.io.File;
+import java.io.FileNotFoundException;
 
 /**
  * Empty content provider, all permissions are enforced in manifest
@@ -58,4 +62,10 @@
             String[] selectionArgs) {
         return 0;
     }
+
+    @Override
+    public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
+        return ParcelFileDescriptor.open(
+                new File("/dev/null"), ParcelFileDescriptor.MODE_READ_ONLY);
+    }
 }
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java
index e7d333c..8f40cf1 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java
@@ -102,7 +102,7 @@
         }
     }
 
-    public void assertWritingContentUriNotAllowed(Uri uri, String msg) {
+    private void assertWritingContentUriNotAllowed(Uri uri, String msg) {
         final ContentResolver resolver = getContext().getContentResolver();
         try {
             resolver.insert(uri, new ContentValues());
diff --git a/libs/testserver/src/android/webkit/cts/CtsTestServer.java b/libs/testserver/src/android/webkit/cts/CtsTestServer.java
index 16d57ea..a18d329 100755
--- a/libs/testserver/src/android/webkit/cts/CtsTestServer.java
+++ b/libs/testserver/src/android/webkit/cts/CtsTestServer.java
@@ -423,11 +423,20 @@
     }
 
     /**
+     * Hook for adding stuffs for HTTP POST. Default implementation does nothing.
+     * @return null to use the default response mechanism of sending the requested uri as it is.
+     *         Otherwise, the whole response should be handled inside onPost.
+     */
+    protected HttpResponse onPost(HttpRequest request) throws Exception {
+        return null;
+    }
+
+    /**
      * Generate a response to the given request.
      * @throws InterruptedException
      * @throws IOException
      */
-    private HttpResponse getResponse(HttpRequest request) throws InterruptedException, IOException {
+    private HttpResponse getResponse(HttpRequest request) throws Exception {
         RequestLine requestLine = request.getRequestLine();
         HttpResponse response = null;
         String uriString = requestLine.getUri();
@@ -441,6 +450,13 @@
             }
         }
 
+        if (requestLine.getMethod().equals("POST")) {
+            HttpResponse responseOnPost = onPost(request);
+            if (responseOnPost != null) {
+                return responseOnPost;
+            }
+        }
+
         URI uri = URI.create(uriString);
         String path = uri.getPath();
         String query = uri.getQuery();
@@ -544,12 +560,11 @@
             Header[] cookies = request.getHeaders("Cookie");
             Pattern p = Pattern.compile("count=(\\d+)");
             StringBuilder cookieString = new StringBuilder(100);
+            cookieString.append(cookies.length);
             int count = 0;
             for (Header cookie : cookies) {
+                cookieString.append("|");
                 String value = cookie.getValue();
-                if (cookieString.length() > 0) {
-                    cookieString.append("|");
-                }
                 cookieString.append(value);
                 Matcher m = p.matcher(value);
                 if (m.find()) {
@@ -864,7 +879,7 @@
             }
 
             @Override
-            public Void call() throws IOException, InterruptedException, HttpException {
+            public Void call() throws Exception {
                 HttpResponse response = mServer.getResponse(mRequest);
                 mConnection.sendResponseHeader(response);
                 mConnection.sendResponseEntity(response);
diff --git a/tests/tests/openglperf/src/android/openglperf/cts/RenderingWatchDog.java b/libs/util/src/android/cts/util/WatchDog.java
similarity index 72%
rename from tests/tests/openglperf/src/android/openglperf/cts/RenderingWatchDog.java
rename to libs/util/src/android/cts/util/WatchDog.java
index 4872af2..ab2a9d9 100644
--- a/tests/tests/openglperf/src/android/openglperf/cts/RenderingWatchDog.java
+++ b/libs/util/src/android/cts/util/WatchDog.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.openglperf.cts;
+package android.cts.util;
 
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
@@ -26,17 +26,23 @@
  * class for checking if rendering function is alive or not.
  * panic if watch-dog is not reset over certain amount of time
  */
-public class RenderingWatchDog implements Runnable {
-    private static final String TAG = "RenderingWatchDog";
+public class WatchDog implements Runnable {
+    private static final String TAG = "WatchDog";
     private Thread mThread;
     private Semaphore mSemaphore;
     private volatile boolean mStopRequested;
     private final long mTimeoutInMilliSecs;
+    private TimeoutCallback mCallback = null;
 
-    public RenderingWatchDog(long timeoutInMilliSecs) {
+    public WatchDog(long timeoutInMilliSecs) {
         mTimeoutInMilliSecs = timeoutInMilliSecs;
     }
 
+    public WatchDog(long timeoutInMilliSecs, TimeoutCallback callback) {
+        this(timeoutInMilliSecs);
+        mCallback = callback;
+    }
+
     /** start watch-dog */
     public void start() {
         Log.i(TAG, "start");
@@ -74,12 +80,24 @@
     public void run() {
         while (!mStopRequested) {
             try {
-                Assert.assertTrue("Watchdog timed-out",
-                        mSemaphore.tryAcquire(mTimeoutInMilliSecs, TimeUnit.MILLISECONDS));
+                boolean success = mSemaphore.tryAcquire(mTimeoutInMilliSecs, TimeUnit.MILLISECONDS);
+                if (mCallback == null) {
+                    Assert.assertTrue("Watchdog timed-out", success);
+                } else if (!success) {
+                    mCallback.onTimeout();
+                }
             } catch (InterruptedException e) {
                 // this thread will not be interrupted,
                 // but if it happens, just check the exit condition.
             }
         }
     }
+
+    /**
+     * Called by the Watchdog when it has timed out.
+     */
+    public interface TimeoutCallback {
+
+        public void onTimeout();
+    }
 }
diff --git a/suite/pts/tools/tradefed/Android.mk b/libs/wrappedgtest/Android.mk
similarity index 74%
rename from suite/pts/tools/tradefed/Android.mk
rename to libs/wrappedgtest/Android.mk
index 09e49b1..f89ba9d 100644
--- a/suite/pts/tools/tradefed/Android.mk
+++ b/libs/wrappedgtest/Android.mk
@@ -1,4 +1,4 @@
-# Copyright (C) 2011 The Android Open Source Project
+# Copyright 2012 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.
@@ -12,11 +12,14 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-LOCAL_PATH := $(call my-dir)
+LOCAL_PATH:= $(call my-dir)
+
 include $(CLEAR_VARS)
 
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
 LOCAL_MODULE_TAGS := optional
 
-LOCAL_PREBUILT_EXECUTABLES := pts-tradefed
-include $(BUILD_HOST_PREBUILT)
+LOCAL_MODULE := ctswrappedgtest
 
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/libs/wrappedgtest/src/WrappedGTestActivity.java b/libs/wrappedgtest/src/WrappedGTestActivity.java
new file mode 100644
index 0000000..0633a5b
--- /dev/null
+++ b/libs/wrappedgtest/src/WrappedGTestActivity.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2012 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.test.wrappedgtest;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.os.Bundle;
+
+public class WrappedGTestActivity extends Activity {
+
+    private WrappedGTestInstrumentation mInstrumentation;
+
+    public void setInstrumentation(WrappedGTestInstrumentation instrumentation) {
+        mInstrumentation = instrumentation;
+    }
+
+    public int runGTests() {
+        return runTests(this);
+    }
+
+    public void sendStatus(String output) {
+        Bundle outputBundle = new Bundle();
+        outputBundle.putString("gtest", output);
+        mInstrumentation.sendStatus(1, outputBundle);
+    }
+
+    protected static native int runTests(WrappedGTestActivity activity);
+}
diff --git a/libs/wrappedgtest/src/WrappedGTestInstrumentation.java b/libs/wrappedgtest/src/WrappedGTestInstrumentation.java
new file mode 100644
index 0000000..b29aaab
--- /dev/null
+++ b/libs/wrappedgtest/src/WrappedGTestInstrumentation.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2012 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.test.wrappedgtest;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+
+public class WrappedGTestInstrumentation extends Instrumentation {
+
+    private static final String TAG = "WrappedGTestInstrumentation";
+    private WrappedGTestActivity mActivity;
+    protected Class mActivityClass;
+
+    public WrappedGTestInstrumentation() {
+    }
+
+    @Override
+    public void onCreate(Bundle arguments) {
+        // attempt to disable keyguard,  if current test has permission to do so
+        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);
+        start();
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+
+        Intent intent = new Intent(getTargetContext(), mActivityClass);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+        mActivity = (WrappedGTestActivity)startActivitySync(intent);
+        mActivity.setInstrumentation(this);
+        mActivity.runGTests();
+
+        finish(Activity.RESULT_OK, new Bundle());
+    }
+}
diff --git a/suite/pts/Android.mk b/suite/pts/Android.mk
index 07b30e3..c141484 100644
--- a/suite/pts/Android.mk
+++ b/suite/pts/Android.mk
@@ -14,5 +14,4 @@
 # limitations under the License.
 #
 
-include cts/suite/pts/PtsBuild.mk
 include $(call all-subdir-makefiles)
diff --git a/suite/pts/PtsBenchmarkingList.mk b/suite/pts/PtsBenchmarkingList.mk
new file mode 100644
index 0000000..7c0ad47
--- /dev/null
+++ b/suite/pts/PtsBenchmarkingList.mk
@@ -0,0 +1,36 @@
+#
+# Copyright (C) 2012 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 definitions for PTS
+
+# New packages should be added here
+PTS_TEST_PACKAGES := \
+    PtsDeviceFilePerf \
+    PtsDeviceUi \
+    PtsDeviceDram \
+    PtsDeviceSimpleCpu \
+    PtsDeviceBrowserBench \
+    PtsDeviceVideoPerf \
+    PtsDeviceOpenGl
+
+PTS_SUPPORT_PACKAGES := \
+    PtsDeviceTaskswitchingAppA \
+    PtsDeviceTaskswitchingAppB \
+    PtsDeviceTaskswitchingControl
+
+PTS_HOST_CASES := \
+    PtsHostBootup \
+    PtsHostUi
diff --git a/suite/pts/PtsBuild.mk b/suite/pts/PtsBuild.mk
deleted file mode 100644
index 18306b9..0000000
--- a/suite/pts/PtsBuild.mk
+++ /dev/null
@@ -1,152 +0,0 @@
-#
-# Copyright (C) 2012 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.
-#
-
-# several makefiles for CTS merged for PTS
-
-LOCAL_PATH:= $(call my-dir)
-
-# New packages should be added here
-PTS_TEST_PACKAGES := \
-    PtsDeviceFilePerf \
-    PtsDeviceUi \
-    PtsDeviceDram
-
-
-PTS_SUPPORT_PACKAGES := \
-    TestDeviceSetup \
-    PtsDeviceTaskswitchingAppA \
-    PtsDeviceTaskswitchingAppB \
-    PtsDeviceTaskswitchingControl \
-    com.replica.replicaisland \
-    PtsDeviceBrowserLauncher
-
-PTS_HOST_CASES := \
-    PtsHostBootup \
-    PtsHostUi \
-    PtsHostBrowser
-
-PTS_HOST_LIBS := \
-    $(HOST_OUT_JAVA_LIBRARIES)/ptscommonutilhost.jar \
-    $(HOST_OUT_JAVA_LIBRARIES)/ptshostutil.jar
-
-BUILD_PTS_PACKAGE := cts/suite/pts/build/test_package.mk
-BUILD_PTS_HOST_JAVA_LIBRARY := cts/suite/pts/build/test_host_java_library.mk
-
-PTS_JAVA_TEST_SCANNER := $(HOST_OUT_EXECUTABLES)/cts-java-scanner
-PTS_JAVA_TEST_SCANNER_DOCLET := $(HOST_OUT_JAVA_LIBRARIES)/cts-java-scanner-doclet.jar
-
-# Generator of test XMLs from scanner output.
-PTS_XML_GENERATOR := $(HOST_OUT_EXECUTABLES)/cts-xml-generator
-
-# File indicating which tests should be blacklisted due to problems.
-PTS_EXPECTATIONS := cts/suite/pts/expectations/knownfailures.txt
-
-PTS_TESTCASES_OUT := $(HOST_OUT)/pts-testcases
-
-define pts-get-package-paths
-	$(foreach pkg,$(1),$(PTS_TESTCASES_OUT)/$(pkg).apk)
-endef
-
-define pts-get-test-xmls
-	$(foreach name,$(1),$(PTS_TESTCASES_OUT)/$(name).xml)
-endef
-
-define pts-get-lib-paths
-	$(foreach lib,$(1),$(HOST_OUT_JAVA_LIBRARIES)/$(lib).jar)
-endef
-
-PTS_TEST_CASE_LIST := \
-	$(PTS_SUPPORT_PACKAGES)
-
-PTS_TEST_CASES := \
-		$(call pts-get-package-paths,$(PTS_TEST_PACKAGES)) \
-		$(call pts-get-lib-paths,$(PTS_HOST_CASES))
-
-PTS_TEST_XMLS := \
-    $(call pts-get-test-xmls,$(PTS_TEST_PACKAGES)) \
-    $(call pts-get-test-xmls,$(PTS_HOST_CASES))
-
-pts_dir := $(HOST_OUT)/pts
-pts_tools_src_dir := cts/tools
-
-pts_name := android-pts
-
-DDMLIB_JAR := $(HOST_OUT_JAVA_LIBRARIES)/ddmlib-prebuilt.jar
-junit_host_jar := $(HOST_OUT_JAVA_LIBRARIES)/junit.jar
-HOSTTESTLIB_JAR := $(HOST_OUT_JAVA_LIBRARIES)/hosttestlib.jar
-TF_JAR := $(HOST_OUT_JAVA_LIBRARIES)/tradefed-prebuilt.jar
-PTS_TF_JAR := $(HOST_OUT_JAVA_LIBRARIES)/cts-tradefed.jar
-PTS_TF_EXEC := $(HOST_OUT_EXECUTABLES)/pts-tradefed
-PTS_TF_README := $(pts_tools_src_dir)/tradefed-host/README
-
-
-DEFAULT_TEST_PLAN := $(pts_dir)/$(pts_name)/resource/plans/PTS.xml
-
-$(pts_dir)/all_pts_files_stamp: PRIVATE_JUNIT_HOST_JAR := $(junit_host_jar)
-
-$(pts_dir)/all_pts_files_stamp: $(PTS_TEST_CASES) $(PTS_TEST_CASE_LIST) $(junit_host_jar) $(HOSTTESTLIB_JAR) $(PTS_HOST_LIBRARY_JARS) $(TF_JAR) $(VMTESTSTF_JAR) $(PTS_TF_JAR) $(PTS_TF_EXEC) $(PTS_TF_README) $(ACP) $(PTS_HOST_LIBS)
-# Make necessary directory for PTS
-	$(hide) rm -rf $(PRIVATE_PTS_DIR)
-	$(hide) mkdir -p $(TMP_DIR)
-	$(hide) mkdir -p $(PRIVATE_DIR)/docs
-	$(hide) mkdir -p $(PRIVATE_DIR)/tools
-	$(hide) mkdir -p $(PRIVATE_DIR)/repository/testcases
-	$(hide) mkdir -p $(PRIVATE_DIR)/repository/plans
-# Copy executable and JARs to PTS directory
-	$(hide) $(ACP) -fp $(DDMLIB_JAR) $(PRIVATE_JUNIT_HOST_JAR) $(HOSTTESTLIB_JAR) $(PTS_HOST_LIBRARY_JARS) $(TF_JAR) $(PTS_TF_JAR) $(PTS_TF_EXEC) $(PTS_TF_README) $(PTS_HOST_LIBS) $(PRIVATE_DIR)/tools
-# Change mode of the executables
-	$(foreach apk,$(PTS_TEST_CASE_LIST),$(call copy-testcase-apk,$(apk)))
-	$(foreach testcase,$(PTS_TEST_CASES),$(call copy-testcase,$(testcase)))
-	$(hide) touch $@
-
-# Generate the default test plan for User.
-# Usage: buildCts.py <testRoot> <ctsOutputDir> <tempDir> <androidRootDir> <docletPath>
-
-$(DEFAULT_TEST_PLAN): $(pts_dir)/all_pts_files_stamp $(pts_tools_src_dir)/utils/buildCts.py $(HOST_OUT_JAVA_LIBRARIES)/descGen.jar $(PTS_TEST_XMLS) | $(ACP)
-	$(hide) $(ACP) -fp $(PTS_TEST_XMLS) $(PRIVATE_DIR)/repository/testcases
-	$(hide) $(pts_tools_src_dir)/utils/buildCts.py cts/suite/pts $(PRIVATE_DIR) $(TMP_DIR) \
-		$(TOP) $(HOST_OUT_JAVA_LIBRARIES)/descGen.jar -pts
-
-# Package PTS and clean up.
-INTERNAL_PTS_TARGET := $(pts_dir)/$(pts_name).zip
-$(INTERNAL_PTS_TARGET): PRIVATE_NAME := $(pts_name)
-$(INTERNAL_PTS_TARGET): PRIVATE_PTS_DIR := $(pts_dir)
-$(INTERNAL_PTS_TARGET): PRIVATE_DIR := $(pts_dir)/$(pts_name)
-$(INTERNAL_PTS_TARGET): TMP_DIR := $(pts_dir)/temp
-$(INTERNAL_PTS_TARGET): $(pts_dir)/all_pts_files_stamp $(DEFAULT_TEST_PLAN)
-	$(hide) echo "Package PTS: $@"
-	$(hide) cd $(dir $@) && zip -rq $(notdir $@) $(PRIVATE_NAME)
-
-.PHONY: pts
-pts: $(INTERNAL_PTS_TARGET)
-cts: pts
-# generate PTS during CTS build
-ifneq ($(filter cts, $(MAKECMDGOALS)),)
-$(call dist-for-goals,cts,$(INTERNAL_PTS_TARGET))
-endif
-
-define copy-testcase-apk
-
-$(hide) $(ACP) -fp $(call intermediates-dir-for,APPS,$(1))/package.apk \
-	$(PRIVATE_DIR)/repository/testcases/$(1).apk
-
-endef
-
-define copy-testcase
-
-$(hide) $(ACP) -fp $(1) $(PRIVATE_DIR)/repository/testcases/$(notdir $1)
-
-endef
diff --git a/suite/pts/build/test_host_java_library.mk b/suite/pts/build/test_host_java_library.mk
deleted file mode 100644
index 3f410d1..0000000
--- a/suite/pts/build/test_host_java_library.mk
+++ /dev/null
@@ -1,38 +0,0 @@
-# Copyright (C) 2012 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.
-
-#
-# Builds a host library and defines a rule to generate the associated test
-# package XML needed by PTS.
-#
-
-include $(BUILD_HOST_JAVA_LIBRARY)
-
-pts_library_xml := $(PTS_TESTCASES_OUT)/$(LOCAL_MODULE).xml
-
-$(pts_library_xml): PRIVATE_PATH := $(LOCAL_PATH)/src
-$(pts_library_xml): PRIVATE_TEST_PACKAGE := $(LOCAL_PTS_TEST_PACKAGE)
-$(pts_library_xml): PRIVATE_LIBRARY := $(LOCAL_MODULE)
-$(pts_library_xml): PRIVATE_JAR_PATH := $(LOCAL_MODULE).jar
-$(pts_library_xml): $(HOST_OUT_JAVA_LIBRARIES)/$(LOCAL_MODULE).jar $(PTS_EXPECTATIONS) $(PTS_JAVA_TEST_SCANNER_DOCLET) $(PTS_JAVA_TEST_SCANNER) $(PTS_XML_GENERATOR)
-	$(hide) echo Generating test description for host library $(PRIVATE_LIBRARY)
-	$(hide) mkdir -p $(PTS_TESTCASES_OUT)
-	$(hide) $(PTS_JAVA_TEST_SCANNER) -s $(PRIVATE_PATH) \
-						-d $(PTS_JAVA_TEST_SCANNER_DOCLET) | \
-			$(PTS_XML_GENERATOR) -t hostSideOnly \
-						-j $(PRIVATE_JAR_PATH) \
-						-n $(PRIVATE_LIBRARY) \
-						-p $(PRIVATE_TEST_PACKAGE) \
-						-e $(PTS_EXPECTATIONS) \
-						-o $@
diff --git a/suite/pts/build/test_package.mk b/suite/pts/build/test_package.mk
deleted file mode 100644
index f677c3b..0000000
--- a/suite/pts/build/test_package.mk
+++ /dev/null
@@ -1,49 +0,0 @@
-# Copyright (C) 2012 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.
-
-# copied from cts/build. modified for PTS
-
-# Disable by default so "m pts" will work in emulator builds
-LOCAL_DEX_PREOPT := false
-
-include $(BUILD_PACKAGE)
-
-pts_package_apk := $(PTS_TESTCASES_OUT)/$(LOCAL_PACKAGE_NAME).apk
-pts_package_xml := $(PTS_TESTCASES_OUT)/$(LOCAL_PACKAGE_NAME).xml
-
-$(pts_package_apk): PRIVATE_PACKAGE := $(LOCAL_PACKAGE_NAME)
-$(pts_package_apk): $(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME))/package.apk | $(ACP)
-	$(hide) mkdir -p $(PTS_TESTCASES_OUT)
-	$(hide) $(ACP) -fp $< $@
-
-$(pts_package_xml): PRIVATE_PATH := $(LOCAL_PATH)
-$(pts_package_xml): PRIVATE_INSTRUMENTATION := $(LOCAL_INSTRUMENTATION_FOR)
-$(pts_package_xml): PRIVATE_PACKAGE := $(LOCAL_PACKAGE_NAME)
-$(pts_package_xml): PRIVATE_TEST_PACKAGE := com.android.pts.$(notdir $(LOCAL_PATH))
-$(pts_package_xml): PRIVATE_MANIFEST := $(LOCAL_PATH)/AndroidManifest.xml
-$(pts_package_xml): PRIVATE_TEST_TYPE := $(if $(LOCAL_PTS_TEST_RUNNER),$(LOCAL_PTS_TEST_RUNNER),'')
-$(pts_package_xml): $(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME))/package.apk $(PTS_EXPECTATIONS) $(PTS_JAVA_TEST_SCANNER_DOCLET) $(PTS_JAVA_TEST_SCANNER) $(PTS_XML_GENERATOR)
-	$(hide) echo Generating test description for java package $(PRIVATE_PACKAGE)
-	$(hide) mkdir -p $(PTS_TESTCASES_OUT)
-	$(hide) $(PTS_JAVA_TEST_SCANNER) \
-						-s $(PRIVATE_PATH) \
-						-d $(PTS_JAVA_TEST_SCANNER_DOCLET) | \
-			$(PTS_XML_GENERATOR) \
-						-t $(PRIVATE_TEST_TYPE) \
-						-m $(PRIVATE_MANIFEST) \
-						-i "$(PRIVATE_INSTRUMENTATION)" \
-						-n $(PRIVATE_PACKAGE) \
-						-p $(PRIVATE_TEST_PACKAGE) \
-						-e $(PTS_EXPECTATIONS) \
-						-o $@
diff --git a/suite/pts/hostTests/browser/browserlauncher/Android.mk b/suite/pts/deviceTests/browserbench/Android.mk
similarity index 92%
rename from suite/pts/hostTests/browser/browserlauncher/Android.mk
rename to suite/pts/deviceTests/browserbench/Android.mk
index 0592017..1ce0e49 100644
--- a/suite/pts/hostTests/browser/browserlauncher/Android.mk
+++ b/suite/pts/deviceTests/browserbench/Android.mk
@@ -24,10 +24,10 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_PACKAGE_NAME := PtsDeviceBrowserLauncher
+LOCAL_PACKAGE_NAME := PtsDeviceBrowserBench
 
 LOCAL_SDK_VERSION := 16
 
-include $(BUILD_PTS_PACKAGE)
+include $(BUILD_CTS_PACKAGE)
 
 include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/suite/pts/hostTests/browser/browserlauncher/AndroidManifest.xml b/suite/pts/deviceTests/browserbench/AndroidManifest.xml
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/AndroidManifest.xml
rename to suite/pts/deviceTests/browserbench/AndroidManifest.xml
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/base.js b/suite/pts/deviceTests/browserbench/assets/octane/base.js
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/base.js
rename to suite/pts/deviceTests/browserbench/assets/octane/base.js
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/box2d.js b/suite/pts/deviceTests/browserbench/assets/octane/box2d.js
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/box2d.js
rename to suite/pts/deviceTests/browserbench/assets/octane/box2d.js
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/code-load.js b/suite/pts/deviceTests/browserbench/assets/octane/code-load.js
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/code-load.js
rename to suite/pts/deviceTests/browserbench/assets/octane/code-load.js
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/crypto.js b/suite/pts/deviceTests/browserbench/assets/octane/crypto.js
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/crypto.js
rename to suite/pts/deviceTests/browserbench/assets/octane/crypto.js
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/css/bootstrap-responsive.css b/suite/pts/deviceTests/browserbench/assets/octane/css/bootstrap-responsive.css
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/css/bootstrap-responsive.css
rename to suite/pts/deviceTests/browserbench/assets/octane/css/bootstrap-responsive.css
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/css/bootstrap.css b/suite/pts/deviceTests/browserbench/assets/octane/css/bootstrap.css
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/css/bootstrap.css
rename to suite/pts/deviceTests/browserbench/assets/octane/css/bootstrap.css
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/css/docs.css b/suite/pts/deviceTests/browserbench/assets/octane/css/docs.css
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/css/docs.css
rename to suite/pts/deviceTests/browserbench/assets/octane/css/docs.css
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/deltablue.js b/suite/pts/deviceTests/browserbench/assets/octane/deltablue.js
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/deltablue.js
rename to suite/pts/deviceTests/browserbench/assets/octane/deltablue.js
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/earley-boyer.js b/suite/pts/deviceTests/browserbench/assets/octane/earley-boyer.js
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/earley-boyer.js
rename to suite/pts/deviceTests/browserbench/assets/octane/earley-boyer.js
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/gbemu.js b/suite/pts/deviceTests/browserbench/assets/octane/gbemu.js
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/gbemu.js
rename to suite/pts/deviceTests/browserbench/assets/octane/gbemu.js
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/ico/apple-touch-icon-114-precomposed.png b/suite/pts/deviceTests/browserbench/assets/octane/ico/apple-touch-icon-114-precomposed.png
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/ico/apple-touch-icon-114-precomposed.png
rename to suite/pts/deviceTests/browserbench/assets/octane/ico/apple-touch-icon-114-precomposed.png
Binary files differ
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/ico/apple-touch-icon-144-precomposed.png b/suite/pts/deviceTests/browserbench/assets/octane/ico/apple-touch-icon-144-precomposed.png
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/ico/apple-touch-icon-144-precomposed.png
rename to suite/pts/deviceTests/browserbench/assets/octane/ico/apple-touch-icon-144-precomposed.png
Binary files differ
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/ico/apple-touch-icon-57-precomposed.png b/suite/pts/deviceTests/browserbench/assets/octane/ico/apple-touch-icon-57-precomposed.png
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/ico/apple-touch-icon-57-precomposed.png
rename to suite/pts/deviceTests/browserbench/assets/octane/ico/apple-touch-icon-57-precomposed.png
Binary files differ
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/ico/apple-touch-icon-72-precomposed.png b/suite/pts/deviceTests/browserbench/assets/octane/ico/apple-touch-icon-72-precomposed.png
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/ico/apple-touch-icon-72-precomposed.png
rename to suite/pts/deviceTests/browserbench/assets/octane/ico/apple-touch-icon-72-precomposed.png
Binary files differ
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/ico/favicon.ico b/suite/pts/deviceTests/browserbench/assets/octane/ico/favicon.ico
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/ico/favicon.ico
rename to suite/pts/deviceTests/browserbench/assets/octane/ico/favicon.ico
Binary files differ
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/index.html b/suite/pts/deviceTests/browserbench/assets/octane/index.html
similarity index 98%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/index.html
rename to suite/pts/deviceTests/browserbench/assets/octane/index.html
index 0e2a79d..704123f 100644
--- a/suite/pts/hostTests/browser/browserlauncher/assets/octane/index.html
+++ b/suite/pts/deviceTests/browserbench/assets/octane/index.html
@@ -15,6 +15,8 @@
 <script src="js/bootstrap-transition.js"></script>
 <script src="js/bootstrap-collapse.js"></script>
 <!-- Octane benchmark code -->
+<!-- PTS -->
+<script type="text/javascript" src="pts_report.js"></script>
 <script type="text/javascript" src="base.js"></script>
 <script type="text/javascript" src="richards.js"></script>
 <script type="text/javascript" src="deltablue.js"></script>
@@ -43,7 +45,8 @@
   }
 
   function AddResult(name, result) {
-    console.log(name + ': ' + result);
+    //PTS
+    PtsReport(name, result, false);
     var box = document.getElementById("Result-" + name);
     box.innerHTML = result;
   }
@@ -65,8 +68,8 @@
     } else {
       status.innerHTML = "Octane Score (incomplete): " + score;
     }
-    // print the result for the host
-    console.log(status.innerHTML);
+    //PTS
+    PtsReport("Octane Score" + (success ? " " : "(incomplete)"), score, true);
     document.getElementById("progress-bar-container").style.visibility = 'hidden';
     document.getElementById("bottom-text").style.visibility = 'visible';
     document.getElementById("inside-anchor").removeChild(document.getElementById("bar-appendix"));
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/js/bootstrap-collapse.js b/suite/pts/deviceTests/browserbench/assets/octane/js/bootstrap-collapse.js
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/js/bootstrap-collapse.js
rename to suite/pts/deviceTests/browserbench/assets/octane/js/bootstrap-collapse.js
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/js/bootstrap-transition.js b/suite/pts/deviceTests/browserbench/assets/octane/js/bootstrap-transition.js
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/js/bootstrap-transition.js
rename to suite/pts/deviceTests/browserbench/assets/octane/js/bootstrap-transition.js
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/js/jquery.js b/suite/pts/deviceTests/browserbench/assets/octane/js/jquery.js
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/js/jquery.js
rename to suite/pts/deviceTests/browserbench/assets/octane/js/jquery.js
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/mandreel.js b/suite/pts/deviceTests/browserbench/assets/octane/mandreel.js
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/mandreel.js
rename to suite/pts/deviceTests/browserbench/assets/octane/mandreel.js
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/navier-stokes.js b/suite/pts/deviceTests/browserbench/assets/octane/navier-stokes.js
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/navier-stokes.js
rename to suite/pts/deviceTests/browserbench/assets/octane/navier-stokes.js
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/pdfjs.js b/suite/pts/deviceTests/browserbench/assets/octane/pdfjs.js
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/pdfjs.js
rename to suite/pts/deviceTests/browserbench/assets/octane/pdfjs.js
diff --git a/suite/pts/deviceTests/browserbench/assets/octane/pts_report.html b/suite/pts/deviceTests/browserbench/assets/octane/pts_report.html
new file mode 100644
index 0000000..40e2de6
--- /dev/null
+++ b/suite/pts/deviceTests/browserbench/assets/octane/pts_report.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<html>
+  <head>
+    <title>pts_report</title>
+  </head>
+  <body>
+  </body>
+</html>
diff --git a/suite/pts/deviceTests/browserbench/assets/octane/pts_report.js b/suite/pts/deviceTests/browserbench/assets/octane/pts_report.js
new file mode 100644
index 0000000..fb320ae
--- /dev/null
+++ b/suite/pts/deviceTests/browserbench/assets/octane/pts_report.js
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+/**
+ *  Utility to report benchmarking result via HTTP POST
+ *  to PTS.
+ * @param msg message to add to the report
+ * @param score resulting score
+ * @param isFinal true if this is the last / final score
+ */
+function PtsReport(msg, score, isFinal)
+{
+    req = new XMLHttpRequest();
+    req.open("POST", "pts_report.html?final=" + (isFinal ? "1" : "0") +
+             "&score=" + score + "&message=" + msg, false);
+    req.send(null)
+}
+
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/raytrace.js b/suite/pts/deviceTests/browserbench/assets/octane/raytrace.js
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/raytrace.js
rename to suite/pts/deviceTests/browserbench/assets/octane/raytrace.js
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/regexp.js b/suite/pts/deviceTests/browserbench/assets/octane/regexp.js
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/regexp.js
rename to suite/pts/deviceTests/browserbench/assets/octane/regexp.js
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/richards.js b/suite/pts/deviceTests/browserbench/assets/octane/richards.js
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/richards.js
rename to suite/pts/deviceTests/browserbench/assets/octane/richards.js
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/run.js b/suite/pts/deviceTests/browserbench/assets/octane/run.js
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/run.js
rename to suite/pts/deviceTests/browserbench/assets/octane/run.js
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/splay.js b/suite/pts/deviceTests/browserbench/assets/octane/splay.js
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/splay.js
rename to suite/pts/deviceTests/browserbench/assets/octane/splay.js
diff --git a/suite/pts/deviceTests/browserbench/src/com/android/pts/browser/BrowserBenchTest.java b/suite/pts/deviceTests/browserbench/src/com/android/pts/browser/BrowserBenchTest.java
new file mode 100644
index 0000000..07f7763
--- /dev/null
+++ b/suite/pts/deviceTests/browserbench/src/com/android/pts/browser/BrowserBenchTest.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2012 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.pts.browser;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.cts.util.TimeoutReq;
+import android.cts.util.WatchDog;
+import android.net.Uri;
+import android.provider.Browser;
+import android.util.Log;
+import android.webkit.cts.CtsTestServer;
+
+import com.android.pts.util.PtsAndroidTestCase;
+import com.android.pts.util.ResultType;
+import com.android.pts.util.ResultUnit;
+import com.android.pts.util.Stat;
+
+import java.net.URLDecoder;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.RequestLine;
+/**
+ * Browser benchmarking.
+ * It launches an activity with URL and wait for POST from the client.
+ */
+public class BrowserBenchTest extends PtsAndroidTestCase {
+    private static final String TAG = "BrowserBench";
+    private static final boolean DEBUG = false;
+    private static final String OCTANE_START_FILE = "octane/index.html";
+    private static final String ROBOHORNET_START_FILE = "robohornet/robohornet.html";
+    private static final String HOST_COMPLETION_BROADCAST = "com.android.pts.browser.completion";
+    // time-out for watch-dog. POST should happen within this time.
+    private static long BROWSER_POST_TIMEOUT_IN_MS = 10 * 60 * 1000L;
+    // watch-dog will time-out first. So make it long enough.
+    private static long BROWSER_COMPLETION_TIMEOUT_IN_MS = 60 * 60 * 1000L;
+    private static final String HTTP_USER_AGENT = "User-Agent";
+    private CtsTestServer mWebServer;
+    // used for final score
+    private ResultType mTypeNonFinal = ResultType.NEUTRAL;
+    private ResultUnit mUnitNonFinal = ResultUnit.NONE;
+    // used for all other scores
+    private ResultType mTypeFinal = ResultType.NEUTRAL;
+    private ResultUnit mUnitFinal = ResultUnit.SCORE;
+    private WatchDog mWatchDog;
+    private CountDownLatch mLatch;
+    // can be changed by each test before starting
+    private volatile int mNumberRepeat;
+    /** tells how many tests have run up to now */
+    private volatile int mRunIndex;
+    /** stores results for each runs. last entry will be the final score. */
+    private LinkedHashMap<String, double[]> mResultsMap;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mWebServer = new CtsTestServer(getContext()) {
+            @Override
+            protected HttpResponse onPost(HttpRequest request) throws Exception {
+                // post uri will look like "pts_report.html?final=1&score=10.1&message=hello"
+                RequestLine requestLine = request.getRequestLine();
+                String uriString = URLDecoder.decode(requestLine.getUri(), "UTF-8");
+                if (DEBUG) {
+                    Log.i(TAG, "uri:" + uriString);
+                }
+                String resultRe =
+                        ".*pts_report.html\\?final=([\\d])&score=([\\d]+\\.?[\\d]*)&message=([\\w][\\w ]*)";
+                Pattern resultPattern = Pattern.compile(resultRe);
+                Matcher matchResult = resultPattern.matcher(uriString);
+                if (matchResult.find()) {
+                    int isFinal = Integer.parseInt(matchResult.group(1));
+                    double score = Double.parseDouble(matchResult.group(2));
+                    String message = matchResult.group(3);
+                    Log.i(TAG, message + ":" + score);
+                    if (!mResultsMap.containsKey(message)) {
+                        mResultsMap.put(message, new double[mNumberRepeat]);
+                    }
+                    double[] scores = mResultsMap.get(message);
+                    scores[mRunIndex] = score;
+                    if (isFinal == 1) {
+                        String userAgent = request.getFirstHeader(HTTP_USER_AGENT).getValue();
+                        getReportLog().printValue(HTTP_USER_AGENT + "=" + userAgent, 0,
+                                ResultType.NEUTRAL, ResultUnit.NONE);
+                        mLatch.countDown();
+                    }
+                    mWatchDog.reset();
+                }
+                return null; // default response is OK as it will be ignored by client anyway.
+            }
+        };
+        mResultsMap = new LinkedHashMap<String, double[]>();
+        mWatchDog = new WatchDog(BROWSER_POST_TIMEOUT_IN_MS);
+        mWatchDog.start();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        mWatchDog.stop();
+        mWebServer.shutdown();
+        mWebServer = null;
+        mResultsMap = null;
+        super.tearDown();
+    }
+
+    @TimeoutReq(minutes = 60)
+    public void testOctane() throws InterruptedException {
+        String url = mWebServer.getAssetUrl(OCTANE_START_FILE) + "?auto=1";
+        final int kRepeat = 5;
+        doTest(url, ResultType.LOWER_BETTER, ResultUnit.MS,
+                ResultType.HIGHER_BETTER, ResultUnit.SCORE, kRepeat);
+    }
+
+    private void doTest(String url, ResultType typeNonFinal, ResultUnit unitNonFinal,
+            ResultType typeFinal, ResultUnit unitFinal, int numberRepeat)
+                    throws InterruptedException {
+        mTypeNonFinal = typeNonFinal;
+        mUnitNonFinal = unitNonFinal;
+        mTypeFinal = typeFinal;
+        mUnitFinal = unitFinal;
+        mNumberRepeat = numberRepeat;
+        Uri uri = Uri.parse(url);
+        for (mRunIndex = 0; mRunIndex < numberRepeat; mRunIndex++) {
+            Log.i(TAG, mRunIndex + "-th round");
+            mLatch = new CountDownLatch(1);
+            Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            // force using only one window or tab
+            intent.putExtra(Browser.EXTRA_APPLICATION_ID, getContext().getPackageName());
+            getContext().startActivity(intent);
+            boolean ok = mLatch.await(BROWSER_COMPLETION_TIMEOUT_IN_MS, TimeUnit.MILLISECONDS);
+            assertTrue("timed-out", ok);
+        }
+        // it is somewhat awkward to handle the last one specially with Map
+        int numberEntries = mResultsMap.size();
+        int numberToProcess = 1;
+        for (Map.Entry<String, double[]> entry : mResultsMap.entrySet()) {
+            String message = entry.getKey();
+            double[] scores = entry.getValue();
+            if (numberToProcess == numberEntries) { // final score
+                // store the whole results first
+                getReportLog().printArray(message, scores, mTypeFinal, mUnitFinal);
+                getReportLog().printSummary(message, Stat.getAverage(scores), mTypeFinal,
+                        mUnitFinal);
+            } else { // interim results
+                getReportLog().printArray(message, scores, mTypeNonFinal, mUnitNonFinal);
+            }
+            numberToProcess++;
+        }
+    }
+}
diff --git a/suite/pts/deviceTests/dram/Android.mk b/suite/pts/deviceTests/dram/Android.mk
index dbe0ad3..47e5b52 100644
--- a/suite/pts/deviceTests/dram/Android.mk
+++ b/suite/pts/deviceTests/dram/Android.mk
@@ -30,6 +30,6 @@
 
 LOCAL_SDK_VERSION := 16
 
-include $(BUILD_PTS_PACKAGE)
+include $(BUILD_CTS_PACKAGE)
 
 include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/suite/pts/deviceTests/dram/jni/MemoryNativeJni.cpp b/suite/pts/deviceTests/dram/jni/MemoryNativeJni.cpp
index 0c429b7..a54ab71 100644
--- a/suite/pts/deviceTests/dram/jni/MemoryNativeJni.cpp
+++ b/suite/pts/deviceTests/dram/jni/MemoryNativeJni.cpp
@@ -19,14 +19,14 @@
 #include <string.h>
 #include <sys/time.h>
 
-long currentTimeMillis()
+double currentTimeMillis()
 {
     struct timeval tv;
     gettimeofday(&tv, (struct timezone *) NULL);
-    return (long)tv.tv_sec * 1000 + tv.tv_usec / 1000;
+    return tv.tv_sec * 1000.0 + tv.tv_usec / 1000.0;
 }
 
-extern "C" JNIEXPORT jlong JNICALL Java_com_android_pts_dram_MemoryNative_runMemcpy(JNIEnv* env,
+extern "C" JNIEXPORT jdouble JNICALL Java_com_android_pts_dram_MemoryNative_runMemcpy(JNIEnv* env,
         jclass clazz, jint bufferSize, jint repetition)
 {
     char* src = new char[bufferSize];
@@ -39,13 +39,33 @@
     }
     memset(src, 0, bufferSize);
     memset(dst, 0, bufferSize);
-    long start = currentTimeMillis();
+    double start = currentTimeMillis();
     for (int i = 0; i < repetition; i++) {
         memcpy(dst, src, bufferSize);
         src[bufferSize - 1] = i & 0xff;
     }
-    long end = currentTimeMillis();
+    double end = currentTimeMillis();
     delete[] src;
     delete[] dst;
     return end - start;
 }
+
+extern "C" JNIEXPORT jdouble JNICALL Java_com_android_pts_dram_MemoryNative_runMemset(JNIEnv* env,
+        jclass clazz, jint bufferSize, jint repetition, jint c)
+{
+    char* dst = new char[bufferSize];
+    if (dst == NULL) {
+        delete[] dst;
+        env->ThrowNew(env->FindClass("java/lang/OutOfMemoryError"), "No memory");
+        return -1;
+    }
+    memset(dst, 0, bufferSize);
+    double start = currentTimeMillis();
+    for (int i = 0; i < repetition; i++) {
+        memset(dst, (c + i) & 0xff, bufferSize);
+    }
+    double end = currentTimeMillis();
+    delete[] dst;
+    return end - start;
+}
+
diff --git a/suite/pts/deviceTests/dram/src/com/android/pts/dram/BandwidthTest.java b/suite/pts/deviceTests/dram/src/com/android/pts/dram/BandwidthTest.java
index c731cc1..8f6860f 100644
--- a/suite/pts/deviceTests/dram/src/com/android/pts/dram/BandwidthTest.java
+++ b/suite/pts/deviceTests/dram/src/com/android/pts/dram/BandwidthTest.java
@@ -21,6 +21,8 @@
 import android.util.Log;
 import android.view.WindowManager;
 
+import com.android.pts.util.ResultType;
+import com.android.pts.util.ResultUnit;
 import com.android.pts.util.PtsAndroidTestCase;
 import com.android.pts.util.ReportLog;
 import com.android.pts.util.Stat;
@@ -37,6 +39,14 @@
     private static final int REPEAT_IN_EACH_CALL = 100;
     private static final int KB = 1024;
     private static final int MB = 1024 * 1024;
+    private static final int MEMSET_CHAR = 0xa5;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        // warm-up
+        MemoryNative.runMemcpy(2 * MB, 100);
+    }
 
     public void testMemcpyK004() {
         doRunMemcpy(4 * KB);
@@ -90,15 +100,74 @@
         doRunMemcpy(16 * MB);
     }
 
+    public void testMemsetK004() {
+        doRunMemset(4 * KB);
+    }
+
+    public void testMemsetK008() {
+        doRunMemset(8 * KB);
+    }
+
+    public void testMemsetK016() {
+        doRunMemset(16 * KB);
+    }
+
+    public void testMemsetK032() {
+        doRunMemset(32 * KB);
+    }
+
+    public void testMemsetK064() {
+        doRunMemset(64 * KB);
+    }
+
+    public void testMemsetK128() {
+        doRunMemset(128 * KB);
+    }
+
+    public void testMemsetK256() {
+        doRunMemset(256 * KB);
+    }
+
+    public void testMemsetK512() {
+        doRunMemset(512 * KB);
+    }
+
+    public void testMemsetM001() {
+        doRunMemset(1 * MB);
+    }
+
+    public void testMemsetM002() {
+        doRunMemset(2 * MB);
+    }
+
+    public void testMemsetM004() {
+        doRunMemset(4 * MB);
+    }
+
+    public void testMemsetM008() {
+        doRunMemset(8 * MB);
+    }
+
+    public void testMemsetM016() {
+        doRunMemset(16 * MB);
+    }
+
     private void doRunMemcpy(int bufferSize) {
         double[] result = new double[REPETITION];
-        for (int i = 0; i < REPETITION; i++) {
-            result[i] = MemoryNative.runMemcpy(bufferSize, REPEAT_IN_EACH_CALL);
+        int repeatInEachCall = REPEAT_IN_EACH_CALL;
+        if (bufferSize < (1 * MB)) {
+            // too small buffer size finishes too early to give accurate result.
+            repeatInEachCall *= (1 * MB / bufferSize);
         }
-        getReportLog().printArray("ms", result, false);
+        for (int i = 0; i < REPETITION; i++) {
+            result[i] = MemoryNative.runMemcpy(bufferSize, repeatInEachCall);
+        }
+        getReportLog().printArray("memcpy time", result, ResultType.LOWER_BETTER,
+                ResultUnit.MS);
         double[] mbps = ReportLog.calcRatePerSecArray(
-                (double)bufferSize * REPEAT_IN_EACH_CALL / 1024.0 / 1024.0, result);
-        getReportLog().printArray("MB/s", mbps, true);
+                (double)bufferSize * repeatInEachCall / 1024.0 / 1024.0, result);
+        getReportLog().printArray("memcpy throughput", mbps, ResultType.HIGHER_BETTER,
+                ResultUnit.MBPS);
         Stat.StatResult stat = Stat.getStat(mbps);
         WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
         Point size = new Point();
@@ -107,8 +176,39 @@
         double pixels = size.x * size.y;
         // now this represents how many times the whole screen can be copied in a sec.
         double screensPerSecAverage = stat.mAverage / pixels * 1024.0 * 1024.0 / 4.0;
-        double screensPerSecStddev = stat.mStddev / pixels * 1024.0 * 1024.0 / 4.0;
-        getReportLog().printSummary("screen copies per sec", screensPerSecAverage,
-                screensPerSecStddev);
+        getReportLog().printValue("memcpy in fps", screensPerSecAverage,
+                ResultType.HIGHER_BETTER, ResultUnit.FPS);
+        getReportLog().printSummary("memcpy throughput", stat.mAverage, ResultType.HIGHER_BETTER,
+                ResultUnit.MBPS);
+    }
+
+    private void doRunMemset(int bufferSize) {
+        double[] result = new double[REPETITION];
+        int repeatInEachCall = REPEAT_IN_EACH_CALL;
+        if (bufferSize < (1 * MB)) {
+            // too small buffer size finishes too early to give accurate result.
+            repeatInEachCall *= (1 * MB / bufferSize);
+        }
+        for (int i = 0; i < REPETITION; i++) {
+            result[i] = MemoryNative.runMemset(bufferSize, repeatInEachCall, MEMSET_CHAR);
+        }
+        getReportLog().printArray("memset time", result, ResultType.LOWER_BETTER,
+                ResultUnit.MS);
+        double[] mbps = ReportLog.calcRatePerSecArray(
+                (double)bufferSize * repeatInEachCall / 1024.0 / 1024.0, result);
+        getReportLog().printArray("memset throughput", mbps, ResultType.HIGHER_BETTER,
+                ResultUnit.MBPS);
+        Stat.StatResult stat = Stat.getStat(mbps);
+        WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
+        Point size = new Point();
+        wm.getDefaultDisplay().getSize(size);
+        Log.i(TAG, " x " + size.x + " y " + size.y);
+        double pixels = size.x * size.y;
+        // now this represents how many times the whole screen can be copied in a sec.
+        double screensPerSecAverage = stat.mAverage / pixels * 1024.0 * 1024.0 / 4.0;
+        getReportLog().printValue("memset in fps", screensPerSecAverage,
+                ResultType.HIGHER_BETTER, ResultUnit.FPS);
+        getReportLog().printSummary("memset throughput", stat.mAverage, ResultType.HIGHER_BETTER,
+                ResultUnit.MBPS);
     }
 }
diff --git a/suite/pts/deviceTests/dram/src/com/android/pts/dram/MemoryNative.java b/suite/pts/deviceTests/dram/src/com/android/pts/dram/MemoryNative.java
index 991de1e..71b60a0 100644
--- a/suite/pts/deviceTests/dram/src/com/android/pts/dram/MemoryNative.java
+++ b/suite/pts/deviceTests/dram/src/com/android/pts/dram/MemoryNative.java
@@ -27,5 +27,15 @@
      * @param repeatition
      * @return time spent in copying in ms.
      */
-    public static native long runMemcpy(int bufferSize, int repetition);
+    public static native double runMemcpy(int bufferSize, int repetition);
+
+    /**
+     * run memset for given number of repetition from a source to a destination buffers
+     * with each having the size of bufferSize.
+     * @param bufferSize
+     * @param repetition
+     * @param c char to set. Only LSBs will be used to get char value.
+     * @return time spent in memset in ms.
+     */
+    public static native double runMemset(int bufferSize, int repetition, int c);
 }
diff --git a/suite/pts/deviceTests/filesystemperf/Android.mk b/suite/pts/deviceTests/filesystemperf/Android.mk
index b42d48e..ca7996c 100644
--- a/suite/pts/deviceTests/filesystemperf/Android.mk
+++ b/suite/pts/deviceTests/filesystemperf/Android.mk
@@ -28,5 +28,5 @@
 
 LOCAL_SDK_VERSION := 16
 
-include $(BUILD_PTS_PACKAGE)
+include $(BUILD_CTS_PACKAGE)
 
diff --git a/suite/pts/deviceTests/filesystemperf/src/com/android/pts/filesystemperf/FileUtil.java b/suite/pts/deviceTests/filesystemperf/src/com/android/pts/filesystemperf/FileUtil.java
index 14fc45a..087cf0d 100644
--- a/suite/pts/deviceTests/filesystemperf/src/com/android/pts/filesystemperf/FileUtil.java
+++ b/suite/pts/deviceTests/filesystemperf/src/com/android/pts/filesystemperf/FileUtil.java
@@ -19,7 +19,6 @@
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileInputStream;
-import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStreamReader;
@@ -28,6 +27,8 @@
 
 import com.android.pts.util.MeasureRun;
 import com.android.pts.util.MeasureTime;
+import com.android.pts.util.ResultType;
+import com.android.pts.util.ResultUnit;
 import com.android.pts.util.ReportLog;
 import com.android.pts.util.Stat;
 import com.android.pts.util.SystemUtil;
@@ -298,12 +299,14 @@
         randomFile.close();
         double[] mbps = ReportLog.calcRatePerSecArray((double)fileSize / runsInOneGo / 1024 / 1024,
                 times);
-        report.printArray("MB/s",
-                mbps, true);
-        report.printArray("Rd amount", rdAmount, true);
+        report.printArray("read throughput",
+                mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS);
+        // This is just the amount of IO returned from kernel. So this is performance neutral.
+        report.printArray("read amount", rdAmount, ResultType.NEUTRAL, ResultUnit.BYTE);
         Stat.StatResult stat = Stat.getStat(mbps);
 
-        report.printSummary("MB/s", stat.mAverage, stat.mStddev);
+        report.printSummary("read throughput", stat.mAverage, ResultType.HIGHER_BETTER,
+                ResultUnit.MBPS);
     }
 
     /**
@@ -348,12 +351,14 @@
         randomFile.close();
         double[] mbps = ReportLog.calcRatePerSecArray((double)fileSize / runsInOneGo / 1024 / 1024,
                 times);
-        report.printArray("MB/s",
-                mbps, true);
-        report.printArray("Wr amount", wrAmount, true);
+        report.printArray("write throughput",
+                mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS);
+        report.printArray("write amount", wrAmount, ResultType.NEUTRAL,
+                ResultUnit.BYTE);
         Stat.StatResult stat = Stat.getStat(mbps);
 
-        report.printSummary("MB/s", stat.mAverage, stat.mStddev);
+        report.printSummary("write throughput", stat.mAverage, ResultType.HIGHER_BETTER,
+                ResultUnit.MBPS);
     }
 
     /**
@@ -386,11 +391,12 @@
             randomFile.close();
             double[] mbps = ReportLog.calcRatePerSecArray((double)bufferSize / 1024 / 1024,
                     times);
-            report.printArray(i + "-th round MB/s",
-                    mbps, true);
+            report.printArray(i + "-th round throughput",
+                    mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS);
             ReportLog.copyArray(mbps, mbpsAll, i * numberRepeatInOneRun);
         }
         Stat.StatResult stat = Stat.getStat(mbpsAll);
-        report.printSummary("MB/s", stat.mAverage, stat.mStddev);
+        report.printSummary("update throughput", stat.mAverage, ResultType.HIGHER_BETTER,
+                ResultUnit.MBPS);
     }
 }
diff --git a/suite/pts/deviceTests/filesystemperf/src/com/android/pts/filesystemperf/SequentialRWTest.java b/suite/pts/deviceTests/filesystemperf/src/com/android/pts/filesystemperf/SequentialRWTest.java
index 252c59e..4a9c3dd 100644
--- a/suite/pts/deviceTests/filesystemperf/src/com/android/pts/filesystemperf/SequentialRWTest.java
+++ b/suite/pts/deviceTests/filesystemperf/src/com/android/pts/filesystemperf/SequentialRWTest.java
@@ -19,6 +19,8 @@
 import android.cts.util.TimeoutReq;
 import com.android.pts.util.MeasureRun;
 import com.android.pts.util.MeasureTime;
+import com.android.pts.util.ResultType;
+import com.android.pts.util.ResultUnit;
 import com.android.pts.util.PtsAndroidTestCase;
 import com.android.pts.util.ReportLog;
 import com.android.pts.util.Stat;
@@ -47,7 +49,8 @@
     public void testSingleSequentialWrite() throws Exception {
         final int numberOfFiles =(int)(FileUtil.getFileSizeExceedingMemory(
                 getContext(), BUFFER_SIZE) / BUFFER_SIZE);
-        getReportLog().printValue("files", numberOfFiles);
+        getReportLog().printValue("files", numberOfFiles, ResultType.NEUTRAL,
+                ResultUnit.COUNT);
         final byte[] data = FileUtil.generateRandomData(BUFFER_SIZE);
         final File[] files = FileUtil.createNewFiles(getContext(), DIR_SEQ_WR,
                 numberOfFiles);
@@ -61,11 +64,13 @@
             }
         });
         double[] mbps = ReportLog.calcRatePerSecArray((double)BUFFER_SIZE / 1024 / 1024, times);
-        getReportLog().printArray("try " + numberOfFiles + " files, result MB/s",
-                mbps, true);
-        getReportLog().printArray("Wr amount", wrAmount, true);
+        getReportLog().printArray("write throughput",
+                mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS);
+        getReportLog().printArray("write amount", wrAmount, ResultType.NEUTRAL,
+                ResultUnit.BYTE);
         Stat.StatResult stat = Stat.getStat(mbps);
-        getReportLog().printSummary("MB/s", stat.mAverage, stat.mStddev);
+        getReportLog().printSummary("write throughput", stat.mAverage, ResultType.HIGHER_BETTER,
+                ResultUnit.MBPS);
     }
 
     @TimeoutReq(minutes = 60)
@@ -83,8 +88,9 @@
         final File file = FileUtil.createNewFilledFile(getContext(),
                 DIR_SEQ_RD, fileSize);
         long finish = System.currentTimeMillis();
-        getReportLog().printValue("write size " + fileSize + " result MB/s",
-                ReportLog.calcRatePerSec((double)fileSize / 1024 / 1024, finish - start));
+        getReportLog().printValue("write throughput for test file of length " + fileSize,
+                ReportLog.calcRatePerSec((double)fileSize / 1024 / 1024, finish - start),
+                ResultType.HIGHER_BETTER, ResultUnit.MBPS);
 
         final int NUMBER_READ = 10;
 
@@ -103,9 +109,10 @@
             }
         });
         double[] mbps = ReportLog.calcRatePerSecArray((double)fileSize / 1024 / 1024, times);
-        getReportLog().printArray("read MB/s",
-                mbps, true);
+        getReportLog().printArray("read throughput",
+                mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS);
         Stat.StatResult stat = Stat.getStat(mbps);
-        getReportLog().printSummary("MB/s", stat.mAverage, stat.mStddev);
+        getReportLog().printSummary("read throughput", stat.mAverage, ResultType.HIGHER_BETTER,
+                ResultUnit.MBPS);
     }
 }
diff --git a/suite/pts/hostTests/browser/browserlauncher/Android.mk b/suite/pts/deviceTests/opengl/Android.mk
similarity index 78%
copy from suite/pts/hostTests/browser/browserlauncher/Android.mk
copy to suite/pts/deviceTests/opengl/Android.mk
index 0592017..9346724 100644
--- a/suite/pts/hostTests/browser/browserlauncher/Android.mk
+++ b/suite/pts/deviceTests/opengl/Android.mk
@@ -1,4 +1,4 @@
-# Copyright (C) 2012 The Android Open Source Project
+# Copyright (C) 2013 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.
@@ -20,14 +20,16 @@
 
 LOCAL_JAVA_LIBRARIES := android.test.runner
 
-LOCAL_STATIC_JAVA_LIBRARIES := ptsutil ctsutil ctstestrunner ctstestserver
+LOCAL_STATIC_JAVA_LIBRARIES := ptsutil ctsutil ctstestrunner
+
+LOCAL_JNI_SHARED_LIBRARIES := libptsopengl_jni
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_PACKAGE_NAME := PtsDeviceBrowserLauncher
+LOCAL_PACKAGE_NAME := PtsDeviceOpenGl
 
 LOCAL_SDK_VERSION := 16
 
-include $(BUILD_PTS_PACKAGE)
+include $(BUILD_CTS_PACKAGE)
 
-include $(call all-makefiles-under,$(LOCAL_PATH))
+include $(call all-makefiles-under,$(LOCAL_PATH))
\ No newline at end of file
diff --git a/suite/pts/deviceTests/opengl/AndroidManifest.xml b/suite/pts/deviceTests/opengl/AndroidManifest.xml
new file mode 100644
index 0000000..a91766f
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.pts.opengl"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk
+        android:minSdkVersion="16" />
+
+    <uses-feature
+        android:glEsVersion="0x00020000"
+        android:required="true" />
+
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />\
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+
+    <application android:allowBackup="false" >
+        <uses-library android:name="android.test.runner" />
+
+        <activity
+            android:name="com.android.pts.opengl.primitive.GLActivity"
+            android:theme="@android:style/Theme.NoTitleBar.Fullscreen" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <instrumentation
+        android:name="android.test.InstrumentationCtsTestRunner"
+        android:label="OpenGL ES 2.0 Benchmark"
+        android:targetPackage="com.android.pts.opengl" />
+
+</manifest>
\ No newline at end of file
diff --git a/suite/pts/hostTests/ptshostutil/Android.mk b/suite/pts/deviceTests/opengl/jni/Android.mk
similarity index 63%
copy from suite/pts/hostTests/ptshostutil/Android.mk
copy to suite/pts/deviceTests/opengl/jni/Android.mk
index 14e786a..7ab7f89 100644
--- a/suite/pts/hostTests/ptshostutil/Android.mk
+++ b/suite/pts/deviceTests/opengl/jni/Android.mk
@@ -1,4 +1,4 @@
-# Copyright (C) 2012 The Android Open Source Project
+# Copyright (C) 2013 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.
@@ -11,19 +11,21 @@
 # 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.
-
+#
 LOCAL_PATH := $(call my-dir)
 
 include $(CLEAR_VARS)
 
-LOCAL_SRC_FILES := \
-    $(call all-java-files-under, src)
-
-LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed-prebuilt ddmlib-prebuilt junit
+LOCAL_MODULE := libptsopengl_jni
 
 LOCAL_MODULE_TAGS := optional
 
-LOCAL_MODULE := ptshostutil
+# Get all cpp files but not hidden files
+LOCAL_SRC_FILES := $(patsubst ./%,%, $(shell cd $(LOCAL_PATH); \
+		  find . -name "*.cpp" -and -not -name ".*"))
 
-include $(BUILD_HOST_JAVA_LIBRARY)
+LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
 
+LOCAL_SHARED_LIBRARIES := libEGL libGLESv2 libandroid libutils
+
+include $(BUILD_SHARED_LIBRARY)
\ No newline at end of file
diff --git a/suite/pts/deviceTests/opengl/jni/GLNative.cpp b/suite/pts/deviceTests/opengl/jni/GLNative.cpp
new file mode 100644
index 0000000..bd132b9
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/GLNative.cpp
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+#include <jni.h>
+
+#include <stdlib.h>
+#include <sys/time.h>
+
+#include <android/native_window.h>
+#include <android/native_window_jni.h>
+
+#include "Renderer.h"
+#include "fullpipeline/FullPipelineRenderer.h"
+#include "pixeloutput/PixelOutputRenderer.h"
+#include "shaderperf/ShaderPerfRenderer.h"
+#include "contextswitch/ContextSwitchRenderer.h"
+
+// Holds the current benchmark's renderer.
+Renderer* gRenderer = NULL;
+
+double currentTimeMillis() {
+    struct timeval tv;
+    gettimeofday(&tv, (struct timezone *) NULL);
+    return tv.tv_sec * 1000.0 + tv.tv_usec / 1000.0;
+}
+
+extern "C" JNIEXPORT jboolean JNICALL
+Java_com_android_pts_opengl_primitive_GLActivity_startBenchmark(
+        JNIEnv* env, jclass clazz, jint numFrames, jdoubleArray frameTimes) {
+    if (gRenderer == NULL) {
+        return false;
+    }
+
+    // Sets up the renderer.
+    bool success = gRenderer->setUp();
+
+    // Records the start time.
+    double start = currentTimeMillis();
+
+    // Draw off the screen.
+    for (int i = 0; i < numFrames && success; i++) {
+        // Draw a frame.
+        success = gRenderer->draw();
+    }
+
+    // Records the end time.
+    double end = currentTimeMillis();
+
+    // Sets the times in the Java array.
+    double times[] = {start, end};
+    env->SetDoubleArrayRegion(frameTimes, 0, 2, times);
+
+    // Tears down and deletes the renderer.
+    success = gRenderer->tearDown() && success;
+    delete gRenderer;
+    gRenderer = NULL;
+    return success;
+}
+
+// The following functions create the renderers for the various benchmarks.
+extern "C" JNIEXPORT void JNICALL
+Java_com_android_pts_opengl_primitive_GLActivity_setupFullPipelineBenchmark(
+        JNIEnv* env, jclass clazz, jobject surface, jboolean offscreen, jint workload) {
+    gRenderer = new FullPipelineRenderer(
+            ANativeWindow_fromSurface(env, surface), offscreen, workload);
+}
+
+extern "C" JNIEXPORT void JNICALL
+Java_com_android_pts_opengl_primitive_GLActivity_setupPixelOutputBenchmark(
+        JNIEnv* env, jclass clazz, jobject surface, jboolean offscreen, jint workload) {
+    gRenderer = new PixelOutputRenderer(
+            ANativeWindow_fromSurface(env, surface), offscreen, workload);
+}
+
+extern "C" JNIEXPORT void JNICALL
+Java_com_android_pts_opengl_primitive_GLActivity_setupShaderPerfBenchmark(
+        JNIEnv* env, jclass clazz, jobject surface, jboolean offscreen, jint workload) {
+    gRenderer = new ShaderPerfRenderer(
+            ANativeWindow_fromSurface(env, surface), offscreen, workload);
+}
+
+extern "C" JNIEXPORT void JNICALL
+Java_com_android_pts_opengl_primitive_GLActivity_setupContextSwitchBenchmark(
+        JNIEnv* env, jclass clazz, jobject surface, jboolean offscreen, jint workload) {
+    gRenderer = new ContextSwitchRenderer(
+            ANativeWindow_fromSurface(env, surface), offscreen, workload);
+}
diff --git a/suite/pts/deviceTests/opengl/jni/GLUtils.cpp b/suite/pts/deviceTests/opengl/jni/GLUtils.cpp
new file mode 100644
index 0000000..f62f5b8
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/GLUtils.cpp
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+#include <GLUtils.h>
+#include <stdlib.h>
+
+// Loads the given source code as a shader of the given type.
+static GLuint loadShader(GLenum shaderType, const char** source) {
+    GLuint shader = glCreateShader(shaderType);
+    if (shader) {
+        glShaderSource(shader, 1, source, NULL);
+        glCompileShader(shader);
+        GLint compiled = 0;
+        glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
+        if (!compiled) {
+            glDeleteShader(shader);
+            shader = 0;
+        }
+    }
+    return shader;
+}
+
+GLuint GLUtils::createProgram(const char** vertexSource,
+        const char** fragmentSource) {
+    GLuint vertexShader = loadShader(GL_VERTEX_SHADER, vertexSource);
+    if (!vertexShader) {
+        return 0;
+    }
+
+    GLuint pixelShader = loadShader(GL_FRAGMENT_SHADER, fragmentSource);
+    if (!pixelShader) {
+        return 0;
+    }
+
+    GLuint program = glCreateProgram();
+    if (program) {
+        bool success = true;
+        glAttachShader(program, vertexShader);
+        if (GLenum(GL_NO_ERROR) != glGetError()) {
+            success = false;
+        }
+        glAttachShader(program, pixelShader);
+        if (GLenum(GL_NO_ERROR) != glGetError()) {
+            success = false;
+        }
+
+        GLint linkStatus = GL_FALSE;
+        if (success) {
+            glLinkProgram(program);
+            glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
+        }
+        if (linkStatus != GL_TRUE || !success) {
+            glDeleteProgram(program);
+            program = 0;
+        }
+    }
+    return program;
+}
+
+// Rounds a number up to the smallest power of 2 that is greater than the original number.
+int GLUtils::roundUpToSmallestPowerOf2(int x) {
+    if (x < 0) {
+        return 0;
+    }
+    --x;
+    x |= x >> 1;
+    x |= x >> 2;
+    x |= x >> 4;
+    x |= x >> 8;
+    x |= x >> 16;
+    return x + 1;
+}
+
+GLuint GLUtils::genRandTex(int texWidth, int texHeight) {
+    GLuint textureId = 0;
+    int w = roundUpToSmallestPowerOf2(texWidth);
+    int h = roundUpToSmallestPowerOf2(texHeight);
+    uint32_t* m = new uint32_t[w * h];
+    if (m != NULL) {
+        uint32_t* d = m;
+        for (int y = 0; y < h; y++) {
+            for (int x = 0; x < w; x++) {
+                *d = 0xff000000 | ((y & 0xff) << 16) | ((x & 0xff) << 8)
+                        | ((x + y) & 0xff);
+                d++;
+            }
+        }
+        glGenTextures(1, &textureId);
+        glBindTexture(GL_TEXTURE_2D, textureId);
+        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA,
+                GL_UNSIGNED_BYTE, m);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+    }
+    delete[] m;
+    return textureId;
+}
diff --git a/suite/pts/deviceTests/opengl/jni/GLUtils.h b/suite/pts/deviceTests/opengl/jni/GLUtils.h
new file mode 100644
index 0000000..a0525bc
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/GLUtils.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+#ifndef GLUTILS_H
+#define GLUTILS_H
+
+#include <EGL/egl.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+class GLUtils {
+public:
+    // Creates a program with the given vertex and fragment shader source code.
+    static GLuint createProgram(const char** vertexSource,
+            const char** fragmentSource);
+    // Rounds a number up to the smallest power of 2 that is greater than the original number.
+    static int roundUpToSmallestPowerOf2(int x);
+    // Generates a random texture of the given dimensions.
+    static GLuint genRandTex(int texWidth, int texHeight);
+};
+
+#endif
diff --git a/suite/pts/deviceTests/opengl/jni/Renderer.cpp b/suite/pts/deviceTests/opengl/jni/Renderer.cpp
new file mode 100644
index 0000000..0f5c3ba
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/Renderer.cpp
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+#include "Renderer.h"
+#include <GLUtils.h>
+
+#define LOG_TAG "PTS_OPENGL"
+#define LOG_NDEBUG 0
+#include "utils/Log.h"
+
+static const EGLint contextAttribs[] = {
+        EGL_CONTEXT_CLIENT_VERSION, 2,
+        EGL_NONE };
+
+static const EGLint configAttribs[] = {
+        EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
+        EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+        EGL_RED_SIZE, 8,
+        EGL_GREEN_SIZE, 8,
+        EGL_BLUE_SIZE, 8,
+        EGL_ALPHA_SIZE, 8,
+        EGL_DEPTH_SIZE, 16,
+        EGL_STENCIL_SIZE, 8,
+        EGL_NONE };
+
+Renderer::Renderer(ANativeWindow* window, bool offscreen, int workload) :
+        mWindow(window), mEglDisplay(EGL_NO_DISPLAY), mEglSurface(EGL_NO_SURFACE),
+        mEglContext(EGL_NO_CONTEXT), mOffscreen(offscreen), mWorkload(workload) {
+}
+
+bool Renderer::setUp() {
+    mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+    if (EGL_NO_DISPLAY == mEglDisplay || EGL_SUCCESS != eglGetError()) {
+        return false;
+    }
+
+    EGLint major;
+    EGLint minor;
+    if (!eglInitialize(mEglDisplay, &major, &minor) || EGL_SUCCESS != eglGetError()) {
+        return false;
+    }
+
+    EGLint numConfigs = 0;
+    if (!eglChooseConfig(mEglDisplay, configAttribs, &mGlConfig, 1, &numConfigs)
+            || EGL_SUCCESS != eglGetError()) {
+        return false;
+    }
+
+    mEglSurface = eglCreateWindowSurface(mEglDisplay, mGlConfig, mWindow, NULL);
+    if (EGL_NO_SURFACE == mEglSurface || EGL_SUCCESS != eglGetError()) {
+        return false;
+    }
+
+    mEglContext = eglCreateContext(mEglDisplay, mGlConfig, EGL_NO_CONTEXT, contextAttribs);
+    if (EGL_NO_CONTEXT == mEglContext || EGL_SUCCESS != eglGetError()) {
+        return false;
+    }
+
+    if (!eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)
+            || EGL_SUCCESS != eglGetError()) {
+        return false;
+    }
+
+    if (!eglQuerySurface(mEglDisplay, mEglSurface, EGL_WIDTH, &width)
+            || EGL_SUCCESS != eglGetError()) {
+        return false;
+    }
+    if (!eglQuerySurface(mEglDisplay, mEglSurface, EGL_HEIGHT, &height)
+            || EGL_SUCCESS != eglGetError()) {
+        return false;
+    }
+
+    glViewport(0, 0, width, height);
+
+    if (mOffscreen) {
+        int w = GLUtils::roundUpToSmallestPowerOf2(width);
+        int h = GLUtils::roundUpToSmallestPowerOf2(height);
+        if (!createFBO(mFboId, mRboId, mCboId, w, h)) {
+            return false;
+        }
+    } else {
+        mFboId = 0;
+        mRboId = 0;
+        mCboId = 0;
+    }
+
+    GLuint err = glGetError();
+    if (err != GL_NO_ERROR) {
+        ALOGV("GLError %d", err);
+        return false;
+    }
+    return true;
+}
+
+bool Renderer::createFBO(GLuint& fboId, GLuint& rboId, GLuint& cboId, int width, int height) {
+    glGenFramebuffers(1, &fboId);
+    glBindFramebuffer(GL_FRAMEBUFFER, fboId);
+
+    glGenRenderbuffers(1, &rboId);
+    glBindRenderbuffer(GL_RENDERBUFFER, rboId);
+    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height);
+    glBindRenderbuffer(GL_RENDERBUFFER, 0);
+    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rboId);
+
+    glGenRenderbuffers(1, &cboId);
+    glBindRenderbuffer(GL_RENDERBUFFER, cboId);
+    glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB565, width, height);
+    glBindRenderbuffer(GL_RENDERBUFFER, 0);
+    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, cboId);
+
+    GLuint err = glGetError();
+    if (err != GL_NO_ERROR) {
+        ALOGV("GLError %d", err);
+        return false;
+    }
+
+    return glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE;
+}
+
+bool Renderer::tearDown() {
+    if (mFboId != 0) {
+        glDeleteFramebuffers(1, &mFboId);
+        mFboId = 0;
+    }
+    if (mRboId != 0) {
+        glDeleteRenderbuffers(1, &mRboId);
+        mRboId = 0;
+    }
+    if (mCboId != 0) {
+        glDeleteRenderbuffers(1, &mCboId);
+        mCboId = 0;
+    }
+    if (mEglContext != EGL_NO_CONTEXT) {
+        eglDestroyContext(mEglDisplay, mEglContext);
+        mEglContext = EGL_NO_CONTEXT;
+    }
+    if (mEglSurface != EGL_NO_SURFACE) {
+        eglDestroySurface(mEglDisplay, mEglSurface);
+        mEglSurface = EGL_NO_SURFACE;
+    }
+    if (mEglDisplay != EGL_NO_DISPLAY) {
+        eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+        eglTerminate(mEglDisplay);
+        mEglDisplay = EGL_NO_DISPLAY;
+    }
+    return EGL_SUCCESS == eglGetError();
+}
diff --git a/suite/pts/deviceTests/opengl/jni/Renderer.h b/suite/pts/deviceTests/opengl/jni/Renderer.h
new file mode 100644
index 0000000..a50d81c
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/Renderer.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+#ifndef RENDERER_H
+#define RENDERER_H
+
+#include <android/native_window.h>
+
+#include <EGL/egl.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+class Renderer {
+public:
+    Renderer(ANativeWindow* window, bool offscreen, int workload);
+    virtual bool setUp();
+    virtual bool tearDown();
+    virtual bool draw() = 0;
+    virtual ~Renderer() {};
+protected:
+    bool createFBO(GLuint& fboId, GLuint& rboId, GLuint& cboId, int width, int height);
+    ANativeWindow* mWindow;
+    EGLDisplay mEglDisplay;
+    EGLSurface mEglSurface;
+    EGLContext mEglContext;
+    EGLConfig mGlConfig;
+    GLuint mFboId; //Frame buffer
+    GLuint mRboId; //Depth buffer
+    GLuint mCboId; //Color buffer
+    GLuint mProgramId;
+    EGLint width;
+    EGLint height;
+    bool mOffscreen;
+    int mWorkload;
+};
+#endif
diff --git a/suite/pts/deviceTests/opengl/jni/contextswitch/ContextSwitchRenderer.cpp b/suite/pts/deviceTests/opengl/jni/contextswitch/ContextSwitchRenderer.cpp
new file mode 100644
index 0000000..0d85cae
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/contextswitch/ContextSwitchRenderer.cpp
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+#include <android/native_window.h>
+
+#include <stdlib.h>
+
+#include <EGL/egl.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+#include "ContextSwitchRenderer.h"
+#include <GLUtils.h>
+
+#define LOG_TAG "PTS_OPENGL"
+#define LOG_NDEBUG 0
+#include "utils/Log.h"
+
+static const EGLint contextAttribs[] =
+        { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
+
+static const int CS_NUM_VERTICES = 6;
+
+static const float CS_VERTICES[CS_NUM_VERTICES * 3] = {
+        1.0f, 1.0f, -1.0f,
+        -1.0f, 1.0f, -1.0f,
+        -1.0f, -1.0f, -1.0f,
+        -1.0f, -1.0f, -1.0f,
+        1.0f, -1.0f, -1.0f,
+        1.0f, 1.0f, -1.0f };
+
+static const float CS_TEX_COORDS[CS_NUM_VERTICES * 2] = {
+        1.0f, 1.0f,
+        0.0f, 1.0f,
+        0.0f, 0.0f,
+        0.0f, 0.0f,
+        1.0f, 0.0f,
+        1.0f, 1.0f };
+
+static const char* CS_VERTEX =
+        "attribute vec4 a_Position;"
+        "attribute vec2 a_TexCoord;"
+        "varying vec2 v_TexCoord;"
+        "void main() {"
+        "  v_TexCoord = a_TexCoord;"
+        "  gl_Position = a_Position;"
+        "}";
+
+static const char* CS_FRAGMENT =
+        "precision mediump float;"
+        "uniform sampler2D u_Texture;"
+        "varying vec2 v_TexCoord;"
+        "void main() {"
+        "  gl_FragColor = texture2D(u_Texture, v_TexCoord);"
+        "}";
+
+ContextSwitchRenderer::ContextSwitchRenderer(ANativeWindow* window, bool offscreen, int workload) :
+        Renderer(window, offscreen, workload), mContexts(NULL) {
+}
+
+bool ContextSwitchRenderer::setUp() {
+    if (!Renderer::setUp()) {
+        return false;
+    }
+
+    // We don't need the context created by Renderer.
+    eglDestroyContext(mEglDisplay, mEglContext);
+    mEglContext = EGL_NO_CONTEXT;
+
+    int w = GLUtils::roundUpToSmallestPowerOf2(width);
+    int h = GLUtils::roundUpToSmallestPowerOf2(height);
+
+    mContexts = new EGLContext[mWorkload];
+    mTextureIds = new GLuint[mWorkload];
+    mProgramIds = new GLuint[mWorkload];
+    mTextureUniformHandles = new GLuint[mWorkload];
+    mPositionHandles = new GLuint[mWorkload];
+    mTexCoordHandles = new GLuint[mWorkload];
+    if (mOffscreen) {
+        mFboIds = new GLuint[mWorkload];
+        mRboIds = new GLuint[mWorkload];
+        mCboIds = new GLuint[mWorkload];
+    }
+    for (int i = 0; i < mWorkload; i++) {
+        mContexts[i] = eglCreateContext(mEglDisplay, mGlConfig, EGL_NO_CONTEXT, contextAttribs);
+        if (EGL_NO_CONTEXT == mContexts[i] || EGL_SUCCESS != eglGetError()) {
+            return false;
+        }
+
+        if (!eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mContexts[i])
+                || EGL_SUCCESS != eglGetError()) {
+            return false;
+        }
+
+        if (mOffscreen) {
+            // Setup FBOs.
+            if (!Renderer::createFBO(mFboIds[i], mRboIds[i], mCboIds[i], w, h)) {
+                return false;
+            }
+        }
+
+        // Setup textures.
+        mTextureIds[i] = GLUtils::genRandTex(64, 64);
+        if (mTextureIds[i] == 0) {
+            return false;
+        }
+
+        // Create program.
+        mProgramIds[i] = GLUtils::createProgram(&CS_VERTEX, &CS_FRAGMENT);
+        if (mProgramIds[i] == 0) {
+            return false;
+        }
+        // Bind attributes.
+        mTextureUniformHandles[i] = glGetUniformLocation(mProgramIds[i], "u_Texture");
+        mPositionHandles[i] = glGetAttribLocation(mProgramIds[i], "a_Position");
+        mTexCoordHandles[i] = glGetAttribLocation(mProgramIds[i], "a_TexCoord");
+    }
+
+    GLuint err = glGetError();
+    if (err != GL_NO_ERROR) {
+        ALOGV("GLError %d", err);
+        return false;
+    }
+
+    return true;
+}
+
+bool ContextSwitchRenderer::tearDown() {
+    if (mContexts) {
+        for (int i = 0; i < mWorkload; i++) {
+            eglDestroyContext(mEglDisplay, mContexts[i]);
+        }
+        delete[] mContexts;
+    }
+    if (mOffscreen) {
+        if (mFboIds) {
+            glDeleteFramebuffers(mWorkload, mFboIds);
+            delete[] mFboIds;
+        }
+        if (mRboIds) {
+            glDeleteRenderbuffers(mWorkload, mRboIds);
+            delete[] mRboIds;
+        }
+        if (mCboIds) {
+            glDeleteRenderbuffers(mWorkload, mCboIds);
+            delete[] mCboIds;
+        }
+    }
+    if (mTextureIds) {
+        glDeleteTextures(mWorkload, mTextureIds);
+        delete[] mTextureIds;
+    }
+    if (!Renderer::tearDown()) {
+        return false;
+    }
+    return true;
+}
+
+bool ContextSwitchRenderer::draw() {
+    for (int i = 0; i < mWorkload; i++) {
+        if (!eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mContexts[i])
+                || EGL_SUCCESS != eglGetError()) {
+            return false;
+        }
+
+        if (mOffscreen) {
+            glBindFramebuffer(GL_FRAMEBUFFER, mFboIds[i]);
+            if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
+                return false;
+            }
+        }
+
+        glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+        glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
+
+        glUseProgram(mProgramIds[i]);
+        glActiveTexture(GL_TEXTURE0);
+        // Bind the texture to this unit.
+        glBindTexture(GL_TEXTURE_2D, mTextureIds[i]);
+
+        // Tell the texture uniform sampler to use this texture in the shader by binding to texture
+        // unit 0.
+        glUniform1i(mTextureUniformHandles[i], 0);
+
+        glEnableVertexAttribArray(mPositionHandles[i]);
+        glEnableVertexAttribArray(mTexCoordHandles[i]);
+        glVertexAttribPointer(mPositionHandles[i], 3, GL_FLOAT, false, 0, CS_VERTICES);
+        glVertexAttribPointer(mTexCoordHandles[i], 2, GL_FLOAT, false, 0, CS_TEX_COORDS);
+
+        glDrawArrays(GL_TRIANGLES, 0, CS_NUM_VERTICES);
+        glFinish();
+    }
+
+    GLuint err = glGetError();
+    if (err != GL_NO_ERROR) {
+        ALOGV("GLError %d", err);
+        return false;
+    }
+
+    return (mOffscreen) ? true : eglSwapBuffers(mEglDisplay, mEglSurface);
+}
diff --git a/suite/pts/deviceTests/opengl/jni/contextswitch/ContextSwitchRenderer.h b/suite/pts/deviceTests/opengl/jni/contextswitch/ContextSwitchRenderer.h
new file mode 100644
index 0000000..3dfe9f3
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/contextswitch/ContextSwitchRenderer.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+#ifndef CONTEXTSWITCHRENDERER_H
+#define CONTEXTSWITCHRENDERER_H
+
+#include <Renderer.h>
+
+class ContextSwitchRenderer: public Renderer {
+public:
+    ContextSwitchRenderer(ANativeWindow* window, bool offscreen, int workload);
+    virtual ~ContextSwitchRenderer() {};
+    bool setUp();
+    bool tearDown();
+    bool draw();
+private:
+    GLuint mTextureUniformHandle;
+    GLuint mPositionHandle;
+    GLuint mTexCoordHandle;
+    EGLContext* mContexts;
+    GLuint* mTextureIds;
+    GLuint* mFboIds;
+    GLuint* mRboIds;
+    GLuint* mCboIds;
+    GLuint* mProgramIds;
+    GLuint* mTextureUniformHandles;
+    GLuint* mPositionHandles;
+    GLuint* mTexCoordHandles;
+};
+
+#endif
diff --git a/suite/pts/deviceTests/opengl/jni/fullpipeline/FullPipelineMesh.cpp b/suite/pts/deviceTests/opengl/jni/fullpipeline/FullPipelineMesh.cpp
new file mode 100644
index 0000000..c0e5250
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/fullpipeline/FullPipelineMesh.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+#include "FullPipelineMesh.h"
+#include "FullPipelineProgram.h"
+
+FullPipelineMesh::FullPipelineMesh(const Mesh* mesh) :
+        MeshNode(mesh) {
+}
+
+void FullPipelineMesh::before(Program& program, Matrix& model, Matrix& view, Matrix& projection) {
+    FullPipelineProgram& prog = (FullPipelineProgram&) program;
+
+    glActiveTexture(GL_TEXTURE0);
+    // Bind the texture to this unit.
+    glBindTexture(GL_TEXTURE_2D, mMesh->mTextureId);
+    // Tell the texture uniform sampler to use this texture in the shader by binding to texture
+    // unit 0.
+    glUniform1i(prog.mTextureUniformHandle, 0);
+
+    glEnableVertexAttribArray(prog.mPositionHandle);
+    glEnableVertexAttribArray(prog.mNormalHandle);
+    glEnableVertexAttribArray(prog.mTexCoordHandle);
+    glVertexAttribPointer(prog.mPositionHandle, 3, GL_FLOAT, false, 0, mMesh->mVertices);
+    glVertexAttribPointer(prog.mNormalHandle, 3, GL_FLOAT, false, 0, mMesh->mNormals);
+    glVertexAttribPointer(prog.mTexCoordHandle, 2, GL_FLOAT, false, 0, mMesh->mTexCoords);
+
+    // This multiplies the view matrix by the model matrix, and stores the result in the MVP
+    // matrix (which currently contains model * view).
+    prog.mMVMatrix.multiply(view, model);
+
+    // Pass in the modelview matrix.
+    glUniformMatrix4fv(prog.mMVMatrixHandle, 1, false, prog.mMVMatrix.mData);
+
+    // This multiplies the modelview matrix by the projection matrix, and stores the result in
+    // the MVP matrix (which now contains model * view * projection).
+    prog.mMVPMatrix.multiply(projection, prog.mMVMatrix);
+
+    // Pass in the combined matrix.
+    glUniformMatrix4fv(prog.mMVPMatrixHandle, 1, false, prog.mMVPMatrix.mData);
+
+    // Pass in the light position in eye space.
+    glUniform3f(prog.mLightPosHandle, prog.mLightPosInEyeSpace[0], prog.mLightPosInEyeSpace[1],
+            prog.mLightPosInEyeSpace[2]);
+
+    glDrawArrays(GL_TRIANGLES, 0, mMesh->mNumVertices);
+}
+
+void FullPipelineMesh::after(Program& program, Matrix& model, Matrix& view, Matrix& projection) {
+}
diff --git a/suite/pts/deviceTests/opengl/jni/fullpipeline/FullPipelineMesh.h b/suite/pts/deviceTests/opengl/jni/fullpipeline/FullPipelineMesh.h
new file mode 100644
index 0000000..3fcb8ae
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/fullpipeline/FullPipelineMesh.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+#ifndef FULLPIPELINEMESH_H
+#define FULLPIPELINEMESH_H
+
+#include <graphics/Matrix.h>
+#include <graphics/Mesh.h>
+#include <graphics/MeshNode.h>
+#include <graphics/Program.h>
+
+class FullPipelineMesh: public MeshNode {
+public:
+    FullPipelineMesh(const Mesh* mesh);
+    virtual ~FullPipelineMesh() {};
+protected:
+    virtual void before(Program& program, Matrix& model, Matrix& view,
+            Matrix& projection);
+    virtual void after(Program& program, Matrix& model, Matrix& view,
+            Matrix& projection);
+};
+
+#endif
diff --git a/suite/pts/deviceTests/opengl/jni/fullpipeline/FullPipelineProgram.cpp b/suite/pts/deviceTests/opengl/jni/fullpipeline/FullPipelineProgram.cpp
new file mode 100644
index 0000000..e3abc2c
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/fullpipeline/FullPipelineProgram.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+#include "FullPipelineProgram.h"
+
+#include <EGL/egl.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+FullPipelineProgram::FullPipelineProgram(GLuint programId) :
+        Program(programId) {
+    mLightPosInModelSpace[0] = 0.0f;
+    mLightPosInModelSpace[1] = 2.0f;
+    mLightPosInModelSpace[2] = 2.0f;
+    mLightPosInModelSpace[3] = 1.0f;
+    mMVMatrixHandle = glGetUniformLocation(programId, "u_MVMatrix");
+    mMVPMatrixHandle = glGetUniformLocation(programId, "u_MVPMatrix");
+    mLightPosHandle = glGetUniformLocation(programId, "u_LightPos");
+    mTextureUniformHandle = glGetUniformLocation(programId, "u_Texture");
+    mPositionHandle = glGetAttribLocation(programId, "a_Position");
+    mNormalHandle = glGetAttribLocation(programId, "a_Normal");
+    mTexCoordHandle = glGetAttribLocation(programId, "a_TexCoordinate");
+}
+
+void FullPipelineProgram::before(Matrix& model, Matrix& view, Matrix& projection) {
+    Program::before(model, view, projection);
+    mLightModelMatrix.identity();
+
+    Matrix::multiplyVector(mLightPosInWorldSpace, mLightModelMatrix, mLightPosInModelSpace);
+    Matrix::multiplyVector(mLightPosInEyeSpace, view, mLightPosInWorldSpace);
+}
diff --git a/suite/pts/deviceTests/opengl/jni/fullpipeline/FullPipelineProgram.h b/suite/pts/deviceTests/opengl/jni/fullpipeline/FullPipelineProgram.h
new file mode 100644
index 0000000..3eab1c4
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/fullpipeline/FullPipelineProgram.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+#ifndef FULLPIPELINEPROGRAM_H
+#define FULLPIPELINEPROGRAM_H
+
+#include <graphics/Matrix.h>
+#include <graphics/Program.h>
+
+#include <EGL/egl.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+class FullPipelineProgram: public Program {
+public:
+    FullPipelineProgram(GLuint programId);
+    virtual ~FullPipelineProgram() {};
+    Matrix mMVMatrix;
+    Matrix mMVPMatrix;
+    Matrix mLightModelMatrix;
+    float mLightPosInModelSpace[4];
+    float mLightPosInWorldSpace[4];
+    float mLightPosInEyeSpace[4];
+
+    int mMVMatrixHandle;
+    int mMVPMatrixHandle;
+    int mLightPosHandle;
+    int mTexCoordHandle;
+    int mPositionHandle;
+    int mNormalHandle;
+    int mTextureUniformHandle;
+    virtual void before(Matrix& model, Matrix& view, Matrix& projection);
+};
+
+#endif
diff --git a/suite/pts/deviceTests/opengl/jni/fullpipeline/FullPipelineRenderer.cpp b/suite/pts/deviceTests/opengl/jni/fullpipeline/FullPipelineRenderer.cpp
new file mode 100644
index 0000000..c2ffecc
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/fullpipeline/FullPipelineRenderer.cpp
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+#include <math.h>
+#include <EGL/egl.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+#include "FullPipelineMesh.h"
+#include "FullPipelineProgram.h"
+#include "FullPipelineRenderer.h"
+#include <graphics/Mesh.h>
+#include <graphics/TransformationNode.h>
+#include <GLUtils.h>
+
+#define LOG_TAG "PTS_OPENGL"
+#define LOG_NDEBUG 0
+#include "utils/Log.h"
+
+static const int FP_NUM_VERTICES = 6;
+
+static const float FP_VERTICES[FP_NUM_VERTICES * 3] = {
+        1.0f, 1.0f, 0.0f,
+        0.0f, 1.0f, 0.0f,
+        0.0f, 0.0f, 0.0f,
+        0.0f, 0.0f, 0.0f,
+        1.0f, 0.0f, 0.0f,
+        1.0f, 1.0f, 0.0f };
+
+static const float FP_NORMALS[FP_NUM_VERTICES * 3] = {
+        0.0f, 0.0f, 1.0f,
+        0.0f, 0.0f, 1.0f,
+        0.0f, 0.0f, 1.0f,
+        0.0f, 0.0f, 1.0f,
+        0.0f, 0.0f, 1.0f,
+        0.0f, 0.0f, 1.0f };
+
+static const float FP_TEX_COORDS[FP_NUM_VERTICES * 2] = {
+        1.0f, 1.0f,
+        0.0f, 1.0f,
+        0.0f, 0.0f,
+        0.0f, 0.0f,
+        1.0f, 0.0f,
+        1.0f, 1.0f };
+
+static const char* FP_VERTEX =
+        "uniform mat4 u_MVPMatrix;"
+        "uniform mat4 u_MVMatrix;"
+        "attribute vec4 a_Position;"
+        "attribute vec3 a_Normal;"
+        "attribute vec2 a_TexCoordinate;"
+        "varying vec3 v_Position;"
+        "varying vec3 v_Normal;"
+        "varying vec2 v_TexCoordinate;"
+        "void main() {\n"
+        "  // Transform the vertex into eye space.\n"
+        "  v_Position = vec3(u_MVMatrix * a_Position);\n"
+        "  // Pass through the texture coordinate.\n"
+        "  v_TexCoordinate = a_TexCoordinate;\n"
+        "  // Transform the normal\'s orientation into eye space.\n"
+        "  v_Normal = vec3(u_MVMatrix * vec4(a_Normal, 0.0));\n"
+        "  // Multiply to get the final point in normalized screen coordinates.\n"
+        "  gl_Position = u_MVPMatrix * a_Position;\n"
+        "}";
+
+static const char* FP_FRAGMENT =
+        "precision mediump float;"
+        "uniform vec3 u_LightPos;"
+        "uniform sampler2D u_Texture;"
+        "varying vec3 v_Position;"
+        "varying vec3 v_Normal;"
+        "varying vec2 v_TexCoordinate;"
+        "void main() {\n"
+        "  // Will be used for attenuation.\n"
+        "  float distance = length(u_LightPos - v_Position);\n"
+        "  // Get a lighting direction vector from the light to the vertex.\n"
+        "  vec3 lightVector = normalize(u_LightPos - v_Position);\n"
+        "  // Calculate the dot product of the light vector and vertex normal.\n"
+        "  float diffuse = max(dot(v_Normal, lightVector), 0.0);\n"
+        "  // Add attenuation.\n"
+        "  diffuse = diffuse * (1.0 / (1.0 + (0.01 * distance)));\n"
+        "  // Add ambient lighting\n"
+        "  diffuse = diffuse + 0.25;\n"
+        "  // Multiply the diffuse illumination and texture to get final output color.\n"
+        "  gl_FragColor = (diffuse * texture2D(u_Texture, v_TexCoordinate));\n"
+        "}";
+
+FullPipelineRenderer::FullPipelineRenderer(ANativeWindow* window, bool offscreen, int workload) :
+        Renderer(window, offscreen, workload), mProgram(NULL), mSceneGraph(NULL),
+        mModelMatrix(NULL), mViewMatrix(NULL), mProjectionMatrix(NULL), mMesh(NULL) {
+}
+
+bool FullPipelineRenderer::setUp() {
+    if (!Renderer::setUp()) {
+        return false;
+    }
+
+    mProgramId = GLUtils::createProgram(&FP_VERTEX, &FP_FRAGMENT);
+    if (mProgramId == 0)
+        return false;
+    mProgram = new FullPipelineProgram(mProgramId);
+
+    mModelMatrix = new Matrix();
+
+    // Position the eye in front of the origin.
+    float eyeX = 0.0f;
+    float eyeY = 0.0f;
+    float eyeZ = 2.0f;
+
+    // We are looking at the origin
+    float centerX = 0.0f;
+    float centerY = 0.0f;
+    float centerZ = 0.0f;
+
+    // Set our up vector. This is where our head would be pointing were we holding the camera.
+    float upX = 0.0f;
+    float upY = 1.0f;
+    float upZ = 0.0f;
+
+    // Set the view matrix. This matrix can be said to represent the camera position.
+    mViewMatrix = Matrix::newLookAt(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ);
+
+    // Create a new perspective projection matrix. The height will stay the same
+    // while the width will vary as per aspect ratio.
+    float ratio = (float) width / height;
+    float left = -ratio;
+    float right = ratio;
+    float bottom = -1.0f;
+    float top = 1.0f;
+    float near = 1.0f;
+    float far = 3.0f;
+
+    mProjectionMatrix = Matrix::newFrustum(left, right, bottom, top, near, far);
+
+    // Setup texture.
+    mTextureId = GLUtils::genRandTex(width, height);
+    if (mTextureId == 0) {
+        return false;
+    }
+
+    float count = pow(2, mWorkload - 1);
+    float middle = count / 2.0f;
+    float scale = 1.0f / count;
+
+    mMesh = new Mesh(FP_VERTICES, FP_NORMALS, FP_TEX_COORDS, FP_NUM_VERTICES, mTextureId);
+    mSceneGraph = new ProgramNode();
+
+    for (int i = 0; i < count; i++) {
+        for (int j = 0; j < count; j++) {
+            Matrix* transformMatrix = Matrix::newScale(scale, scale, scale);
+            transformMatrix->translate(i - middle, j - middle, 0.0f);
+            TransformationNode* transformNode = new TransformationNode(transformMatrix);
+            mSceneGraph->addChild(transformNode);
+            FullPipelineMesh* meshNode = new FullPipelineMesh(mMesh);
+            transformNode->addChild(meshNode);
+        }
+    }
+    return true;
+}
+
+bool FullPipelineRenderer::tearDown() {
+    if (mTextureId != 0) {
+        glDeleteTextures(1, &mTextureId);
+        mTextureId = 0;
+    }
+    if (!Renderer::tearDown()) {
+        return false;
+    }
+    delete mModelMatrix;
+    mModelMatrix = NULL;
+    delete mViewMatrix;
+    mViewMatrix = NULL;
+    delete mProjectionMatrix;
+    mProjectionMatrix = NULL;
+    delete mProgram;
+    mProgram = NULL;
+    delete mSceneGraph;
+    mSceneGraph = NULL;
+    delete mMesh;
+    mMesh = NULL;
+    return true;
+}
+
+bool FullPipelineRenderer::draw() {
+    if (mOffscreen) {
+        glBindFramebuffer(GL_FRAMEBUFFER, mFboId);
+    }
+    // Set the background clear color to black.
+    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+    // Use culling to remove back faces.
+    glEnable(GL_CULL_FACE);
+    // Use depth testing.
+    glEnable(GL_DEPTH_TEST);
+    glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
+    mModelMatrix->identity();
+    mSceneGraph->draw(*mProgram, *mModelMatrix, *mViewMatrix, *mProjectionMatrix);
+
+    GLuint err = glGetError();
+    if (err != GL_NO_ERROR) {
+        ALOGV("GLError %d", err);
+        return false;
+    }
+
+    if (mOffscreen) {
+        glFinish();
+        return true;
+    } else {
+        return eglSwapBuffers(mEglDisplay, mEglSurface);
+    }
+}
diff --git a/suite/pts/deviceTests/opengl/jni/fullpipeline/FullPipelineRenderer.h b/suite/pts/deviceTests/opengl/jni/fullpipeline/FullPipelineRenderer.h
new file mode 100644
index 0000000..0c5acae
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/fullpipeline/FullPipelineRenderer.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+#ifndef FULLPIPELINERENDERER_H
+#define FULLPIPELINERENDERER_H
+
+#include "FullPipelineProgram.h"
+
+#include <Renderer.h>
+#include <graphics/Matrix.h>
+#include <graphics/Mesh.h>
+#include <graphics/ProgramNode.h>
+
+class FullPipelineRenderer: public Renderer {
+public:
+    FullPipelineRenderer(ANativeWindow* window, bool offscreen, int workload);
+    virtual ~FullPipelineRenderer() {};
+    bool setUp();
+    bool tearDown();
+    bool draw();
+private:
+    FullPipelineProgram* mProgram;
+    ProgramNode* mSceneGraph;
+    Matrix* mModelMatrix;
+    Matrix* mViewMatrix;
+    Matrix* mProjectionMatrix;
+    Mesh* mMesh;
+    GLuint mTextureId;
+};
+#endif
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/Matrix.cpp b/suite/pts/deviceTests/opengl/jni/graphics/Matrix.cpp
new file mode 100644
index 0000000..83591e8
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/graphics/Matrix.cpp
@@ -0,0 +1,329 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+#include "Matrix.h"
+#include <string.h>
+#include <math.h>
+
+#define LOG_TAG "PTS_OPENGL"
+#define LOG_NDEBUG 0
+#include "utils/Log.h"
+
+Matrix::Matrix() {
+    identity();
+}
+
+Matrix::Matrix(const Matrix& src) {
+    loadWith(src);
+}
+
+void Matrix::print(const char* label) {
+    ALOGI("%c", *label);
+    for (int i = 0; i < 4; i++) {
+        const float* d = &(mData[i * 4]);
+        ALOGI("%f %f %f %f\n", d[0], d[1], d[2], d[3]);
+    }
+}
+
+bool Matrix::equals(const Matrix& src) {
+    bool equals = true;
+    const float* d = src.mData;
+    for (int i = 0; i < MATRIX_SIZE && equals; i++) {
+        if (mData[i] != d[i]) {
+            equals = false;
+        }
+    }
+    return equals;
+}
+
+void Matrix::loadWith(const Matrix& src) {
+    memcpy(mData, src.mData, MATRIX_SIZE * sizeof(float));
+}
+
+void Matrix::identity() {
+    mData[0] = 1.0f;
+    mData[1] = 0.0f;
+    mData[2] = 0.0f;
+    mData[3] = 0.0f;
+
+    mData[4] = 0.0f;
+    mData[5] = 1.0f;
+    mData[6] = 0.0f;
+    mData[7] = 0.0f;
+
+    mData[8] = 0.0f;
+    mData[9] = 0.0f;
+    mData[10] = 1.0f;
+    mData[11] = 0.0f;
+
+    mData[12] = 0.0f;
+    mData[13] = 0.0f;
+    mData[14] = 0.0f;
+    mData[15] = 1.0f;
+}
+
+void Matrix::translate(float x, float y, float z) {
+    Matrix* m = newTranslate(x, y, z);
+    Matrix* temp = new Matrix(*this);
+    if (m != NULL && temp != NULL) {
+        multiply(*temp, *m);
+    }
+    delete m;
+    delete temp;
+}
+
+void Matrix::scale(float x, float y, float z) {
+    Matrix* m = newScale(x, y, z);
+    Matrix* temp = new Matrix(*this);
+    if (m != NULL && temp != NULL) {
+        multiply(*temp, *m);
+    }
+    delete m;
+    delete temp;
+}
+
+void Matrix::rotate(float degrees, float x, float y, float z) {
+    Matrix* m = newRotate(degrees, x, y, z);
+    Matrix* temp = new Matrix(*this);
+    if (m != NULL && temp != NULL) {
+        multiply(*temp, *m);
+    }
+    delete m;
+    delete temp;
+}
+
+void Matrix::multiply(const Matrix& l, const Matrix& r) {
+    float const* const lhs = l.mData;
+    float const* const rhs = r.mData;
+    for (int i = 0; i < 4; i++) {
+        const int i4 = i * 4;
+        float x = 0;
+        float y = 0;
+        float z = 0;
+        float w = 0;
+
+        for (int j = 0; j < 4; j++) {
+            const int j4 = j * 4;
+            const float e = rhs[i4 + j];
+            x += lhs[j4 + 0] * e;
+            y += lhs[j4 + 1] * e;
+            z += lhs[j4 + 2] * e;
+            w += lhs[j4 + 3] * e;
+        }
+
+        mData[i4 + 0] = x;
+        mData[i4 + 1] = y;
+        mData[i4 + 2] = z;
+        mData[i4 + 3] = w;
+    }
+}
+
+Matrix* Matrix::newLookAt(float eyeX, float eyeY, float eyeZ, float centerX,
+        float centerY, float centerZ, float upX, float upY, float upZ) {
+    Matrix* m = new Matrix();
+    if (m != NULL) {
+        // See the OpenGL GLUT documentation for gluLookAt for a description
+        // of the algorithm. We implement it in a straightforward way:
+
+        float fx = centerX - eyeX;
+        float fy = centerY - eyeY;
+        float fz = centerZ - eyeZ;
+
+        // Normalize f
+        float rlf = 1.0f / (float) sqrt(fx * fx + fy * fy + fz * fz);
+        fx *= rlf;
+        fy *= rlf;
+        fz *= rlf;
+
+        // compute s = f x up (x means "cross product")
+        float sx = fy * upZ - fz * upY;
+        float sy = fz * upX - fx * upZ;
+        float sz = fx * upY - fy * upX;
+
+        // and normalize s
+        float rls = 1.0f / (float) sqrt(sx * sx + sy * sy + sz * sz);
+        sx *= rls;
+        sy *= rls;
+        sz *= rls;
+
+        // compute u = s x f
+        float ux = sy * fz - sz * fy;
+        float uy = sz * fx - sx * fz;
+        float uz = sx * fy - sy * fx;
+
+        float* d = m->mData;
+        d[0] = sx;
+        d[1] = ux;
+        d[2] = -fx;
+        d[3] = 0.0f;
+
+        d[4] = sy;
+        d[5] = uy;
+        d[6] = -fy;
+        d[7] = 0.0f;
+
+        d[8] = sz;
+        d[9] = uz;
+        d[10] = -fz;
+        d[11] = 0.0f;
+
+        d[12] = 0.0f;
+        d[13] = 0.0f;
+        d[14] = 0.0f;
+        d[15] = 1.0f;
+
+        m->translate(-eyeX, -eyeY, -eyeZ);
+    }
+    return m;
+}
+
+Matrix* Matrix::newFrustum(float left, float right, float bottom, float top,
+        float near, float far) {
+    const float r_width = 1.0f / (right - left);
+    const float r_height = 1.0f / (top - bottom);
+    const float r_depth = 1.0f / (near - far);
+    const float x = 2.0f * (near * r_width);
+    const float y = 2.0f * (near * r_height);
+    const float A = (right + left) * r_width;
+    const float B = (top + bottom) * r_height;
+    const float C = (far + near) * r_depth;
+    const float D = 2.0f * (far * near * r_depth);
+    Matrix* m = new Matrix();
+    if (m != NULL) {
+        float* d = m->mData;
+        d[0] = x;
+        d[5] = y;
+        d[8] = A;
+        d[9] = B;
+        d[10] = C;
+        d[14] = D;
+        d[11] = -1.0f;
+        d[1] = 0.0f;
+        d[2] = 0.0f;
+        d[3] = 0.0f;
+        d[4] = 0.0f;
+        d[6] = 0.0f;
+        d[7] = 0.0f;
+        d[12] = 0.0f;
+        d[13] = 0.0f;
+        d[15] = 0.0f;
+    }
+    return m;
+}
+
+Matrix* Matrix::newTranslate(float x, float y, float z) {
+    Matrix* m = new Matrix();
+    if (m != NULL) {
+        float* d = m->mData;
+        d[12] = x;
+        d[13] = y;
+        d[14] = z;
+    }
+    return m;
+}
+Matrix* Matrix::newScale(float x, float y, float z) {
+    Matrix* m = new Matrix();
+    if (m != NULL) {
+        float* d = m->mData;
+        d[0] = x;
+        d[5] = y;
+        d[10] = z;
+    }
+    return m;
+}
+Matrix* Matrix::newRotate(float degrees, float x, float y, float z) {
+    Matrix* m = new Matrix();
+    if (m != NULL) {
+        float* d = m->mData;
+        d[3] = 0;
+        d[7] = 0;
+        d[11] = 0;
+        d[12] = 0;
+        d[13] = 0;
+        d[14] = 0;
+        d[15] = 1;
+        float radians = degrees * (M_PI / 180.0f);
+        float s = (float) sinf(radians);
+        float c = (float) cosf(radians);
+        if (1.0f == x && 0.0f == y && 0.0f == z) {
+            d[5] = c;
+            d[10] = c;
+            d[6] = s;
+            d[9] = -s;
+            d[1] = 0;
+            d[2] = 0;
+            d[4] = 0;
+            d[8] = 0;
+            d[0] = 1;
+        } else if (0.0f == x && 1.0f == y && 0.0f == z) {
+            d[0] = c;
+            d[10] = c;
+            d[8] = s;
+            d[2] = -s;
+            d[1] = 0;
+            d[4] = 0;
+            d[6] = 0;
+            d[9] = 0;
+            d[5] = 1;
+        } else if (0.0f == x && 0.0f == y && 1.0f == z) {
+            d[0] = c;
+            d[5] = c;
+            d[1] = s;
+            d[4] = -s;
+            d[2] = 0;
+            d[6] = 0;
+            d[8] = 0;
+            d[9] = 0;
+            d[10] = 1;
+        } else {
+            float len = sqrt((x * x) + (y * y) + (z * z));
+            if (1.0f != len) {
+                float recipLen = 1.0f / len;
+                x *= recipLen;
+                y *= recipLen;
+                z *= recipLen;
+            }
+            float nc = 1.0f - c;
+            float xy = x * y;
+            float yz = y * z;
+            float zx = z * x;
+            float xs = x * s;
+            float ys = y * s;
+            float zs = z * s;
+            d[0] = x * x * nc + c;
+            d[4] = xy * nc - zs;
+            d[8] = zx * nc + ys;
+            d[1] = xy * nc + zs;
+            d[5] = y * y * nc + c;
+            d[9] = yz * nc - xs;
+            d[2] = zx * nc - ys;
+            d[6] = yz * nc + xs;
+            d[10] = z * z * nc + c;
+        }
+    }
+    return m;
+}
+
+void Matrix::multiplyVector(float* result, const Matrix& lhs,
+        const float* rhs) {
+    const float* d = lhs.mData;
+    const float x = rhs[0];
+    const float y = rhs[1];
+    const float z = rhs[2];
+    const float w = rhs[3];
+    for (int i = 0; i < 4; i++) {
+        const int j = i * 4;
+        result[i] = d[j + 0] * x + d[j + 1] * y + d[j + 2] * z + d[j + 3] * w;
+    }
+}
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/Matrix.h b/suite/pts/deviceTests/opengl/jni/graphics/Matrix.h
new file mode 100644
index 0000000..3484266
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/graphics/Matrix.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+#ifndef MATRIX_H
+#define MATRIX_H
+
+class Matrix {
+public:
+    static const int MATRIX_SIZE = 16;
+    float mData[MATRIX_SIZE];
+    Matrix();
+    Matrix(const Matrix& src);
+    // Returns true if the two matrices have the same values.
+    bool equals(const Matrix& src);
+    // Loads this matrix with the identity matrix.
+    void identity();
+    // Loads this matrix with the data from src.
+    void loadWith(const Matrix& src);
+    // Translates this matrix by the given amounts.
+    void translate(float x, float y, float z);
+    // Scales this matrix by the given amounts.
+    void scale(float x, float y, float z);
+    // Rotates this matrix the given angle.
+    void rotate(float degrees, float x, float y, float z);
+    // Sets this matrix to be the result of multiplying the given matrices.
+    void multiply(const Matrix& l, const Matrix& r);
+
+    void print(const char* label);
+
+    // Returns a new matrix representing the camera.
+    static Matrix* newLookAt(float eyeX, float eyeY, float eyeZ, float centerX,
+            float centerY, float centerZ, float upX, float upY, float upZ);
+    // Returns a new matrix representing the perspective matrix.
+    static Matrix* newFrustum(float left, float right, float bottom, float top,
+            float near, float far);
+    // Returns a new matrix representing the translation.
+    static Matrix* newTranslate(float x, float y, float z);
+    // Returns a new matrix representing the scaling.
+    static Matrix* newScale(float x, float y, float z);
+    // Returns a new matrix representing the rotation.
+    static Matrix* newRotate(float degrees, float x, float y, float z);
+
+    // Sets the given matrix to be the result of multiplying the given matrix by the given vector.
+    static void multiplyVector(float* result, const Matrix& lhs,
+            const float* rhs);
+};
+
+#endif
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/Mesh.cpp b/suite/pts/deviceTests/opengl/jni/graphics/Mesh.cpp
new file mode 100644
index 0000000..b92551c
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/graphics/Mesh.cpp
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+#include "Mesh.h"
+
+Mesh::Mesh(const float* vertices, const float* normals, const float* texCoords,
+           const int numVertices, const GLuint textureId)
+    : mVertices(vertices),
+      mNormals(normals),
+      mTexCoords(texCoords),
+      mNumVertices(numVertices),
+      mTextureId(textureId) {
+
+}
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/Mesh.h b/suite/pts/deviceTests/opengl/jni/graphics/Mesh.h
new file mode 100644
index 0000000..5f5ac7d
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/graphics/Mesh.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+#ifndef MESH_H
+#define MESH_H
+
+#include <GLES2/gl2.h>
+
+// Meshes act as holders for geometry data etc. This is accessed by the MeshNodes to render them.
+// This allows a mesh to appear multiple times in a SceneGraph without duplication.
+class Mesh {
+public:
+    Mesh(const float* vertices, const float* normals, const float* texCoords,
+            const int numVertices, const GLuint textureId);
+    virtual ~Mesh() {};
+    const float* mVertices;
+    const float* mNormals;
+    const float* mTexCoords;
+    const int mNumVertices;
+    const GLuint mTextureId;
+};
+
+#endif
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/MeshNode.cpp b/suite/pts/deviceTests/opengl/jni/graphics/MeshNode.cpp
new file mode 100644
index 0000000..8ea5f76
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/graphics/MeshNode.cpp
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+#include "MeshNode.h"
+
+MeshNode::MeshNode(const Mesh* mesh) :
+        mMesh(mesh) {
+
+}
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/MeshNode.h b/suite/pts/deviceTests/opengl/jni/graphics/MeshNode.h
new file mode 100644
index 0000000..a02b6bd
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/graphics/MeshNode.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+#ifndef MESHNODE_H
+#define MESHNODE_H
+
+#include "Matrix.h"
+#include "Mesh.h"
+#include "Program.h"
+#include "SceneGraphNode.h"
+
+class MeshNode: public SceneGraphNode {
+public:
+    MeshNode(const Mesh* mesh);
+    virtual ~MeshNode() {};
+protected:
+    virtual void before(Program& program, Matrix& model, Matrix& view, Matrix& projection) = 0;
+    virtual void after(Program& program, Matrix& model, Matrix& view, Matrix& projection) = 0;
+    const Mesh* mMesh;
+};
+
+#endif
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/Program.cpp b/suite/pts/deviceTests/opengl/jni/graphics/Program.cpp
new file mode 100644
index 0000000..b951a4b
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/graphics/Program.cpp
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+#include "ProgramNode.h"
+
+Program::Program(GLuint programId) :
+        mProgramId(programId) {
+}
+
+void Program::before(Matrix& model, Matrix& view, Matrix& projection) {
+    glUseProgram(mProgramId);
+}
+
+void Program::after(Matrix& model, Matrix& view, Matrix& projection) {
+}
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/Program.h b/suite/pts/deviceTests/opengl/jni/graphics/Program.h
new file mode 100644
index 0000000..f351b58
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/graphics/Program.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+#ifndef PROGRAM_H
+#define PROGRAM_H
+
+#include "Matrix.h"
+
+#include <GLES2/gl2.h>
+
+class Program {
+public:
+    Program(GLuint programId);
+    virtual ~Program() {};
+    virtual void before(Matrix& model, Matrix& view, Matrix& projection);
+    virtual void after(Matrix& model, Matrix& view, Matrix& projection);
+private:
+    GLuint mProgramId;
+};
+
+#endif
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/ProgramNode.cpp b/suite/pts/deviceTests/opengl/jni/graphics/ProgramNode.cpp
new file mode 100644
index 0000000..bc4eb90
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/graphics/ProgramNode.cpp
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+#include "ProgramNode.h"
+
+void ProgramNode::before(Program& program, Matrix& model, Matrix& view,
+        Matrix& projection) {
+    program.before(model, view, projection);
+}
+
+void ProgramNode::after(Program& program, Matrix& model, Matrix& view,
+        Matrix& projection) {
+    program.after(model, view, projection);
+}
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/ProgramNode.h b/suite/pts/deviceTests/opengl/jni/graphics/ProgramNode.h
new file mode 100644
index 0000000..54ac92a
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/graphics/ProgramNode.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+#ifndef PROGRAMNODE_H
+#define PROGRAMNODE_H
+
+#include "Matrix.h"
+#include "Program.h"
+#include "SceneGraphNode.h"
+
+class ProgramNode: public SceneGraphNode {
+public:
+    ProgramNode() {};
+    virtual ~ProgramNode() {};
+protected:
+    virtual void before(Program& program, Matrix& model, Matrix& view,
+            Matrix& projection);
+    virtual void after(Program& program, Matrix& model, Matrix& view,
+            Matrix& projection);
+};
+
+#endif
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/SceneGraphNode.cpp b/suite/pts/deviceTests/opengl/jni/graphics/SceneGraphNode.cpp
new file mode 100644
index 0000000..473c846
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/graphics/SceneGraphNode.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+#include "SceneGraphNode.h"
+
+SceneGraphNode::~SceneGraphNode() {
+    for (size_t i = 0; i < mChildren.size(); i++) {
+        delete mChildren[i];
+    }
+}
+
+void SceneGraphNode::addChild(SceneGraphNode* child) {
+    mChildren.add(child);
+}
+
+void SceneGraphNode::draw(Program& program, Matrix& model, Matrix& view,
+        Matrix& projection) {
+    before(program, model, view, projection);
+    for (size_t i = 0; i < mChildren.size(); i++) {
+        mChildren[i]->draw(program, model, view, projection);
+    }
+    after(program, model, view, projection);
+}
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/SceneGraphNode.h b/suite/pts/deviceTests/opengl/jni/graphics/SceneGraphNode.h
new file mode 100644
index 0000000..55a20f1
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/graphics/SceneGraphNode.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+#ifndef SCENEGRAPHNODE_H
+#define SCENEGRAPHNODE_H
+
+#include <utils/Vector.h>
+#include "Matrix.h"
+#include "Program.h"
+
+class SceneGraphNode {
+public:
+    SceneGraphNode() {};
+    virtual ~SceneGraphNode();
+    void addChild(SceneGraphNode* child);
+    void draw(Program& program, Matrix& model, Matrix& view,
+            Matrix& projection);
+protected:
+    virtual void before(Program& program, Matrix& model, Matrix& view,
+            Matrix& projection) = 0;
+    virtual void after(Program& program, Matrix& model, Matrix& view,
+            Matrix& projection) = 0;
+private:
+    android::Vector<SceneGraphNode*> mChildren;
+};
+
+#endif
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/TransformationNode.cpp b/suite/pts/deviceTests/opengl/jni/graphics/TransformationNode.cpp
new file mode 100644
index 0000000..3cfa55b
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/graphics/TransformationNode.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+#include "TransformationNode.h"
+
+TransformationNode::TransformationNode(Matrix* matrix) :
+        mMatrix(matrix) {
+}
+
+TransformationNode::~TransformationNode() {
+    delete mMatrix;
+}
+
+void TransformationNode::before(Program& program, Matrix& model, Matrix& view,
+        Matrix& projection) {
+    // Save the current Matrix.
+    mSavedModelMatrix.loadWith(model);
+    // Apply transformation.
+    model.multiply(*mMatrix, mSavedModelMatrix);
+}
+
+void TransformationNode::after(Program& program, Matrix& model, Matrix& view,
+        Matrix& projection) {
+    // Restore original Matrix.
+    model.loadWith(mSavedModelMatrix);
+}
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/TransformationNode.h b/suite/pts/deviceTests/opengl/jni/graphics/TransformationNode.h
new file mode 100644
index 0000000..04ba5c2
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/graphics/TransformationNode.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+#ifndef TRANSFORMATIONNODE_H
+#define TRANSFORMATIONNODE_H
+
+#include "Matrix.h"
+#include "Program.h"
+#include "SceneGraphNode.h"
+
+class TransformationNode: public SceneGraphNode {
+public:
+    TransformationNode(Matrix* matrix);
+    virtual ~TransformationNode();
+protected:
+    virtual void before(Program& program, Matrix& model, Matrix& view,
+            Matrix& projection);
+    virtual void after(Program& program, Matrix& model, Matrix& view,
+            Matrix& projection);
+private:
+    Matrix mSavedModelMatrix;
+    Matrix* mMatrix;
+};
+
+#endif
diff --git a/suite/pts/deviceTests/opengl/jni/pixeloutput/PixelOutputRenderer.cpp b/suite/pts/deviceTests/opengl/jni/pixeloutput/PixelOutputRenderer.cpp
new file mode 100644
index 0000000..95bf52b
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/pixeloutput/PixelOutputRenderer.cpp
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+#include "PixelOutputRenderer.h"
+#include <GLUtils.h>
+
+#define LOG_TAG "PTS_OPENGL"
+#define LOG_NDEBUG 0
+#include "utils/Log.h"
+
+static const int PO_NUM_VERTICES = 6;
+
+static const float PO_VERTICES[PO_NUM_VERTICES * 3] = {
+        1.0f, 1.0f, -1.0f,
+        -1.0f, 1.0f, -1.0f,
+        -1.0f, -1.0f, -1.0f,
+        -1.0f, -1.0f, -1.0f,
+        1.0f, -1.0f, -1.0f,
+        1.0f, 1.0f, -1.0f };
+static const float PO_TEX_COORDS[PO_NUM_VERTICES * 2] = {
+        1.0f, 1.0f,
+        0.0f, 1.0f,
+        0.0f, 0.0f,
+        0.0f, 0.0f,
+        1.0f, 0.0f,
+        1.0f, 1.0f };
+
+static const char* PO_VERTEX =
+        "attribute vec4 a_Position;"
+        "attribute vec2 a_TexCoord;"
+        "varying vec2 v_TexCoord;"
+        "void main() {"
+        "  v_TexCoord = a_TexCoord;"
+        "  gl_Position = a_Position;"
+        "}";
+
+static const char* PO_FRAGMENT =
+        "precision mediump float;"
+        "uniform sampler2D u_Texture;"
+        "varying vec2 v_TexCoord;"
+        "void main() {"
+        "  gl_FragColor = texture2D(u_Texture, v_TexCoord);"
+        "}";
+
+PixelOutputRenderer::PixelOutputRenderer(ANativeWindow* window, bool offscreen, int workload) :
+        Renderer(window, offscreen, workload) {
+}
+
+bool PixelOutputRenderer::setUp() {
+    if (!Renderer::setUp()) {
+        return false;
+    }
+
+    // Create program.
+    mProgramId = GLUtils::createProgram(&PO_VERTEX, &PO_FRAGMENT);
+    if (mProgramId == 0)
+        return false;
+    // Bind attributes.
+    mTextureUniformHandle = glGetUniformLocation(mProgramId, "u_Texture");
+    mPositionHandle = glGetAttribLocation(mProgramId, "a_Position");
+    mTexCoordHandle = glGetAttribLocation(mProgramId, "a_TexCoord");
+
+    // Setup texture.
+    mTextureId = GLUtils::genRandTex(width, height);
+    if (mTextureId == 0) {
+        return false;
+    }
+    return true;
+}
+
+bool PixelOutputRenderer::tearDown() {
+    if (mTextureId != 0) {
+        glDeleteTextures(1, &mTextureId);
+        mTextureId = 0;
+    }
+    if (!Renderer::tearDown()) {
+        return false;
+    }
+    return true;
+}
+
+bool PixelOutputRenderer::draw() {
+    if (mOffscreen) {
+        glBindFramebuffer(GL_FRAMEBUFFER, mFboId);
+    }
+    glUseProgram(mProgramId);
+    // Set the background clear color to black.
+    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+    glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
+
+    // No culling of back faces
+    glDisable(GL_CULL_FACE);
+
+    // No depth testing
+    glDisable(GL_DEPTH_TEST);
+
+    // Enable blending
+    glEnable(GL_BLEND);
+    glBlendFunc(GL_ONE, GL_ONE);
+
+    glActiveTexture(GL_TEXTURE0);
+    // Bind the texture to this unit.
+    glBindTexture(GL_TEXTURE_2D, mTextureId);
+    // Tell the texture uniform sampler to use this texture in the shader by binding to texture
+    // unit 0.
+    glUniform1i(mTextureUniformHandle, 0);
+
+    glEnableVertexAttribArray(mPositionHandle);
+    glEnableVertexAttribArray(mTexCoordHandle);
+    glVertexAttribPointer(mPositionHandle, 3, GL_FLOAT, false, 0, PO_VERTICES);
+    glVertexAttribPointer(mTexCoordHandle, 2, GL_FLOAT, false, 0, PO_TEX_COORDS);
+
+    for (int i = 0; i < mWorkload; i++) {
+        glDrawArrays(GL_TRIANGLES, 0, PO_NUM_VERTICES);
+    }
+
+    GLuint err = glGetError();
+    if (err != GL_NO_ERROR) {
+        ALOGV("GLError %d", err);
+        return false;
+    }
+
+    if (mOffscreen) {
+        glFinish();
+        return true;
+    } else {
+        return eglSwapBuffers(mEglDisplay, mEglSurface);
+    }
+}
diff --git a/suite/pts/deviceTests/opengl/jni/pixeloutput/PixelOutputRenderer.h b/suite/pts/deviceTests/opengl/jni/pixeloutput/PixelOutputRenderer.h
new file mode 100644
index 0000000..6422517
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/pixeloutput/PixelOutputRenderer.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+#ifndef PIXELOUTPUTRENDERER_H
+#define PIXELOUTPUTRENDERER_H
+
+#include <Renderer.h>
+
+class PixelOutputRenderer: public Renderer {
+public:
+    PixelOutputRenderer(ANativeWindow* window, bool offscreen, int workload);
+    virtual ~PixelOutputRenderer() {};
+    bool setUp();
+    bool tearDown();
+    bool draw();
+private:
+    GLuint mTextureId;
+    GLuint mTextureUniformHandle;
+    GLuint mPositionHandle;
+    GLuint mTexCoordHandle;
+};
+
+#endif
diff --git a/suite/pts/deviceTests/opengl/jni/shaderperf/ShaderPerfRenderer.cpp b/suite/pts/deviceTests/opengl/jni/shaderperf/ShaderPerfRenderer.cpp
new file mode 100644
index 0000000..a3fea22
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/shaderperf/ShaderPerfRenderer.cpp
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+#include "ShaderPerfRenderer.h"
+#include <GLUtils.h>
+
+#define LOG_TAG "PTS_OPENGL"
+#define LOG_NDEBUG 0
+#include "utils/Log.h"
+
+static const int SP_NUM_VERTICES = 6;
+
+static const float SP_VERTICES[SP_NUM_VERTICES * 3] = {
+        1.0f, 1.0f, -1.0f,
+        -1.0f, 1.0f, -1.0f,
+        -1.0f, -1.0f, -1.0f,
+        -1.0f, -1.0f, -1.0f,
+        1.0f, -1.0f, -1.0f,
+        1.0f, 1.0f, -1.0f };
+
+static const char* SP_VERTEX = "attribute vec4 a_Position;"
+                              "varying vec4 v_Position;"
+                              "void main() {"
+                              "  v_Position = a_Position;"
+                              "  gl_Position = a_Position;"
+                              "}";
+
+// TODO At the moment this a very simple shader. Later on this will get more complex.
+static const char* SP_FRAGMENT = "precision mediump float;"
+                                "varying vec4 v_Position;"
+                                "void main() {"
+                                "  gl_FragColor = v_Position;"
+                                "}";
+
+ShaderPerfRenderer::ShaderPerfRenderer(ANativeWindow* window, bool offscreen, int workload) :
+        Renderer(window, offscreen, workload) {
+}
+
+bool ShaderPerfRenderer::setUp() {
+    if (!Renderer::setUp()) {
+        return false;
+    }
+    // Create program.
+    mProgramId = GLUtils::createProgram(&SP_VERTEX, &SP_FRAGMENT);
+    if (mProgramId == 0)
+        return false;
+    // Bind attributes.
+    mPositionHandle = glGetAttribLocation(mProgramId, "a_Position");
+    return true;
+}
+
+bool ShaderPerfRenderer::draw() {
+    if (mOffscreen) {
+        glBindFramebuffer(GL_FRAMEBUFFER, mFboId);
+    }
+    glUseProgram(mProgramId);
+    // Set the background clear color to black.
+    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+    glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
+
+    // No culling of back faces
+    glDisable(GL_CULL_FACE);
+
+    glEnableVertexAttribArray(mPositionHandle);
+    glVertexAttribPointer(mPositionHandle, 3, GL_FLOAT, false, 0, SP_VERTICES);
+
+    glDrawArrays(GL_TRIANGLES, 0, SP_NUM_VERTICES);
+
+    GLuint err = glGetError();
+    if (err != GL_NO_ERROR) {
+        ALOGV("GLError %d", err);
+        return false;
+    }
+
+    if (mOffscreen) {
+        glFinish();
+        return true;
+    } else {
+        return eglSwapBuffers(mEglDisplay, mEglSurface);
+    }
+}
diff --git a/suite/pts/deviceTests/opengl/jni/shaderperf/ShaderPerfRenderer.h b/suite/pts/deviceTests/opengl/jni/shaderperf/ShaderPerfRenderer.h
new file mode 100644
index 0000000..22e0420
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/shaderperf/ShaderPerfRenderer.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+#ifndef SHADERPERFRENDERER_H
+#define SHADERPERFRENDERER_H
+
+#include <Renderer.h>
+
+class ShaderPerfRenderer: public Renderer {
+public:
+    ShaderPerfRenderer(ANativeWindow* window, bool offscreen, int workload);
+    virtual ~ShaderPerfRenderer() {};
+    bool setUp();
+    bool draw();
+private:
+    GLuint mPositionHandle;
+};
+
+#endif
diff --git a/suite/pts/deviceTests/opengl/src/com/android/pts/opengl/primitive/Benchmark.java b/suite/pts/deviceTests/opengl/src/com/android/pts/opengl/primitive/Benchmark.java
new file mode 100644
index 0000000..d61f45d
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/src/com/android/pts/opengl/primitive/Benchmark.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2013 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.pts.opengl.primitive;
+
+/**
+ * Represents the different primitive benchmarks.
+ */
+public enum Benchmark {
+    FullPipeline,
+    PixelOutput,
+    ShaderPerf,
+    ContextSwitch;
+}
diff --git a/suite/pts/deviceTests/opengl/src/com/android/pts/opengl/primitive/GLActivity.java b/suite/pts/deviceTests/opengl/src/com/android/pts/opengl/primitive/GLActivity.java
new file mode 100644
index 0000000..df0698a
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/src/com/android/pts/opengl/primitive/GLActivity.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2013 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.pts.opengl.primitive;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.cts.util.WatchDog;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.Surface;
+
+import com.android.pts.util.ResultType;
+import com.android.pts.util.ResultUnit;
+
+import java.util.ArrayList;
+import java.util.concurrent.Semaphore;
+
+public class GLActivity extends Activity {
+
+    public final static String TAG = "GLActivity";
+    /**
+     * Holds the name of the benchmark to run.
+     */
+    public final static String INTENT_EXTRA_BENCHMARK_NAME = "benchmark_name";
+    /**
+     * Holds whether or not the benchmark is to be run offscreen.
+     */
+    public final static String INTENT_EXTRA_OFFSCREEN = "offscreen";
+    /**
+     * The number of frames to render for each workload.
+     */
+    public final static String INTENT_EXTRA_NUM_FRAMES = "num_frames";
+    /**
+     * The number of iterations to run, the workload increases with each iteration.
+     */
+    public final static String INTENT_EXTRA_NUM_ITERATIONS = "num_iterations";
+    /**
+     * The number of milliseconds to wait before timing out.
+     */
+    public final static String INTENT_EXTRA_TIMEOUT = "timeout";
+
+    private Worker runner;
+    private volatile Exception mException;
+    private volatile Surface mSurface;
+    private Semaphore mSemaphore = new Semaphore(0);
+
+    private Benchmark mBenchmark;
+    private boolean mOffscreen;
+    private int mNumFrames;
+    private int mNumIterations;
+    private int mTimeout;
+    public double[] mFpsValues;
+
+    @Override
+    public void onCreate(Bundle data) {
+        super.onCreate(data);
+        System.loadLibrary("ptsopengl_jni");
+        Intent intent = getIntent();
+        mBenchmark = Benchmark.valueOf(intent.getStringExtra(INTENT_EXTRA_BENCHMARK_NAME));
+        mOffscreen = intent.getBooleanExtra(INTENT_EXTRA_OFFSCREEN, false);
+        mNumFrames = intent.getIntExtra(INTENT_EXTRA_NUM_FRAMES, 0);
+        mNumIterations = intent.getIntExtra(INTENT_EXTRA_NUM_ITERATIONS, 0);
+        mTimeout = intent.getIntExtra(INTENT_EXTRA_TIMEOUT, 0);
+        mFpsValues = new double[mNumIterations];
+
+        Log.i(TAG, "Benchmark: " + mBenchmark);
+        Log.i(TAG, "Offscreen: " + mOffscreen);
+        Log.i(TAG, "Num Frames: " + mNumFrames);
+        Log.i(TAG, "Num Iterations: " + mNumIterations);
+        Log.i(TAG, "Time Out: " + mTimeout);
+
+        SurfaceView surfaceView = new SurfaceView(this);
+        surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
+            @Override
+            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+                mSurface = holder.getSurface();
+            }
+
+            @Override
+            public void surfaceCreated(SurfaceHolder holder) {}
+
+            @Override
+            public void surfaceDestroyed(SurfaceHolder holder) {}
+        });
+        setContentView(surfaceView);
+    }
+
+    public void spawnAndWaitForCompletion() throws Exception {
+        // Spawns a worker to run the benchmark.
+        runner = new Worker();
+        runner.start();
+        // Wait for semiphore.
+        mSemaphore.acquire();
+        if (mException != null) {
+            throw mException;
+        }
+    }
+
+    private void complete() {
+        // Release semiphore.
+        mSemaphore.release();
+    }
+
+    private synchronized void setException(Exception e) {
+        if (mException == null) {
+            mException = e;
+        }
+    }
+
+    private static native void setupFullPipelineBenchmark(
+            Surface surface, boolean offscreen, int workload);
+
+    private static native void setupPixelOutputBenchmark(
+            Surface surface, boolean offscreen, int workload);
+
+    private static native void setupShaderPerfBenchmark(
+            Surface surface, boolean offscreen, int workload);
+
+    private static native void setupContextSwitchBenchmark(
+            Surface surface, boolean offscreen, int workload);
+
+    private static native boolean startBenchmark(int numFrames, double[] frameTimes);
+
+    /**
+     * This thread runs the benchmarks, freeing the UI thread.
+     */
+    private class Worker extends Thread implements WatchDog.TimeoutCallback {
+
+        private WatchDog watchDog;
+        private volatile boolean success = true;
+
+        @Override
+        public void run() {
+            // Creates a watchdog to ensure a iteration doesn't exceed the timeout.
+            watchDog = new WatchDog(mTimeout, this);
+            // Used to record the start and end time of the iteration.
+            double[] times = new double[2];
+            for (int i = 0; i < mNumIterations && success; i++) {
+                // The workload to use for this iteration.
+                int workload = i + 1;
+                // Setup the benchmark.
+                switch (mBenchmark) {
+                    case FullPipeline:
+                        setupFullPipelineBenchmark(mSurface, mOffscreen, workload);
+                        break;
+                    case PixelOutput:
+                        setupPixelOutputBenchmark(mSurface, mOffscreen, workload);
+                        break;
+                    case ShaderPerf:
+                        setupShaderPerfBenchmark(mSurface, mOffscreen, workload);
+                        break;
+                    case ContextSwitch:
+                        setupContextSwitchBenchmark(mSurface, mOffscreen, workload);
+                        break;
+                }
+                watchDog.start();
+                // Start benchmark.
+                success = startBenchmark(mNumFrames, times);
+                watchDog.stop();
+
+                if (!success) {
+                    setException(new Exception("Benchmark failed to run"));
+                } else {
+                    // Calculate FPS.
+                    mFpsValues[i] = mNumFrames * 1000.0f / (times[1] - times[0]);
+                }
+            }
+            complete();
+        }
+
+        public void onTimeout() {
+            setException(new Exception("Benchmark timed out"));
+            complete();
+        }
+
+    }
+}
diff --git a/suite/pts/deviceTests/opengl/src/com/android/pts/opengl/primitive/GLBenchmark.java b/suite/pts/deviceTests/opengl/src/com/android/pts/opengl/primitive/GLBenchmark.java
new file mode 100644
index 0000000..b1c9112
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/src/com/android/pts/opengl/primitive/GLBenchmark.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2013 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.pts.opengl.primitive;
+
+import com.android.pts.util.PtsActivityInstrumentationTestCase2;
+import com.android.pts.util.ResultType;
+import com.android.pts.util.ResultUnit;
+
+import android.content.Intent;
+import android.cts.util.TimeoutReq;
+import android.opengl.Matrix;
+import android.util.Log;
+
+import java.util.Arrays;
+
+/**
+ * Runs the Primitive OpenGL ES 2.0 Benchmarks.
+ */
+public class GLBenchmark extends PtsActivityInstrumentationTestCase2<GLActivity> {
+
+    public GLBenchmark() {
+        super(GLActivity.class);
+    }
+
+    /**
+     * Runs the full OpenGL ES 2.0 pipeline test offscreen.
+     */
+    @TimeoutReq(minutes = 20)
+    public void testFullPipelineOffscreen() throws Exception {
+        runBenchmark(Benchmark.FullPipeline, true, 500, 8, 1000000);
+    }
+
+    /**
+     * Runs the full OpenGL ES 2.0 pipeline test onscreen.
+     */
+    @TimeoutReq(minutes = 20)
+    public void testFullPipelineOnscreen() throws Exception {
+        runBenchmark(Benchmark.FullPipeline, false, 500, 8, 1000000);
+    }
+
+    /**
+     * Runs the pixel output test offscreen.
+     */
+    public void testPixelOutputOffscreen() throws Exception {
+        runBenchmark(Benchmark.PixelOutput, true, 500, 8, 1000000);
+    }
+
+    /**
+     * Runs the pixel output test onscreen.
+     */
+    public void testPixelOutputOnscreen() throws Exception {
+        runBenchmark(Benchmark.PixelOutput, false, 500, 8, 1000000);
+    }
+
+    /**
+     * Runs the shader performance test offscreen.
+     */
+    public void testShaderPerfOffscreen() throws Exception {
+        // TODO(stuartscott): Not yet implemented
+        // runBenchmark(Benchmark.ShaderPerf, true, 500, 8, 1000000);
+    }
+
+    /**
+     * Runs the shader performance test onscreen.
+     */
+    public void testShaderPerfOnscreen() throws Exception {
+        // TODO(stuartscott): Not yet implemented
+        // runBenchmark(Benchmark.ShaderPerf, false, 500, 8, 1000000);
+    }
+
+    /**
+     * Runs the context switch overhead test offscreen.
+     */
+    public void testContextSwitchOffscreen() throws Exception {
+        runBenchmark(Benchmark.ContextSwitch, true, 500, 8, 1000000);
+    }
+
+    /**
+     * Runs the context switch overhead test onscreen.
+     */
+    public void testContextSwitchOnscreen() throws Exception {
+        runBenchmark(Benchmark.ContextSwitch, false, 500, 8, 1000000);
+    }
+
+    /**
+     * Runs the specified test.
+     *
+     * @param benchmark An enum representing the benchmark to run.
+     * @param offscreen Whether to render to an offscreen framebuffer rather than the screen.
+     * @param numFrames The number of frames to render.
+     * @param numIterations The number of iterations to run, each iteration has a bigger workload.
+     * @param timeout The milliseconds to wait for an iteration of the benchmark before timing out.
+     * @throws Exception If the benchmark could not be run.
+     */
+    private void runBenchmark(
+            Benchmark benchmark, boolean offscreen, int numFrames, int numIterations, int timeout)
+            throws Exception {
+        String benchmarkName = benchmark.toString();
+        Intent intent = new Intent();
+        intent.putExtra(GLActivity.INTENT_EXTRA_BENCHMARK_NAME, benchmarkName);
+        intent.putExtra(GLActivity.INTENT_EXTRA_OFFSCREEN, offscreen);
+        intent.putExtra(GLActivity.INTENT_EXTRA_NUM_FRAMES, numFrames);
+        intent.putExtra(GLActivity.INTENT_EXTRA_NUM_ITERATIONS, numIterations);
+        intent.putExtra(GLActivity.INTENT_EXTRA_TIMEOUT, timeout);
+
+        GLActivity activity = null;
+        setActivityIntent(intent);
+        try {
+            activity = getActivity();
+            activity.spawnAndWaitForCompletion();
+        } finally {
+            if (activity != null) {
+                double[] fpsValues = activity.mFpsValues;
+                double score = 0;
+                for (double d : fpsValues) {
+                    score += d;
+                }
+                getReportLog().printArray(
+                        "Fps Values", fpsValues, ResultType.HIGHER_BETTER, ResultUnit.FPS);
+                getReportLog()
+                        .printSummary("Score", score, ResultType.HIGHER_BETTER, ResultUnit.SCORE);
+                activity.finish();
+            }
+        }
+    }
+}
diff --git a/suite/pts/deviceTests/opengl/test/Android.mk b/suite/pts/deviceTests/opengl/test/Android.mk
new file mode 100644
index 0000000..1939b2d
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/test/Android.mk
@@ -0,0 +1,34 @@
+#
+# Copyright (C) 2013 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.
+
+# build only for linux
+ifeq ($(HOST_OS),linux)
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+LOCAL_SRC_FILES := $(patsubst ./%,%, $(shell cd $(LOCAL_PATH); \
+  find . -name "*.cpp" -and -not -name ".*"))
+LOCAL_SRC_FILES += ../jni/graphics/Matrix.cpp
+
+#$(info $(LOCAL_SRC_FILES))
+LOCAL_C_INCLUDES += external/gtest/include $(LOCAL_PATH)/../jni/graphics/
+LOCAL_STATIC_LIBRARIES := libutils libcutils libgtest_host libgtest_main_host
+LOCAL_LDFLAGS:= -g -lrt -ldl -lstdc++ -lm -fno-exceptions
+LOCAL_MODULE:= pts_device_opengl_test
+include $(BUILD_HOST_EXECUTABLE)
+
+endif # linux
\ No newline at end of file
diff --git a/suite/pts/deviceTests/opengl/test/MatrixTest.cpp b/suite/pts/deviceTests/opengl/test/MatrixTest.cpp
new file mode 100644
index 0000000..d78dfc4
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/test/MatrixTest.cpp
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+#include <gtest/gtest.h>
+#include <math.h>
+
+#include "Matrix.h"
+
+class MatrixTest: public testing::Test {
+public:
+
+};
+
+void checkValues(const float* arr1, const float* arr2, const int size) {
+    for (int i = 0; i < size; i++) {
+        ASSERT_FLOAT_EQ(arr1[i], arr2[i]);
+    }
+}
+
+TEST(MatrixTest, matrixEqualityTest) {
+    // Create two identity matrixes.
+    Matrix m1;
+    Matrix m2;
+    // Change some random values.
+    m1.mData[4] = 9;
+    m2.mData[4] = 9;
+    // Check they are the same.
+    ASSERT_TRUE(m1.equals(m2));
+    Matrix* clone = new Matrix(m1);
+    ASSERT_TRUE(clone != NULL);
+    ASSERT_TRUE(m1.equals(*clone));
+    delete clone;
+}
+
+TEST(MatrixTest, matrixIdentityTest) {
+    // Create an identity matrix.
+    Matrix m;
+    float expected[] = {
+        1.0f, 0.0f, 0.0f, 0.0f,
+        0.0f, 1.0f, 0.0f, 0.0f,
+        0.0f, 0.0f, 1.0f, 0.0f,
+        0.0f, 0.0f, 0.0f, 1.0f};
+    // Check values
+    checkValues(m.mData, expected, Matrix::MATRIX_SIZE);
+}
+
+TEST(MatrixTest, matrixLoadWithTest) {
+    // Create a matrix.
+    Matrix m1;
+    float* d1 = m1.mData;
+    float data[Matrix::MATRIX_SIZE];
+
+    // Fill with rubbish
+    for (int i = 0; i < Matrix::MATRIX_SIZE; i++) {
+        d1[i] = i;
+        data[i] = i;
+    }
+
+    // Create another matrix
+    Matrix m2;
+
+    // Load second matrix with first
+    m2.loadWith(m1);
+
+    // Check values
+    checkValues(m2.mData, data, Matrix::MATRIX_SIZE);
+}
+
+TEST(MatrixTest, matrixTranslateTest) {
+    Matrix m1;
+    m1.translate(10, 5, 6);
+    Matrix* m2 = Matrix::newTranslate(10, 5, 6);
+    ASSERT_TRUE(m2 != NULL);
+    ASSERT_TRUE(m1.equals(*m2));
+    delete m2;
+}
+
+TEST(MatrixTest, matrixScaleTest) {
+    Matrix m1;
+    m1.scale(10, 5, 6);
+    Matrix* m2 = Matrix::newScale(10, 5, 6);
+    ASSERT_TRUE(m2 != NULL);
+    ASSERT_TRUE(m1.equals(*m2));
+    delete m2;
+}
+
+TEST(MatrixTest, matrixRotateTest) {
+    Matrix m1;
+    m1.rotate(180, 1, 0, 1);
+    Matrix* m2 = Matrix::newRotate(180, 1, 0, 1);
+    ASSERT_TRUE(m2 != NULL);
+    ASSERT_TRUE(m1.equals(*m2));
+    delete m2;
+}
+
+TEST(MatrixTest, matrixMultiplyTest) {
+    // Create three identity matrixes.
+    Matrix m1;
+    Matrix m2;
+    Matrix m3;
+    float* d1 = m1.mData;
+    float* d2 = m2.mData;
+
+    m3.multiply(m1, m2);
+    // Multiplication of identity matrixes should give identity
+    ASSERT_TRUE(m3.equals(m1));
+
+    // Fill with ascending numbers
+    for (int i = 0; i < Matrix::MATRIX_SIZE; i++) {
+        d1[i] = i;
+        d2[i] = i;
+    }
+    m3.multiply(m1, m2);
+
+    // Check against expected
+    float expected[] = {
+        56, 62, 68, 74,
+        152, 174, 196, 218,
+        248, 286, 324, 362,
+        344, 398, 452, 506};
+    checkValues(m3.mData, expected, Matrix::MATRIX_SIZE);
+}
+
+TEST(MatrixTest, matrixNewLookAtTest) {
+    // Position the eye in front of the origin.
+    float eyeX = 0.0f;
+    float eyeY = 0.0f;
+    float eyeZ = 6.0f;
+
+    // We are looking at the origin
+    float centerX = 0.0f;
+    float centerY = 0.0f;
+    float centerZ = 0.0f;
+
+    // Set our up vector. This is where our head would be pointing were we holding the camera.
+    float upX = 0.0f;
+    float upY = 1.0f;
+    float upZ = 0.0f;
+
+    // Set the view matrix. This matrix can be said to represent the camera position.
+    Matrix* m = Matrix::newLookAt(eyeX, eyeY, eyeZ, centerX, centerY, centerZ,
+            upX, upY, upZ);
+    ASSERT_TRUE(m != NULL);
+    float expected[] = {
+        1.0f, 0.0f, 0.0f, 0.0f,
+        0.0f, 1.0f, 0.0f, 0.0f,
+        0.0f, 0.0f, 1.0f, 0.0f,
+        0.0f, 0.0f, -6.0f, 1.0f};
+    // Check values
+    checkValues(m->mData, expected, Matrix::MATRIX_SIZE);
+    delete m;
+}
+
+TEST(MatrixTest, matrixNewFrustumTest) {
+    float ratio = (float) 800 / 600;
+    float left = -ratio;
+    float right = ratio;
+    float bottom = -1.0f;
+    float top = 1.0f;
+    float near = 1.0f;
+    float far = 8.0f;
+
+    Matrix* m = Matrix::newFrustum(left, right, bottom, top, near, far);
+    ASSERT_TRUE(m != NULL);
+    float expected[] = {
+        0.75f, 0.0f, 0.0f, 0.0f,
+        0.0f, 1.0f, 0.0f, 0.0f,
+        0.0f, 0.0f, 9.0f / -7.0f, -1.0f,
+        0.0f, 0.0f, 16.0f / -7.0f, 0.0f};
+    // Check values
+    checkValues(m->mData, expected, Matrix::MATRIX_SIZE);
+    delete m;
+}
+
+TEST(MatrixTest, matrixNewTranslateTest) {
+    Matrix* m = Matrix::newTranslate(5, 6, 8);
+    ASSERT_TRUE(m != NULL);
+    float expected[] = {
+        1.0f, 0.0f, 0.0f, 0.0f,
+        0.0f, 1.0f, 0.0f, 0.0f,
+        0.0f, 0.0f, 1.0f, 0.0f,
+        5.0f, 6.0f, 8.0f, 1.0f};
+    // Check values
+    checkValues(m->mData, expected, Matrix::MATRIX_SIZE);
+    delete m;
+}
+
+TEST(MatrixTest, matrixNewScaleTest) {
+    Matrix* m = Matrix::newScale(3, 7, 2);
+    ASSERT_TRUE(m != NULL);
+    float expected[] = {
+        3.0f, 0.0f, 0.0f, 0.0f,
+        0.0f, 7.0f, 0.0f, 0.0f,
+        0.0f, 0.0f, 2.0f, 0.0f,
+        0.0f, 0.0f, 0.0f, 1.0f};
+    // Check values
+    checkValues(m->mData, expected, Matrix::MATRIX_SIZE);
+    delete m;
+}
+
+TEST(MatrixTest, matrixNewRotateTest) {
+    Matrix* m = Matrix::newRotate(45.0f, 0.0f, 1.0f, 0.0f);
+    ASSERT_TRUE(m != NULL);
+    float radians = 45.0f * (M_PI / 180.0f);
+    float sin = sinf(radians);
+    float cos = cosf(radians);
+    float expected[] = {
+        cos, 0.0f, -sin, 0.0f,
+        0.0f, 1.0f, 0.0f, 0.0f,
+        sin, 0.0f, cos, 0.0f,
+        0.0f, 0.0f, 0.0f, 1.0f};
+    // Check values
+    checkValues(m->mData, expected, Matrix::MATRIX_SIZE);
+    delete m;
+}
+
+TEST(MatrixTest, matrixMultiplyVectorTest) {
+    float in[] = {2, 4, 6, 8};
+    float out[4];
+    Matrix m;
+    float* d = m.mData;
+    // Fill with rubbish
+    for (int i = 0; i < Matrix::MATRIX_SIZE; i++) {
+        d[i] = i;
+    }
+    float expected[] = {40, 120, 200, 280};
+    Matrix::multiplyVector(out, m, in);
+    checkValues(out, expected, 4);
+}
diff --git a/suite/pts/hostTests/browser/browserlauncher/Android.mk b/suite/pts/deviceTests/simplecpu/Android.mk
similarity index 83%
copy from suite/pts/hostTests/browser/browserlauncher/Android.mk
copy to suite/pts/deviceTests/simplecpu/Android.mk
index 0592017..69db471 100644
--- a/suite/pts/hostTests/browser/browserlauncher/Android.mk
+++ b/suite/pts/deviceTests/simplecpu/Android.mk
@@ -20,14 +20,16 @@
 
 LOCAL_JAVA_LIBRARIES := android.test.runner
 
-LOCAL_STATIC_JAVA_LIBRARIES := ptsutil ctsutil ctstestrunner ctstestserver
+LOCAL_STATIC_JAVA_LIBRARIES := ptsutil ctsutil ctstestrunner
+
+LOCAL_JNI_SHARED_LIBRARIES := libptscpu_jni
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_PACKAGE_NAME := PtsDeviceBrowserLauncher
+LOCAL_PACKAGE_NAME := PtsDeviceSimpleCpu
 
 LOCAL_SDK_VERSION := 16
 
-include $(BUILD_PTS_PACKAGE)
+include $(BUILD_CTS_PACKAGE)
 
 include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/suite/pts/deviceTests/simplecpu/AndroidManifest.xml b/suite/pts/deviceTests/simplecpu/AndroidManifest.xml
new file mode 100644
index 0000000..930bcbd
--- /dev/null
+++ b/suite/pts/deviceTests/simplecpu/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.pts.simplecpu">
+
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+    <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+            android:targetPackage="com.android.pts.simplecpu"
+            android:label="Vesy simple CPU benchmarking" />
+</manifest>
diff --git a/suite/pts/hostTests/ptshostutil/Android.mk b/suite/pts/deviceTests/simplecpu/jni/Android.mk
similarity index 76%
rename from suite/pts/hostTests/ptshostutil/Android.mk
rename to suite/pts/deviceTests/simplecpu/jni/Android.mk
index 14e786a..9c34e0e 100644
--- a/suite/pts/hostTests/ptshostutil/Android.mk
+++ b/suite/pts/deviceTests/simplecpu/jni/Android.mk
@@ -11,19 +11,21 @@
 # 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.
-
+#
 LOCAL_PATH := $(call my-dir)
 
 include $(CLEAR_VARS)
 
-LOCAL_SRC_FILES := \
-    $(call all-java-files-under, src)
-
-LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed-prebuilt ddmlib-prebuilt junit
+LOCAL_MODULE    := libptscpu_jni
 
 LOCAL_MODULE_TAGS := optional
 
-LOCAL_MODULE := ptshostutil
+LOCAL_SRC_FILES := CpuNativeJni.cpp
 
-include $(BUILD_HOST_JAVA_LIBRARY)
+LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
 
+LOCAL_SHARED_LIBRARIES := libnativehelper
+
+LOCAL_SDK_VERSION := 14
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/suite/pts/deviceTests/simplecpu/jni/CpuNativeJni.cpp b/suite/pts/deviceTests/simplecpu/jni/CpuNativeJni.cpp
new file mode 100644
index 0000000..851f35e
--- /dev/null
+++ b/suite/pts/deviceTests/simplecpu/jni/CpuNativeJni.cpp
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#include <jni.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+
+/* Code from now to qsort_local all copied from bionic source.
+ * The code is duplicated here to remove dependency on optimized bionic
+ */
+static __inline char    *med3(char *, char *, char *, int (*)(const void *, const void *));
+static __inline void     swapfunc(char *, char *, int, int);
+
+#define min(a, b)   (a) < (b) ? a : b
+
+/*
+ * Qsort routine from Bentley & McIlroy's "Engineering a Sort Function".
+ */
+#define swapcode(TYPE, parmi, parmj, n) {       \
+    long i = (n) / sizeof (TYPE);           \
+    TYPE *pi = (TYPE *) (parmi);            \
+    TYPE *pj = (TYPE *) (parmj);            \
+    do {                        \
+        TYPE    t = *pi;            \
+        *pi++ = *pj;                \
+        *pj++ = t;              \
+        } while (--i > 0);              \
+}
+
+#define SWAPINIT(a, es) swaptype = ((char *)a - (char *)0) % sizeof(long) || \
+    es % sizeof(long) ? 2 : es == sizeof(long)? 0 : 1;
+
+static __inline void
+swapfunc(char *a, char *b, int n, int swaptype)
+{
+    if (swaptype <= 1)
+        swapcode(long, a, b, n)
+    else
+        swapcode(char, a, b, n)
+}
+
+#define swap(a, b)                  \
+    if (swaptype == 0) {                \
+        long t = *(long *)(a);          \
+        *(long *)(a) = *(long *)(b);        \
+        *(long *)(b) = t;           \
+    } else                      \
+        swapfunc(a, b, es, swaptype)
+
+#define vecswap(a, b, n)    if ((n) > 0) swapfunc(a, b, n, swaptype)
+
+static __inline char *
+med3(char *a, char *b, char *c, int (*cmp)(const void *, const void *))
+{
+    return cmp(a, b) < 0 ?
+           (cmp(b, c) < 0 ? b : (cmp(a, c) < 0 ? c : a ))
+              :(cmp(b, c) > 0 ? b : (cmp(a, c) < 0 ? a : c ));
+}
+
+void
+qsort_local(void *aa, size_t n, size_t es, int (*cmp)(const void *, const void *))
+{
+    char *pa, *pb, *pc, *pd, *pl, *pm, *pn;
+    int d, r, swaptype, swap_cnt;
+    char *a = (char*)aa;
+
+loop:   SWAPINIT(a, es);
+    swap_cnt = 0;
+    if (n < 7) {
+        for (pm = (char *)a + es; pm < (char *) a + n * es; pm += es)
+            for (pl = pm; pl > (char *) a && cmp(pl - es, pl) > 0;
+                 pl -= es)
+                swap(pl, pl - es);
+        return;
+    }
+    pm = (char *)a + (n / 2) * es;
+    if (n > 7) {
+        pl = (char *)a;
+        pn = (char *)a + (n - 1) * es;
+        if (n > 40) {
+            d = (n / 8) * es;
+            pl = med3(pl, pl + d, pl + 2 * d, cmp);
+            pm = med3(pm - d, pm, pm + d, cmp);
+            pn = med3(pn - 2 * d, pn - d, pn, cmp);
+        }
+        pm = med3(pl, pm, pn, cmp);
+    }
+    swap(a, pm);
+    pa = pb = (char *)a + es;
+
+    pc = pd = (char *)a + (n - 1) * es;
+    for (;;) {
+        while (pb <= pc && (r = cmp(pb, a)) <= 0) {
+            if (r == 0) {
+                swap_cnt = 1;
+                swap(pa, pb);
+                pa += es;
+            }
+            pb += es;
+        }
+        while (pb <= pc && (r = cmp(pc, a)) >= 0) {
+            if (r == 0) {
+                swap_cnt = 1;
+                swap(pc, pd);
+                pd -= es;
+            }
+            pc -= es;
+        }
+        if (pb > pc)
+            break;
+        swap(pb, pc);
+        swap_cnt = 1;
+        pb += es;
+        pc -= es;
+    }
+    if (swap_cnt == 0) {  /* Switch to insertion sort */
+        for (pm = (char *) a + es; pm < (char *) a + n * es; pm += es)
+            for (pl = pm; pl > (char *) a && cmp(pl - es, pl) > 0;
+                 pl -= es)
+                swap(pl, pl - es);
+        return;
+    }
+
+    pn = (char *)a + n * es;
+    r = min(pa - (char *)a, pb - pa);
+    vecswap(a, pb - r, r);
+    r = min(pd - pc, pn - pd - (int)es);
+    vecswap(pb, pn - r, r);
+    if ((r = pb - pa) > (int)es)
+        qsort_local(a, r / es, es, cmp);
+    if ((r = pd - pc) > (int)es) {
+        /* Iterate rather than recurse to save stack space */
+        a = pn - r;
+        n = r / es;
+        goto loop;
+    }
+    /* qsort(pn - r, r / es, es, cmp); */
+}
+
+/* code duplication ends here */
+
+/**
+ * Util for getting time stamp
+ */
+double currentTimeMillis()
+{
+    struct timeval tv;
+    gettimeofday(&tv, (struct timezone *) NULL);
+    return tv.tv_sec * 1000.0 + tv.tv_usec / 1000.0;
+}
+
+/**
+ * Initialize given array randomly for the given seed
+ */
+template <typename T> void randomInitArray(T* array, int len, unsigned int seed)
+{
+    srand(seed);
+    for (int i = 0; i < len; i++) {
+        array[i] = (T) rand();
+    }
+}
+
+/**
+ * comparison function for int, for qsort
+ */
+int cmpint(const void* p1, const void* p2)
+{
+    return *(int*)p1 - *(int*)p2;
+}
+
+extern "C" JNIEXPORT jdouble JNICALL Java_com_android_pts_simplecpu_CpuNative_runSort(JNIEnv* env,
+        jclass clazz, jint numberElements, jint repetition)
+{
+    int* data = new int[numberElements];
+    if (data == NULL) {
+        env->ThrowNew(env->FindClass("java/lang/OutOfMemoryError"), "No memory");
+        return -1;
+    }
+    double totalTime = 0;
+    for (int i = 0; i < repetition; i++) {
+        randomInitArray<int>(data, numberElements, 0);
+        double start = currentTimeMillis();
+        qsort_local(data, numberElements, sizeof(int), cmpint);
+        double end = currentTimeMillis();
+        totalTime += (end - start);
+    }
+    delete[] data;
+    return totalTime;
+}
+
+
+/**
+ * Do matrix multiplication, C = A x B with all matrices having dimension of n x n
+ * The implementation is not in the most efficient, but it is good enough for benchmarking purpose.
+ * @param n should be multiple of 8
+ */
+void doMatrixMultiplication(float* A, float* B, float* C, int n)
+{
+    // batch size
+    const int M = 8;
+    for (int i = 0; i < n; i++) {
+        for (int j = 0; j < n; j += M) {
+            float sum[M];
+            for (int k = 0; k < M; k++) {
+                sum[k] = 0;
+            }
+            // re-use the whole cache line for accessing B.
+            // otherwise, the whole line will be read and only one value will be used.
+
+            for (int k = 0; k < n; k++) {
+                float a = A[i * n + k];
+                sum[0] += a * B[k * n + j];
+                sum[1] += a * B[k * n + j + 1];
+                sum[2] += a * B[k * n + j + 2];
+                sum[3] += a * B[k * n + j + 3];
+                sum[4] += a * B[k * n + j + 4];
+                sum[5] += a * B[k * n + j + 5];
+                sum[6] += a * B[k * n + j + 6];
+                sum[7] += a * B[k * n + j + 7];
+            }
+            for (int k = 0; k < M; k++) {
+                C[i * n + j + k] = sum[k];
+            }
+        }
+    }
+}
+
+extern "C" JNIEXPORT jdouble JNICALL Java_com_android_pts_simplecpu_CpuNative_runMatrixMultiplication(
+        JNIEnv* env, jclass clazz, jint n, jint repetition)
+{
+    // C = A x B
+    float* A = new float[n * n];
+    float* B = new float[n * n];
+    float* C = new float[n * n];
+    if ((A == NULL) || (B == NULL) || (C == NULL)) {
+        delete[] A;
+        delete[] B;
+        delete[] C;
+        env->ThrowNew(env->FindClass("java/lang/OutOfMemoryError"), "No memory");
+        return -1;
+    }
+    double totalTime = 0;
+    for (int i = 0; i < repetition; i++) {
+        randomInitArray<float>(A, n * n, 0);
+        randomInitArray<float>(B, n * n, 1);
+        double start = currentTimeMillis();
+        doMatrixMultiplication(A, B, C, n);
+        double end = currentTimeMillis();
+        totalTime += (end - start);
+    }
+    delete[] A;
+    delete[] B;
+    delete[] C;
+    return totalTime;
+}
+
diff --git a/suite/pts/deviceTests/simplecpu/src/com/android/pts/simplecpu/CpuNative.java b/suite/pts/deviceTests/simplecpu/src/com/android/pts/simplecpu/CpuNative.java
new file mode 100644
index 0000000..43c4b50
--- /dev/null
+++ b/suite/pts/deviceTests/simplecpu/src/com/android/pts/simplecpu/CpuNative.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2012 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.pts.simplecpu;
+
+public class CpuNative {
+    static {
+        System.loadLibrary("ptscpu_jni");
+    }
+    /**
+     * run qsort for given number of repetition
+     * with each having the size of bufferSize.
+     * @param numberElements
+     * @param repeatition
+     * @return time taken for computation, added for all repetition in ms
+     */
+    public static native double runSort(int numberElements, int repetition);
+
+    /**
+     * run matrix multiplication of (n x n) x (n x n)
+     *
+     * @param n dimension, should be multiple of 8
+     * @param repetition
+     * @return time taken for computation, added for all repetition in ms
+     */
+    public static native double runMatrixMultiplication(int n, int repetition);
+}
diff --git a/suite/pts/deviceTests/simplecpu/src/com/android/pts/simplecpu/SimpleCpuTest.java b/suite/pts/deviceTests/simplecpu/src/com/android/pts/simplecpu/SimpleCpuTest.java
new file mode 100644
index 0000000..8b17171
--- /dev/null
+++ b/suite/pts/deviceTests/simplecpu/src/com/android/pts/simplecpu/SimpleCpuTest.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2012 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.pts.simplecpu;
+
+import android.cts.util.TimeoutReq;
+
+import com.android.pts.util.ResultType;
+import com.android.pts.util.ResultUnit;
+import com.android.pts.util.PtsAndroidTestCase;
+import com.android.pts.util.ReportLog;
+import com.android.pts.util.Stat;
+
+/**
+ * Very simple CPU benchmarking to check the basic capability of CPU.
+ * Cases include
+ *   qsort
+ *   matrix multiplication (for floating point performance)
+ */
+public class SimpleCpuTest extends PtsAndroidTestCase {
+    private static final String TAG = "BandwidthTest";
+    private static final int KB = 1024;
+    private static final int MB = 1024 * 1024;
+    private static final int NUMBER_REPEAT = 20;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        warmUpCpu();
+    }
+
+    public void testSort004KB() {
+        doTestSort(NUMBER_REPEAT, 4 * KB);
+    }
+
+    public void testSort128KB() {
+        doTestSort(NUMBER_REPEAT, 128 * KB);
+    }
+
+    public void testSort001MB() {
+        doTestSort(NUMBER_REPEAT, 1 * MB);
+    }
+
+    // will fit into L1
+    public void testMatrixMultiplication032() {
+        doMatrixMultiplication(NUMBER_REPEAT, 32);
+    }
+
+    // mostly fit into L2
+    public void testMatrixMultiplication128() {
+        doMatrixMultiplication(NUMBER_REPEAT, 128);
+    }
+
+    // may fit into L2
+    public void testMatrixMultiplication200() {
+        doMatrixMultiplication(NUMBER_REPEAT, 200);
+    }
+
+    public void testMatrixMultiplication400() {
+        doMatrixMultiplication(NUMBER_REPEAT, 400);
+    }
+
+    // will exceed L2
+    @TimeoutReq(minutes = 20)
+    public void testMatrixMultiplication600() {
+        doMatrixMultiplication(NUMBER_REPEAT, 600);
+    }
+
+    /**
+     * run some code to force full CPU freq.
+     */
+    private void warmUpCpu() {
+        CpuNative.runSort(1 * MB, 10);
+    }
+
+    /**
+     * qsort test
+     * @param numberRepeat
+     * @param arrayLength
+     */
+    private void doTestSort(int numberRepeat, int arrayLength) {
+        final int numberRepeatInEachCall = 10;
+        double[] result = new double[numberRepeat];
+        for (int i = 0; i < numberRepeat; i++) {
+            result[i] = CpuNative.runSort(arrayLength, numberRepeatInEachCall);
+        }
+        getReportLog().printArray("sorting time", result, ResultType.LOWER_BETTER,
+                ResultUnit.MS);
+        Stat.StatResult stat = Stat.getStat(result);
+        getReportLog().printSummary("sorting time", stat.mAverage, ResultType.LOWER_BETTER,
+                ResultUnit.MS);
+    }
+
+    /**
+     * Matrix multiplication test, nxn matrix multiplication
+     * @param numberRepeat
+     * @param n should be multiple of 8
+     */
+    private void doMatrixMultiplication(int numberRepeat, int n) {
+        assertTrue(n % 8 == 0);
+        final int numberRepeatInEachCall = 10;
+        double[] result = new double[numberRepeat];
+        for (int i = 0; i < numberRepeat; i++) {
+            result[i] = CpuNative.runMatrixMultiplication(n, numberRepeatInEachCall);
+        }
+        getReportLog().printArray("matrix mutiplication time", result, ResultType.LOWER_BETTER,
+                ResultUnit.MS);
+        Stat.StatResult stat = Stat.getStat(result);
+        getReportLog().printSummary("matrix mutiplication time", stat.mAverage,
+                ResultType.LOWER_BETTER, ResultUnit.MS);
+    }
+
+}
diff --git a/suite/pts/deviceTests/ui/Android.mk b/suite/pts/deviceTests/ui/Android.mk
index 28db8a1..3790342 100644
--- a/suite/pts/deviceTests/ui/Android.mk
+++ b/suite/pts/deviceTests/ui/Android.mk
@@ -24,12 +24,18 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := ptsutil ctsutil ctstestrunner
 
+LOCAL_JNI_SHARED_LIBRARIES := libctsopenglperf_jni
+
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
+# re-use existing openglperf code for CTS. Do not include any test from that dir.
+LOCAL_SRC_FILES += $(filter-out %Test.java,$(call all-java-files-under, ../../../../tests/tests/openglperf/src))
+# do not use its own assets for this package.
+LOCAL_ASSET_DIR := $(LOCAL_PATH)/../../../../tests/tests/openglperf/assets
 
 LOCAL_PACKAGE_NAME := PtsDeviceUi
 
 LOCAL_SDK_VERSION := 16
 
-include $(BUILD_PTS_PACKAGE)
+include $(BUILD_CTS_PACKAGE)
 
 
diff --git a/suite/pts/deviceTests/ui/AndroidManifest.xml b/suite/pts/deviceTests/ui/AndroidManifest.xml
index b5e4937b..6925b5d 100644
--- a/suite/pts/deviceTests/ui/AndroidManifest.xml
+++ b/suite/pts/deviceTests/ui/AndroidManifest.xml
@@ -31,6 +31,7 @@
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
+         <activity android:name="android.openglperf.cts.GlPlanetsActivity" />
     </application>
     <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
             android:targetPackage="com.android.pts.ui"
diff --git a/suite/pts/deviceTests/ui/src/com/android/pts/ui/FpsTest.java b/suite/pts/deviceTests/ui/src/com/android/pts/ui/FpsTest.java
new file mode 100644
index 0000000..a2fd8d9
--- /dev/null
+++ b/suite/pts/deviceTests/ui/src/com/android/pts/ui/FpsTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2012 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.pts.ui;
+
+import android.content.Context;
+import android.content.Intent;
+import android.openglperf.cts.GlPlanetsActivity;
+import android.util.Log;
+import android.view.Display;
+import android.view.WindowManager;
+
+import com.android.pts.util.ResultType;
+import com.android.pts.util.ResultUnit;
+import com.android.pts.util.PtsActivityInstrumentationTestCase2;
+import com.android.pts.util.ReportLog;
+import com.android.pts.util.Stat;
+
+/**
+ * measure time taken to render n frames with OpenGL.
+ * This will measure the jankiness of the Gl rendering.
+ * If some frames are delayed, total time will take longer than n x refresh_rate
+ */
+public class FpsTest extends PtsActivityInstrumentationTestCase2<GlPlanetsActivity> {
+    private static final String TAG = "FpsTest";
+    private static final int NUM_FRAMES_TO_RENDER = 60 * 60;
+    private static final long RENDERING_TIMEOUT = NUM_FRAMES_TO_RENDER / 10;
+    // error of this much in ms in refresh interval is not considered as jankiness
+    // note that this margin is set to be little big to consider that there can be additional delay
+    // measurement on some devices showed many jankiness around 2 to 3 ms,
+    // which should not be counted
+    private static final double REFRESH_INTERVAL_THRESHHOLD_IN_MS = 4.0;
+    private GlPlanetsActivity mActivity;
+
+    public FpsTest() {
+        super(GlPlanetsActivity.class);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        mActivity = null;
+        super.tearDown();
+    }
+
+    public void testFrameIntervals() throws Exception {
+        Intent intent = new Intent();
+        intent.putExtra(GlPlanetsActivity.INTENT_EXTRA_NUM_FRAMES,
+                NUM_FRAMES_TO_RENDER);
+        intent.putExtra(GlPlanetsActivity.INTENT_EXTRA_NUM_PLANETS, 0);
+        intent.putExtra(GlPlanetsActivity.INTENT_EXTRA_USE_VBO_VERTICES, true);
+        intent.putExtra(GlPlanetsActivity.INTENT_EXTRA_USE_VBO_INDICES, true);
+        intent.putExtra(GlPlanetsActivity.INTENT_EXTRA_NUM_INDEX_BUFFERS, 10);
+
+        setActivityIntent(intent);
+        mActivity = getActivity();
+        boolean waitResult = mActivity.waitForGlPlanetsCompletionWithTimeout(RENDERING_TIMEOUT);
+        assertTrue("timeout while waiting for rendering completion", waitResult);
+
+        int[] frameInterval = mActivity.getFrameInterval();
+        assertTrue(frameInterval.length == NUM_FRAMES_TO_RENDER);
+        double fpsMeasured = mActivity.getAverageFps();
+        WindowManager wm = (WindowManager)mActivity.getSystemService(Context.WINDOW_SERVICE);
+        Display dpy = wm.getDefaultDisplay();
+        double fpsNominal = dpy.getRefreshRate();
+        double frameIntervalNominalInMs = 1000.0 / fpsNominal;
+        int jankNumber = 0;
+        // only count positive ( = real delay)
+        double maxDelay = 0;
+        // first one not valid, and should be thrown away
+        double[] intervals = new double[NUM_FRAMES_TO_RENDER - 1];
+        double[] jankiness = new double[NUM_FRAMES_TO_RENDER - 1];
+        double deltaAccumulated = 0;
+        for (int i = 0; i < NUM_FRAMES_TO_RENDER - 1; i++) {
+            intervals[i] = frameInterval[i + 1];
+            double delay = (double)intervals[i] - frameIntervalNominalInMs;
+            if (Math.abs(delay) > REFRESH_INTERVAL_THRESHHOLD_IN_MS) {
+                // Falling here does not necessarily mean jank as it may be catching up
+                // the previous delay. Basically count the first delay as jank, but subsequent
+                // variation should be checked with accumulated delay
+                double delayAdjusted = 0;
+                if (deltaAccumulated == 0) { // This is the first delay. always consider as total
+                    jankNumber++;
+                    delayAdjusted = delay;
+                } else { // delay already happened
+                    double deltaFromLastRefresh = deltaAccumulated - Math.floor(deltaAccumulated /
+                            frameIntervalNominalInMs) * frameIntervalNominalInMs;
+                    if (deltaAccumulated < 0) {
+                        // adjust as the above operation makes delay positive
+                        deltaFromLastRefresh -= frameIntervalNominalInMs;
+                    }
+                    delayAdjusted = delay + deltaFromLastRefresh;
+                    if (Math.abs(delayAdjusted) > REFRESH_INTERVAL_THRESHHOLD_IN_MS) {
+                        jankNumber++;
+                    } else { // caught up
+                        delayAdjusted = 0;
+                        deltaAccumulated = 0;
+                    }
+                }
+                deltaAccumulated += delay;
+                if (delayAdjusted > maxDelay) {
+                    maxDelay = delayAdjusted;
+                }
+                jankiness[i] = delayAdjusted;
+            } else {
+                jankiness[i] = 0;
+                deltaAccumulated = 0;
+            }
+        }
+        Log.i(TAG, " fps nominal " + fpsNominal + " fps measured " + fpsMeasured);
+        getReportLog().printArray("intervals", intervals, ResultType.NEUTRAL,
+                ResultUnit.MS);
+        getReportLog().printArray("jankiness", jankiness, ResultType.LOWER_BETTER,
+                ResultUnit.MS);
+        getReportLog().printValue("number of jank", jankNumber, ResultType.LOWER_BETTER,
+                ResultUnit.COUNT);
+        getReportLog().printSummary("max jank delay", maxDelay, ResultType.LOWER_BETTER,
+                ResultUnit.MS);
+    }
+}
diff --git a/suite/pts/deviceTests/ui/src/com/android/pts/ui/ScrollingActivity.java b/suite/pts/deviceTests/ui/src/com/android/pts/ui/ScrollingActivity.java
index 7bdfff9..fbec65d 100644
--- a/suite/pts/deviceTests/ui/src/com/android/pts/ui/ScrollingActivity.java
+++ b/suite/pts/deviceTests/ui/src/com/android/pts/ui/ScrollingActivity.java
@@ -26,8 +26,10 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
-public class ScrollingActivity extends ListActivity implements OnScrollListener
-{
+/**
+ * Activity for measuring scrolling time of long list.
+ */
+public class ScrollingActivity extends ListActivity implements OnScrollListener {
     static final String TAG = "ScrollingActivity";
     private static final int NUMBER_ELEMENTS = 10000;
     private static final int SCROLL_TIME_IN_MS = 1;
@@ -36,8 +38,7 @@
     private CountDownLatch mLatchStop = null;
     private int mTargetLoc;
 
-    public void onCreate(Bundle icicle)
-    {
+    public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
         for (int i = 0; i < NUMBER_ELEMENTS; i++) {
             mItems[i] = Integer.toString(i);
@@ -46,7 +47,6 @@
                 android.R.layout.simple_list_item_1, mItems));
         ListView view = getListView();
         view.setOnScrollListener(this);
-        //view.setVelocityScale(100.0f);
     }
 
     public boolean scrollToTop() {
@@ -75,15 +75,6 @@
         mLatchStop = null;
         return result;
     }
-    public void onStop()
-    {
-        super.onStop();
-    }
-
-    public void onResume()
-    {
-        super.onResume();
-    }
 
     @Override
     public void onScrollStateChanged(AbsListView view, int scrollState) {
diff --git a/suite/pts/deviceTests/ui/src/com/android/pts/ui/ScrollingTest.java b/suite/pts/deviceTests/ui/src/com/android/pts/ui/ScrollingTest.java
index 8e4bbed..9fd43de 100644
--- a/suite/pts/deviceTests/ui/src/com/android/pts/ui/ScrollingTest.java
+++ b/suite/pts/deviceTests/ui/src/com/android/pts/ui/ScrollingTest.java
@@ -18,6 +18,8 @@
 import android.cts.util.TimeoutReq;
 import com.android.pts.util.MeasureRun;
 import com.android.pts.util.MeasureTime;
+import com.android.pts.util.ResultType;
+import com.android.pts.util.ResultUnit;
 import com.android.pts.util.PtsActivityInstrumentationTestCase2;
 import com.android.pts.util.ReportLog;
 import com.android.pts.util.Stat;
@@ -65,8 +67,10 @@
                 assertTrue(activity.scrollToTop());
             }
         });
-        getReportLog().printArray("ms", results, false);
+        getReportLog().printArray("scrolling time", results, ResultType.LOWER_BETTER,
+                ResultUnit.MS);
         Stat.StatResult stat = Stat.getStat(results);
-        getReportLog().printSummary("Time ms", stat.mAverage, stat.mStddev);
+        getReportLog().printSummary("scrolling time", stat.mAverage, ResultType.LOWER_BETTER,
+                ResultUnit.MS);
     }
 }
diff --git a/suite/pts/hostTests/browser/browserlauncher/Android.mk b/suite/pts/deviceTests/videoperf/Android.mk
similarity index 75%
copy from suite/pts/hostTests/browser/browserlauncher/Android.mk
copy to suite/pts/deviceTests/videoperf/Android.mk
index 0592017..055c9f7 100644
--- a/suite/pts/hostTests/browser/browserlauncher/Android.mk
+++ b/suite/pts/deviceTests/videoperf/Android.mk
@@ -1,4 +1,4 @@
-# Copyright (C) 2012 The Android Open Source Project
+# Copyright (C) 2013 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.
@@ -20,14 +20,13 @@
 
 LOCAL_JAVA_LIBRARIES := android.test.runner
 
-LOCAL_STATIC_JAVA_LIBRARIES := ptsutil ctsutil ctstestrunner ctstestserver
+LOCAL_STATIC_JAVA_LIBRARIES := ptsutil ctsutil ctstestrunner
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_PACKAGE_NAME := PtsDeviceBrowserLauncher
+LOCAL_PACKAGE_NAME := PtsDeviceVideoPerf
 
 LOCAL_SDK_VERSION := 16
 
-include $(BUILD_PTS_PACKAGE)
+include $(BUILD_CTS_PACKAGE)
 
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/suite/pts/deviceTests/videoperf/AndroidManifest.xml b/suite/pts/deviceTests/videoperf/AndroidManifest.xml
new file mode 100644
index 0000000..fdef1ef
--- /dev/null
+++ b/suite/pts/deviceTests/videoperf/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.pts.videoperf">
+
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+    <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+            android:targetPackage="com.android.pts.videoperf"
+            android:label="UI Latency measurement" />
+</manifest>
diff --git a/suite/pts/deviceTests/videoperf/src/com/android/pts/videoperf/CodecInfo.java b/suite/pts/deviceTests/videoperf/src/com/android/pts/videoperf/CodecInfo.java
new file mode 100644
index 0000000..a94395a
--- /dev/null
+++ b/suite/pts/deviceTests/videoperf/src/com/android/pts/videoperf/CodecInfo.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2013 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.pts.videoperf;
+
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaCodecInfo.CodecProfileLevel;
+import android.media.MediaCodecList;
+import android.util.Log;
+
+
+/**
+ * Utility class for getting codec information like bit rate, fps, and etc.
+ * Uses public member variables instead of methods as this code is only for video benchmarking.
+ */
+public class CodecInfo {
+    /** bit rate in bps */
+    public int mBitRate = 0;
+    /** Frame rate */
+    public int mFps = 0;
+    /** if codec is supporting YUV semiplanar format */
+    public boolean mSupportSemiPlanar = false;
+    /** if codec is supporting YUV planar format */
+    public boolean mSupportPlanar = false;
+
+    private static final String TAG = "CodecInfo";
+    private static final String VIDEO_AVC = "video/avc";
+    /**
+     * Check if given codec with given (w,h) is supported.
+     * @param mimeType codec type in mime format like "video/avc"
+     * @param w video width
+     * @param h video height
+     * @param isEncoder whether the codec is encoder or decoder
+     * @return null if the configuration is not supported.
+     */
+    public static CodecInfo getSupportedFormatInfo(String mimeType, int w, int h,
+            boolean isEncoder) {
+        CodecCapabilities cap = getCodecCapability(mimeType, isEncoder);
+        if (cap == null) { // not supported
+            return null;
+        }
+        CodecInfo info = new CodecInfo();
+        for (int color : cap.colorFormats) {
+            if (color == CodecCapabilities.COLOR_FormatYUV420SemiPlanar) {
+                info.mSupportSemiPlanar = true;
+            }
+            if (color == CodecCapabilities.COLOR_FormatYUV420Planar) {
+                info.mSupportPlanar = true;
+            }
+        }
+        printIntArray("supported colors", cap.colorFormats);
+        //  either YUV420 planar or semiplanar should be supported
+        if (!info.mSupportPlanar && !info.mSupportSemiPlanar) {
+            Log.i(TAG, "no supported color format");
+            return null;
+        }
+
+        if (mimeType.equals(VIDEO_AVC)) {
+            int highestLevel = 0;
+            for (CodecProfileLevel lvl : cap.profileLevels) {
+                if (lvl.level > highestLevel) {
+                    highestLevel = lvl.level;
+                }
+            }
+            Log.i(TAG, "Avc highest level " + Integer.toHexString(highestLevel));
+            int maxW = 0;
+            int maxH = 0;
+            int bitRate = 0;
+            double fps = 0; // frame rate for the max resolution
+            switch(highestLevel) {
+            // Do not support Level 1 to 2.
+            case CodecProfileLevel.AVCLevel1:
+            case CodecProfileLevel.AVCLevel11:
+            case CodecProfileLevel.AVCLevel12:
+            case CodecProfileLevel.AVCLevel13:
+            case CodecProfileLevel.AVCLevel1b:
+            case CodecProfileLevel.AVCLevel2:
+                return null;
+            case CodecProfileLevel.AVCLevel21:
+                maxW = 352;
+                maxH = 576;
+                bitRate = 4000000;
+                fps = 25;
+                break;
+            case CodecProfileLevel.AVCLevel22:
+                maxW = 720;
+                maxH = 480;
+                bitRate = 4000000;
+                fps = 15;
+                break;
+            case CodecProfileLevel.AVCLevel3:
+                maxW = 720;
+                maxH = 480;
+                bitRate = 10000000;
+                fps = 30;
+                break;
+            case CodecProfileLevel.AVCLevel31:
+                maxW = 1280;
+                maxH = 720;
+                bitRate = 14000000;
+                fps = 30;
+                break;
+            case CodecProfileLevel.AVCLevel32:
+                maxW = 1280;
+                maxH = 720;
+                bitRate = 20000000;
+                fps = 60;
+                break;
+            case CodecProfileLevel.AVCLevel4:
+                maxW = 1920;
+                maxH = 1080;
+                bitRate = 20000000;
+                fps = 30.1;
+                break;
+            case CodecProfileLevel.AVCLevel41:
+                maxW = 1920;
+                maxH = 1080;
+                bitRate = 50000000;
+                fps = 30.1;
+                break;
+            case CodecProfileLevel.AVCLevel42:
+                maxW = 2048;
+                maxH = 1080;
+                bitRate = 50000000;
+                fps = 60;
+                break;
+            case CodecProfileLevel.AVCLevel5:
+                maxW = 3672;
+                maxH = 1536;
+                bitRate = 135000000;
+                fps = 26.7;
+                break;
+            case CodecProfileLevel.AVCLevel51:
+                maxW = 4096;
+                maxH = 2304;
+                bitRate = 240000000;
+                fps = 26.7;
+                break;
+            default:
+                maxW = 4096;
+                maxH = 2304;
+                bitRate = 240000000;
+                fps = 26.7;
+                break;
+            }
+            if ((w > maxW) || (h > maxH)) {
+                Log.i(TAG, "Requested resolution (" + w + "," + h + ") exceeds (" +
+                        maxW + "," + maxH + ")");
+                return null;
+            }
+            info.mFps = (int)(fps * maxW * maxH / (w * h));
+            info.mBitRate = bitRate;
+            Log.i(TAG, "AVC Level " + Integer.toHexString(highestLevel) + " bit rate " + bitRate +
+                    " fps " + info.mFps);
+        }
+        return info;
+    }
+
+    /**
+     * Search for given codecName and returns CodecCapabilities if found
+     * @param codecName
+     * @param isEncoder true for encoder, false for decoder
+     * @return null if the codec is not supported
+     */
+    private static CodecCapabilities getCodecCapability(
+            String codecName, boolean isEncoder) {
+        int codecCount = MediaCodecList.getCodecCount();
+        for (int i = 0; i < codecCount; ++i) {
+            MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i);
+            String[] types = info.getSupportedTypes();
+            if (isEncoder != info.isEncoder()) {
+                continue;
+            }
+            for (int j = 0; j < types.length; ++j) {
+                if (types[j].compareTo(codecName) == 0) {
+                    CodecCapabilities cap = info.getCapabilitiesForType(types[j]);
+                    Log.i(TAG, "Use codec " + info.getName());
+                    return cap;
+                }
+            }
+        }
+        return null;
+    }
+
+    // for debugging
+    private static void printIntArray(String msg, int[] data) {
+        StringBuilder builder = new StringBuilder();
+        builder.append(msg);
+        builder.append(":");
+        for (int e : data) {
+            builder.append(Integer.toHexString(e));
+            builder.append(",");
+        }
+        builder.deleteCharAt(builder.length() - 1);
+        Log.i(TAG, builder.toString());
+    }
+}
diff --git a/suite/pts/deviceTests/videoperf/src/com/android/pts/videoperf/VideoEncoderDecoderTest.java b/suite/pts/deviceTests/videoperf/src/com/android/pts/videoperf/VideoEncoderDecoderTest.java
new file mode 100644
index 0000000..0a03ff3
--- /dev/null
+++ b/suite/pts/deviceTests/videoperf/src/com/android/pts/videoperf/VideoEncoderDecoderTest.java
@@ -0,0 +1,607 @@
+/*
+ * Copyright (C) 2013 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.pts.videoperf;
+
+import android.graphics.Point;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaCodecInfo.CodecProfileLevel;
+import android.media.MediaCodecList;
+import android.media.MediaFormat;
+import android.util.Log;
+
+import com.android.pts.util.PtsAndroidTestCase;
+import com.android.pts.util.ResultType;
+import com.android.pts.util.ResultUnit;
+import com.android.pts.util.Stat;
+
+import java.nio.ByteBuffer;
+import java.lang.System;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ * This tries to test video encoder / decoder performance by running encoding / decoding
+ * without displaying the raw data. To make things simpler, encoder is used to encode synthetic
+ * data and decoder is used to decode the encoded video. This approach does not work where
+ * there is only decoder. Performance index is total time taken for encoding and decoding
+ * the whole frames.
+ * To prevent sacrificing quality for faster encoding / decoding, randomly selected pixels are
+ * compared with the original image. As the pixel comparison can slow down the decoding process,
+ * only some randomly selected pixels are compared. As there can be only one performance index,
+ * error above certain threshold in pixel value will be treated as an error.
+ */
+public class VideoEncoderDecoderTest extends PtsAndroidTestCase {
+    private static final String TAG = "VideoEncoderDecoderTest";
+    // this wait time affects fps as too big value will work as a blocker if device fps
+    // is not very high.
+    private static final long VIDEO_CODEC_WAIT_TIME_US = 5000;
+    private static final boolean VERBOSE = false;
+    private static final String VIDEO_AVC = "video/avc";
+    private static final int TOTAL_FRAMES = 300;
+    private static final int NUMBER_OF_REPEAT = 10;
+    // i frame interval for encoder
+    private static final int KEY_I_FRAME_INTERVAL = 5;
+
+    private static final int Y_CLAMP_MIN = 16;
+    private static final int Y_CLAMP_MAX = 235;
+    private static final int YUV_PLANE_ADDITIONAL_LENGTH = 200;
+    private ByteBuffer mYBuffer;
+    private ByteBuffer mUVBuffer;
+    // if input raw data is semi-planar
+    private boolean mSrcSemiPlanar;
+    // if output raw data is semi-planar
+    private boolean mDstSemiPlanar;
+    private int mBufferWidth;
+    private int mBufferHeight;
+    private int mVideoWidth;
+    private int mVideoHeight;
+
+    private Vector<ByteBuffer> mEncodedOutputBuffer;
+    // check this many pixels per each decoded frame
+    // checking too many points decreases decoder frame rates a lot.
+    private static final int PIXEL_CHECK_PER_FRAME = 1000;
+    // RMS error in pixel values above this will be treated as error.
+    private static final double PIXEL_RMS_ERROR_MARGAIN = 20.0;
+    private Random mRandom;
+
+    @Override
+    protected void setUp() throws Exception {
+        mEncodedOutputBuffer = new Vector<ByteBuffer>(TOTAL_FRAMES * 2);
+        // Use time as a seed, hoping to prevent checking pixels in the same pattern
+        long now = System.currentTimeMillis();
+        mRandom = new Random(now);
+        super.setUp();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        mEncodedOutputBuffer.clear();
+        mEncodedOutputBuffer = null;
+        mYBuffer = null;
+        mUVBuffer = null;
+        mRandom = null;
+        super.tearDown();
+    }
+
+    public void testAvc0176x0144() throws Exception {
+        doTest(VIDEO_AVC, 176, 144, NUMBER_OF_REPEAT);
+    }
+
+    public void testAvc0352x0288() throws Exception {
+        doTest(VIDEO_AVC, 352, 288, NUMBER_OF_REPEAT);
+    }
+
+    public void testAvc0720x0480() throws Exception {
+        doTest(VIDEO_AVC, 720, 480, NUMBER_OF_REPEAT);
+    }
+
+    public void testAvc1280x0720() throws Exception {
+        doTest(VIDEO_AVC, 1280, 720, NUMBER_OF_REPEAT);
+    }
+
+    /**
+     * resolution intentionally set to 1072 not 1080
+     * as 1080 is not multiple of 16, and it requires additional setting like stride
+     * which is not specified in API documentation.
+     */
+    public void testAvc1920x1072() throws Exception {
+        doTest(VIDEO_AVC, 1920, 1072, NUMBER_OF_REPEAT);
+    }
+
+    /**
+     * Run encoding / decoding test for given mimeType of codec
+     * @param mimeType like video/avc
+     * @param w video width
+     * @param h video height
+     * @param numberRepeat how many times to repeat the encoding / decoding process
+     */
+    private void doTest(String mimeType, int w, int h, int numberRepeat) throws Exception {
+        CodecInfo infoEnc = CodecInfo.getSupportedFormatInfo(mimeType, w, h, true);
+        if (infoEnc == null) {
+            Log.i(TAG, "Codec " + mimeType + "with " + w + "," + h + " not supported");
+            return;
+        }
+        CodecInfo infoDec = CodecInfo.getSupportedFormatInfo(mimeType, w, h, false);
+        assertNotNull(infoDec);
+        mVideoWidth = w;
+        mVideoHeight = h;
+        initYUVPlane(w + YUV_PLANE_ADDITIONAL_LENGTH, h + YUV_PLANE_ADDITIONAL_LENGTH,
+                infoEnc.mSupportSemiPlanar, infoDec.mSupportSemiPlanar);
+        double[] encoderFpsResults = new double[numberRepeat];
+        double[] decoderFpsResults = new double[numberRepeat];
+        double[] totalFpsResults = new double[numberRepeat];
+        double[] decoderRmsErrorResults = new double[numberRepeat];
+        for (int i = 0; i < numberRepeat; i++) {
+            MediaFormat format = new MediaFormat();
+            format.setString(MediaFormat.KEY_MIME, mimeType);
+            format.setInteger(MediaFormat.KEY_BIT_RATE, infoEnc.mBitRate);
+            format.setInteger(MediaFormat.KEY_WIDTH, w);
+            format.setInteger(MediaFormat.KEY_HEIGHT, h);
+            format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+                    infoEnc.mSupportSemiPlanar ? CodecCapabilities.COLOR_FormatYUV420SemiPlanar :
+                        CodecCapabilities.COLOR_FormatYUV420Planar);
+            format.setInteger(MediaFormat.KEY_FRAME_RATE, infoEnc.mFps);
+            format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, KEY_I_FRAME_INTERVAL);
+            double encodingTime = runEncoder(VIDEO_AVC, format, TOTAL_FRAMES);
+            // re-initialize format for decoder
+            format = new MediaFormat();
+            format.setString(MediaFormat.KEY_MIME, mimeType);
+            format.setInteger(MediaFormat.KEY_WIDTH, w);
+            format.setInteger(MediaFormat.KEY_HEIGHT, h);
+            format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+                    infoDec.mSupportSemiPlanar ? CodecCapabilities.COLOR_FormatYUV420SemiPlanar :
+                        CodecCapabilities.COLOR_FormatYUV420Planar);
+            double[] decoderResult = runDecoder(VIDEO_AVC, format);
+            if (decoderResult == null) {
+                // color change in the middle. give up
+                return;
+            }
+            double decodingTime = decoderResult[0];
+            decoderRmsErrorResults[i] = decoderResult[1];
+            encoderFpsResults[i] = (double)TOTAL_FRAMES / encodingTime * 1000.0;
+            decoderFpsResults[i] = (double)TOTAL_FRAMES / decodingTime * 1000.0;
+            totalFpsResults[i] = (double)TOTAL_FRAMES / (encodingTime + decodingTime) * 1000.0;
+
+            // clear things for re-start
+            mEncodedOutputBuffer.clear();
+            // it will be good to clean everything to make every run the same.
+            System.gc();
+        }
+        getReportLog().printArray("encoder", encoderFpsResults, ResultType.HIGHER_BETTER,
+                ResultUnit.FPS);
+        getReportLog().printArray("rms error", decoderRmsErrorResults, ResultType.LOWER_BETTER,
+                ResultUnit.NONE);
+        getReportLog().printArray("decoder", decoderFpsResults, ResultType.HIGHER_BETTER,
+                ResultUnit.FPS);
+        getReportLog().printArray("encoder decoder", totalFpsResults, ResultType.HIGHER_BETTER,
+                ResultUnit.FPS);
+        getReportLog().printSummary("encoder decoder", Stat.getAverage(totalFpsResults),
+                ResultType.HIGHER_BETTER, ResultUnit.FPS);
+        // make sure that rms error is not too big.
+        for (int i = 0; i < numberRepeat; i++) {
+            assertTrue(decoderRmsErrorResults[i] < PIXEL_RMS_ERROR_MARGAIN);
+        }
+    }
+
+    /**
+     * run encoder benchmarking
+     * @param mimeType encoder type like video/avc
+     * @param format format of media to encode
+     * @param totalFrames total number of frames to encode
+     * @return time taken in ms to encode the frames. This does not include initialization time.
+     */
+    private double runEncoder(String mimeType, MediaFormat format, int totalFrames) {
+        MediaCodec codec = MediaCodec.createEncoderByType(mimeType);
+        try {
+            codec.configure(
+                    format,
+                    null /* surface */,
+                    null /* crypto */,
+                    MediaCodec.CONFIGURE_FLAG_ENCODE);
+        } catch (IllegalStateException e) {
+            Log.e(TAG, "codec '" + mimeType + "' failed configuration.");
+            assertTrue("codec '" + mimeType + "' failed configuration.", false);
+        }
+        codec.start();
+        ByteBuffer[] codecInputBuffers = codec.getInputBuffers();
+        ByteBuffer[] codecOutputBuffers = codec.getOutputBuffers();
+
+        int numBytesSubmitted = 0;
+        int numBytesDequeued = 0;
+        int inFramesCount = 0;
+        long start = System.currentTimeMillis();
+        while (true) {
+            int index;
+
+            if (inFramesCount < totalFrames) {
+                index = codec.dequeueInputBuffer(VIDEO_CODEC_WAIT_TIME_US /* timeoutUs */);
+                if (index != MediaCodec.INFO_TRY_AGAIN_LATER) {
+                    int size = queueInputBufferEncoder(
+                            codec, codecInputBuffers, index, inFramesCount,
+                            (inFramesCount == (totalFrames - 1)) ?
+                                    MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
+                    inFramesCount++;
+                    numBytesSubmitted += size;
+                    if (VERBOSE) {
+                        Log.d(TAG, "queued " + size + " bytes of input data, frame " +
+                                (inFramesCount - 1));
+                    }
+
+                }
+            }
+            MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+            index = codec.dequeueOutputBuffer(info, VIDEO_CODEC_WAIT_TIME_US /* timeoutUs */);
+            if (index == MediaCodec.INFO_TRY_AGAIN_LATER) {
+            } else if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+            } else if (index == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                codecOutputBuffers = codec.getOutputBuffers();
+            } else if (index >= 0) {
+                dequeueOutputBufferEncoder(codec, codecOutputBuffers, index, info);
+                numBytesDequeued += info.size;
+                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                    if (VERBOSE) {
+                        Log.d(TAG, "dequeued output EOS.");
+                    }
+                    break;
+                }
+                if (VERBOSE) {
+                    Log.d(TAG, "dequeued " + info.size + " bytes of output data.");
+                }
+            }
+        }
+        long finish = System.currentTimeMillis();
+        if (VERBOSE) {
+            Log.d(TAG, "queued a total of " + numBytesSubmitted + "bytes, "
+                    + "dequeued " + numBytesDequeued + " bytes.");
+        }
+        codec.stop();
+        codec.release();
+        codec = null;
+        return (double)(finish - start);
+    }
+
+    /**
+     * Fills input buffer for encoder from YUV buffers.
+     * @return size of enqueued data.
+     */
+    private int queueInputBufferEncoder(
+            MediaCodec codec, ByteBuffer[] inputBuffers, int index, int frameCount, int flags) {
+        ByteBuffer buffer = inputBuffers[index];
+        buffer.clear();
+
+        Point origin = getOrigin(frameCount);
+        // Y color first
+        int srcOffsetY = origin.x + origin.y * mBufferWidth;
+        final byte[] yBuffer = mYBuffer.array();
+        for (int i = 0; i < mVideoHeight; i++) {
+            buffer.put(yBuffer, srcOffsetY, mVideoWidth);
+            srcOffsetY += mBufferWidth;
+        }
+        if (mSrcSemiPlanar) {
+            int srcOffsetU = origin.y / 2 * mBufferWidth + origin.x / 2 * 2;
+            final byte[] uvBuffer = mUVBuffer.array();
+            for (int i = 0; i < mVideoHeight / 2; i++) {
+                buffer.put(uvBuffer, srcOffsetU, mVideoWidth);
+                srcOffsetU += mBufferWidth;
+            }
+        } else {
+            int srcOffsetU = origin.y / 2 * mBufferWidth / 2 + origin.x / 2;
+            int srcOffsetV = srcOffsetU + mBufferWidth / 2 * mBufferHeight / 2;
+            final byte[] uvBuffer = mUVBuffer.array();
+            for (int i = 0; i < mVideoHeight /2; i++) { //U only
+                buffer.put(uvBuffer, srcOffsetU, mVideoWidth / 2);
+                srcOffsetU += mBufferWidth / 2;
+            }
+            for (int i = 0; i < mVideoHeight /2; i++) { //V only
+                buffer.put(uvBuffer, srcOffsetV, mVideoWidth / 2);
+                srcOffsetV += mBufferWidth / 2;
+            }
+        }
+        int size = mVideoHeight * mVideoWidth * 3 /2;
+
+        codec.queueInputBuffer(index, 0 /* offset */, size, 0 /* timeUs */, flags);
+        if (VERBOSE && (frameCount == 0)) {
+            printByteArray("Y ", mYBuffer.array(), 0, 20);
+            printByteArray("UV ", mUVBuffer.array(), 0, 20);
+            printByteArray("UV ", mUVBuffer.array(), mBufferWidth * 60, 20);
+        }
+        return size;
+    }
+
+    /**
+     * Dequeue encoded data from output buffer and store for later usage.
+     */
+    private void dequeueOutputBufferEncoder(
+            MediaCodec codec, ByteBuffer[] outputBuffers,
+            int index, MediaCodec.BufferInfo info) {
+        ByteBuffer output = outputBuffers[index];
+        output.clear();
+        int l = info.size;
+        ByteBuffer copied = ByteBuffer.allocate(l);
+        output.get(copied.array(), 0, l);
+        mEncodedOutputBuffer.add(copied);
+        codec.releaseOutputBuffer(index, false /* render */);
+    }
+
+    /**
+     * run encoder benchmarking with encoded stream stored from encoding phase
+     * @param mimeType encoder type like video/avc
+     * @param format format of media to decode
+     * @return returns length-2 array with 0: time for decoding, 1 : rms error of pixels
+     */
+    private double[] runDecoder(String mimeType, MediaFormat format) {
+        MediaCodec codec = MediaCodec.createDecoderByType(mimeType);
+        codec.configure(format, null /* surface */, null /* crypto */, 0 /* flags */);
+        codec.start();
+        ByteBuffer[] codecInputBuffers = codec.getInputBuffers();
+        ByteBuffer[] codecOutputBuffers = codec.getOutputBuffers();
+
+        double totalErrorSquared = 0;
+
+        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+        boolean sawOutputEOS = false;
+        int inputLeft = mEncodedOutputBuffer.size();
+        int inputBufferCount = 0;
+        int outFrameCount = 0;
+        YUVValue expected = new YUVValue();
+        YUVValue decoded = new YUVValue();
+        long start = System.currentTimeMillis();
+        while (!sawOutputEOS) {
+            if (inputLeft > 0) {
+                int inputBufIndex = codec.dequeueInputBuffer(VIDEO_CODEC_WAIT_TIME_US);
+
+                if (inputBufIndex >= 0) {
+                    ByteBuffer dstBuf = codecInputBuffers[inputBufIndex];
+                    dstBuf.clear();
+                    ByteBuffer src = mEncodedOutputBuffer.get(inputBufferCount);
+                    int writeSize = src.capacity();
+                    dstBuf.put(src.array(), 0, writeSize);
+                    codec.queueInputBuffer(
+                            inputBufIndex,
+                            0 /* offset */,
+                            writeSize,
+                            0,
+                            (inputLeft == 1) ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
+                    inputLeft --;
+                    inputBufferCount ++;
+                }
+            }
+
+            int res = codec.dequeueOutputBuffer(info, VIDEO_CODEC_WAIT_TIME_US);
+            if (res >= 0) {
+                int outputBufIndex = res;
+                ByteBuffer buf = codecOutputBuffers[outputBufIndex];
+                if (VERBOSE && (outFrameCount == 0)) {
+                    printByteBuffer("Y ", buf, 0, 20);
+                    printByteBuffer("UV ", buf, mVideoWidth * mVideoHeight, 20);
+                    printByteBuffer("UV ", buf, mVideoWidth * mVideoHeight + mVideoWidth * 60, 20);
+                }
+                Point origin = getOrigin(outFrameCount);
+                for (int i = 0; i < PIXEL_CHECK_PER_FRAME; i++) {
+                    int w = mRandom.nextInt(mVideoWidth);
+                    int h = mRandom.nextInt(mVideoHeight);
+                    getPixelValuesFromYUVBuffers(origin.x, origin.y, w, h, expected);
+                    getPixelValuesFromOutputBuffer(buf, w, h, decoded);
+                    if (VERBOSE) {
+                        Log.i(TAG, outFrameCount + "-" + i + "- th round expcted " + expected.mY +
+                                "," + expected.mU + "," + expected.mV + "  decoded " + decoded.mY +
+                                "," + decoded.mU + "," + decoded.mV);
+                    }
+                    totalErrorSquared += expected.calcErrorSquared(decoded);
+                }
+                codec.releaseOutputBuffer(outputBufIndex, false /* render */);
+                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                    Log.d(TAG, "saw output EOS.");
+                    sawOutputEOS = true;
+                }
+                outFrameCount++;
+            } else if (res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                codecOutputBuffers = codec.getOutputBuffers();
+                Log.d(TAG, "output buffers have changed.");
+            } else if (res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                MediaFormat oformat = codec.getOutputFormat();
+                Log.d(TAG, "output format has changed to " + oformat);
+                int colorFormat = oformat.getInteger(MediaFormat.KEY_COLOR_FORMAT);
+                if (colorFormat == CodecCapabilities.COLOR_FormatYUV420SemiPlanar ) {
+                    mDstSemiPlanar = true;
+                } else if (colorFormat == CodecCapabilities.COLOR_FormatYUV420Planar ) {
+                    mDstSemiPlanar = false;
+                } else {
+                    Log.w(TAG, "output format changed to unsupported one " +
+                            Integer.toHexString(colorFormat));
+                    // give up and return as nothing can be done
+                    codec.release();
+                    return null;
+                }
+            }
+        }
+        long finish = System.currentTimeMillis();
+        codec.stop();
+        codec.release();
+        codec = null;
+        assertTrue(outFrameCount >= TOTAL_FRAMES);
+        // divide by 3 as sum is done for Y, U, V.
+        double errorRms = Math.sqrt(totalErrorSquared / PIXEL_CHECK_PER_FRAME / outFrameCount / 3);
+        double[] result = { (double) finish - start, errorRms };
+        return result;
+    }
+
+    /**
+     *  returns origin in the absolute frame for given frame count.
+     *  The video scene is moving by moving origin per each frame.
+     */
+    private Point getOrigin(int frameCount) {
+        if (frameCount < 100) {
+            return new Point(2 * frameCount, 0);
+        } else if (frameCount < 200) {
+            return new Point(200, (frameCount - 100) * 2);
+        } else {
+            if (frameCount > 300) { // for safety
+                frameCount = 300;
+            }
+            return new Point(600 - frameCount * 2, 600 - frameCount * 2);
+        }
+    }
+
+    /**
+     * initialize reference YUV plane
+     * @param w This should be YUV_PLANE_ADDITIONAL_LENGTH pixels bigger than video resolution
+     *          to allow movements
+     * @param h This should be YUV_PLANE_ADDITIONAL_LENGTH pixels bigger than video resolution
+     *          to allow movements
+     * @param semiPlanarEnc
+     * @param semiPlanarDec
+     */
+    private void initYUVPlane(int w, int h, boolean semiPlanarEnc, boolean semiPlanarDec) {
+        int bufferSizeY = w * h;
+        mYBuffer = ByteBuffer.allocate(bufferSizeY);
+        mUVBuffer = ByteBuffer.allocate(bufferSizeY / 2);
+        mSrcSemiPlanar = semiPlanarEnc;
+        mDstSemiPlanar = semiPlanarDec;
+        mBufferWidth = w;
+        mBufferHeight = h;
+        final byte[] yArray = mYBuffer.array();
+        final byte[] uvArray = mUVBuffer.array();
+        for (int i = 0; i < h; i++) {
+            for (int j = 0; j < w; j++) {
+                yArray[i * w + j]  = clampY((i + j) & 0xff);
+            }
+        }
+        if (semiPlanarEnc) {
+            for (int i = 0; i < h/2; i++) {
+                for (int j = 0; j < w/2; j++) {
+                    uvArray[i * w + 2 * j]  = (byte) (i & 0xff);
+                    uvArray[i * w + 2 * j + 1]  = (byte) (j & 0xff);
+                }
+            }
+        } else { // planar, U first, then V
+            int vOffset = bufferSizeY / 4;
+            for (int i = 0; i < h/2; i++) {
+                for (int j = 0; j < w/2; j++) {
+                    uvArray[i * w/2 + j]  = (byte) (i & 0xff);
+                    uvArray[i * w/2 + vOffset + j]  = (byte) (j & 0xff);
+                }
+            }
+        }
+    }
+
+    /**
+     * class to store pixel values in YUV
+     *
+     */
+    public class YUVValue {
+        public byte mY;
+        public byte mU;
+        public byte mV;
+        public YUVValue() {
+        }
+
+        public boolean equalTo(YUVValue other) {
+            return (mY == other.mY) && (mU == other.mU) && (mV == other.mV);
+        }
+
+        public double calcErrorSquared(YUVValue other) {
+            double yDelta = mY - other.mY;
+            double uDelta = mU - other.mU;
+            double vDelta = mV - other.mV;
+            return yDelta * yDelta + uDelta * uDelta + vDelta * vDelta;
+        }
+    }
+
+    /**
+     * Read YUV values from given position (x,y) for given origin (originX, originY)
+     * The whole data is already available from YBuffer and UVBuffer.
+     * @param result pass the result via this. This is for avoiding creating / destroying too many
+     *               instances
+     */
+    private void getPixelValuesFromYUVBuffers(int originX, int originY, int x, int y,
+            YUVValue result) {
+        result.mY = mYBuffer.get((originY + y) * mBufferWidth + (originX + x));
+        if (mSrcSemiPlanar) {
+            int index = (originY + y) / 2 * mBufferWidth + (originX + x) / 2 * 2;
+            //Log.d(TAG, "YUV " + originX + "," + originY + "," + x + "," + y + "," + index);
+            result.mU = mUVBuffer.get(index);
+            result.mV = mUVBuffer.get(index + 1);
+        } else {
+            int vOffset = mBufferWidth * mBufferHeight / 4;
+            int index = (originY + y) / 2 * mBufferWidth / 2 + (originX + x) / 2;
+            result.mU = mUVBuffer.get(index);
+            result.mV = mUVBuffer.get(vOffset + index);
+        }
+    }
+
+    /**
+     * Read YUV pixels from decoded output buffer for give (x, y) position
+     * Output buffer is composed of Y parts followed by U/V
+     * @param result pass the result via this. This is for avoiding creating / destroying too many
+     *               instances
+     */
+    private void getPixelValuesFromOutputBuffer(ByteBuffer buffer, int x, int y, YUVValue result) {
+        result.mY = buffer.get(y * mVideoWidth + x);
+        if (mDstSemiPlanar) {
+            int index = mVideoWidth * mVideoHeight + y / 2 * mVideoWidth + x / 2 * 2;
+            //Log.d(TAG, "Decoded " + x + "," + y + "," + index);
+            result.mU = buffer.get(index);
+            result.mV = buffer.get(index + 1);
+        } else {
+            int vOffset = mVideoWidth * mVideoHeight / 4;
+            int index = mVideoWidth * mVideoHeight + y / 2 * mVideoWidth / 2 + x / 2;
+            result.mU = buffer.get(index);
+            result.mV = buffer.get(index + vOffset);
+        }
+    }
+
+    /**
+     * Y cannot have full range. clamp it to prevent invalid value.
+     */
+    private byte clampY(int y) {
+        if (y < Y_CLAMP_MIN) {
+            y = Y_CLAMP_MIN;
+        } else if (y > Y_CLAMP_MAX) {
+            y = Y_CLAMP_MAX;
+        }
+        return (byte) (y & 0xff);
+    }
+
+    // for debugging
+    private void printByteArray(String msg, byte[] data, int offset, int len) {
+        StringBuilder builder = new StringBuilder();
+        builder.append(msg);
+        builder.append(":");
+        for (int i = offset; i < offset + len; i++) {
+            builder.append(Integer.toHexString(data[i]));
+            builder.append(",");
+        }
+        builder.deleteCharAt(builder.length() - 1);
+        Log.i(TAG, builder.toString());
+    }
+
+    // for debugging
+    private void printByteBuffer(String msg, ByteBuffer data, int offset, int len) {
+        StringBuilder builder = new StringBuilder();
+        builder.append(msg);
+        builder.append(":");
+        for (int i = offset; i < offset + len; i++) {
+            builder.append(Integer.toHexString(data.get(i)));
+            builder.append(",");
+        }
+        builder.deleteCharAt(builder.length() - 1);
+        Log.i(TAG, builder.toString());
+    }
+}
diff --git a/suite/pts/expectations/knownfailures.txt b/suite/pts/expectations/knownfailures.txt
deleted file mode 100644
index 0d4f101..0000000
--- a/suite/pts/expectations/knownfailures.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-[
-]
diff --git a/suite/pts/hostTests/bootup/Android.mk b/suite/pts/hostTests/bootup/Android.mk
index 4d779a0..1872284 100644
--- a/suite/pts/hostTests/bootup/Android.mk
+++ b/suite/pts/hostTests/bootup/Android.mk
@@ -24,7 +24,7 @@
 
 LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed-prebuilt ddmlib-prebuilt junit ptscommonutilhost
 
-LOCAL_PTS_TEST_PACKAGE := com.android.pts.bootup
+LOCAL_CTS_TEST_PACKAGE := com.android.pts.bootup
 
-include $(BUILD_PTS_HOST_JAVA_LIBRARY)
+include $(BUILD_CTS_HOST_JAVA_LIBRARY)
 
diff --git a/suite/pts/hostTests/bootup/src/com/android/pts/bootup/BootupTimeTest.java b/suite/pts/hostTests/bootup/src/com/android/pts/bootup/BootupTimeTest.java
index e78289b..dd7b834 100644
--- a/suite/pts/hostTests/bootup/src/com/android/pts/bootup/BootupTimeTest.java
+++ b/suite/pts/hostTests/bootup/src/com/android/pts/bootup/BootupTimeTest.java
@@ -19,6 +19,8 @@
 import android.cts.util.TimeoutReq;
 import com.android.pts.util.MeasureRun;
 import com.android.pts.util.MeasureTime;
+import com.android.pts.util.ResultType;
+import com.android.pts.util.ResultUnit;
 import com.android.pts.util.ReportLog;
 import com.android.pts.util.Stat;
 import com.android.pts.util.Stat.StatResult;
@@ -65,9 +67,11 @@
                 rebootDevice();
             }
         });
-        mReport.printArray("time in ms", result, false);
+        mReport.printArray("bootup time", result, ResultType.LOWER_BETTER,
+                ResultUnit.MS);
         StatResult stat = Stat.getStat(result);
-        mReport.printSummary("time in ms", stat.mAverage, stat.mStddev);
+        mReport.printSummary("bootup time", stat.mAverage, ResultType.LOWER_BETTER,
+                ResultUnit.MS);
     }
 
     private void rebootDevice() throws DeviceNotAvailableException {
diff --git a/suite/pts/hostTests/browser/Android.mk b/suite/pts/hostTests/browser/Android.mk
deleted file mode 100644
index 9e45477..0000000
--- a/suite/pts/hostTests/browser/Android.mk
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright (C) 2012 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.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_MODULE := PtsHostBrowser
-
-LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed-prebuilt ddmlib-prebuilt junit ptscommonutilhost ptshostutil
-
-LOCAL_PTS_TEST_PACKAGE := com.android.pts.browser
-
-include $(BUILD_PTS_HOST_JAVA_LIBRARY)
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/suite/pts/hostTests/browser/browserlauncher/src/com/android/pts/browser/LaunchBrowserTest.java b/suite/pts/hostTests/browser/browserlauncher/src/com/android/pts/browser/LaunchBrowserTest.java
deleted file mode 100644
index a9e48f3..0000000
--- a/suite/pts/hostTests/browser/browserlauncher/src/com/android/pts/browser/LaunchBrowserTest.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2012 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.pts.browser;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.Uri;
-import android.webkit.cts.CtsTestServer;
-import com.android.pts.util.PtsAndroidTestCase;
-
-import java.util.concurrent.Semaphore;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Device side code to run browser benchmarking.
- * It launches an activity with URL and wait for broadcast sent from host.
- * It is host's responsibility to send broadcast after parsing browser's result.
- */
-public class LaunchBrowserTest extends PtsAndroidTestCase {
-
-    private static final String OCTANE_START_FILE = "octane/index.html";
-    private static final String HOST_COMPLETION_BROADCAST = "com.android.pts.browser.completion";
-    private static long BROWSER_COMPLETION_TIMEOUT = 10 * 60;
-    private CtsTestServer mWebServer;
-    private HostBroadcastReceiver mReceiver;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mWebServer = new CtsTestServer(getContext());
-        mReceiver = new HostBroadcastReceiver();
-        mReceiver.register(getContext(), HOST_COMPLETION_BROADCAST);
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        mReceiver.unregister(getContext());
-        mWebServer.shutdown();
-        mWebServer = null;
-        super.tearDown();
-    }
-
-    public void testOctane() throws InterruptedException {
-        String url = mWebServer.getAssetUrl(OCTANE_START_FILE) + "?auto=1";
-        Uri uri = Uri.parse(url);
-        Intent intent = new Intent(Intent.ACTION_VIEW, uri);
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        getContext().startActivity(intent);
-        mReceiver.waitForBroadcast(BROWSER_COMPLETION_TIMEOUT);
-    }
-
-    class HostBroadcastReceiver extends BroadcastReceiver {
-        private final Semaphore mSemaphore = new Semaphore(0);
-
-        public void register(Context context, String actionName) {
-            IntentFilter filter = new IntentFilter();
-            filter.addAction(actionName);
-            context.registerReceiver(this, filter);
-        }
-
-        public void unregister(Context context) {
-            context.unregisterReceiver(this);
-        }
-
-        public boolean waitForBroadcast(long timeoutInSec) throws InterruptedException {
-            return mSemaphore.tryAcquire(timeoutInSec, TimeUnit.SECONDS);
-        }
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            mSemaphore.release();
-        }
-    }
-}
diff --git a/suite/pts/hostTests/browser/src/com/android/pts/browser/BrowserTest.java b/suite/pts/hostTests/browser/src/com/android/pts/browser/BrowserTest.java
deleted file mode 100644
index b5400ab..0000000
--- a/suite/pts/hostTests/browser/src/com/android/pts/browser/BrowserTest.java
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright (C) 2012 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.pts.browser;
-
-import android.cts.util.TimeoutReq;
-
-import com.android.cts.tradefed.build.CtsBuildHelper;
-import com.android.ddmlib.Log;
-import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
-import com.android.pts.ptsutil.LogcatLineReceiver;
-import com.android.pts.util.ReportLog;
-import com.android.pts.util.Stat;
-import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.result.CollectingTestListener;
-import com.android.tradefed.result.TestRunResult;
-import com.android.tradefed.testtype.DeviceTestCase;
-import com.android.tradefed.testtype.IBuildReceiver;
-
-import java.io.File;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Run browser benchmarking.
- * Benchmarking result is printed via logcat (JavaScript console.log method).
- * Corresponding device side test should be running to make browser benchmarking working,
- * And the device test expects HOST_COMPLETION_BROADCAST broadcast from host as device
- * test cannot detect the completion from browser side.
- */
-public class BrowserTest extends DeviceTestCase implements IBuildReceiver {
-    private static final String BROADCAST_CMD = "am broadcast -a com.android.pts.browser.completion";
-    private static final String TAG = "BrowserTest";
-    private static final String CTS_RUNNER = "android.test.InstrumentationCtsTestRunner";
-    private static final String PACKAGE = "com.android.pts.browser";
-    private static final String APK = "PtsDeviceBrowserLauncher.apk";
-    private static final long LOGCAT_TIMEOUT_IN_SEC = 10 * 60L;
-    private static final String LOGCAT_FILTER = " browser:D chromium:D *:S";
-    private static final long REBOOT_WAIT_TIME_IN_MS = 2 * 60 * 1000L;
-
-    private CtsBuildHelper mBuild;
-    private ITestDevice mDevice;
-    private LogcatLineReceiver mReceiver;
-    private ReportLog mReport;
-
-    private volatile boolean mIgnoreLine = true;
-    private double mResult;
-
-    @Override
-    public void setBuild(IBuildInfo buildInfo) {
-        mBuild = CtsBuildHelper.createBuildHelper(buildInfo);
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mReport = new ReportLog();
-        mDevice = getDevice();
-        mDevice.uninstallPackage(PACKAGE);
-        File app = mBuild.getTestApp(APK);
-        mDevice.installPackage(app, false);
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-        mDevice.uninstallPackage(PACKAGE);
-        mReport.throwReportToHost();
-    }
-
-    @TimeoutReq(minutes = 40)
-    public void testOctane() throws Exception {
-        String resultPattern = "([A-Z][\\d\\w]+): ([\\d]+)";
-        String summaryPattern = "(Octane Score.*): ([\\d]+)";
-        int numberRepeat = 5;
-        double[] results = new double[numberRepeat];
-        for (int i = 0; i < numberRepeat; i++) {
-            Log.i(TAG, i + "-th round");
-            // browser will not refresh if the page is already loaded.
-            mDevice.reboot();
-            Thread.sleep(REBOOT_WAIT_TIME_IN_MS);
-            results[i] = runBenchmarking("testOctane", resultPattern, summaryPattern);
-        }
-        mReport.printArray("scores", results, true);
-        Stat.StatResult stat = Stat.getStat(results);
-        mReport.printSummary("Score", stat.mAverage, stat.mStddev);
-    }
-
-    private double runBenchmarking(String testMethodName, String resultPattern,
-            String summaryPattern) throws DeviceNotAvailableException, InterruptedException {
-        RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(PACKAGE, CTS_RUNNER,
-                mDevice.getIDevice());
-        testRunner.setMethodName("com.android.pts.browser.LaunchBrowserTest", testMethodName);
-        CollectingTestListener listener = new CollectingTestListener();
-        mIgnoreLine = true;
-        startLogMonitoring(resultPattern, summaryPattern);
-        // hack to ignore already captured logcat as the monitor will get it again.
-        // Checking time and skipping may be a better logic, but simply throwing away also works.
-        Thread.sleep(5000);
-        mIgnoreLine = false;
-        Log.i(TAG, "start to run test in device");
-        mDevice.runInstrumentationTests(testRunner, listener);
-        TestRunResult result = listener.getCurrentRunResults();
-        if (result.isRunFailure()) {
-            fail(result.getRunFailureMessage());
-        }
-        if (result.getNumPassedTests() == 0) {
-            fail("maybe timeout");
-        }
-        stopLogMonitoring();
-        return mResult;
-    }
-
-    void startLogMonitoring(String resultPattern, String summaryPattern)
-            throws InterruptedException, DeviceNotAvailableException {
-        final Pattern result = Pattern.compile(resultPattern);
-        final Pattern summary = Pattern.compile(summaryPattern);
-        mReceiver = new LogcatLineReceiver(mDevice, LOGCAT_FILTER, LOGCAT_TIMEOUT_IN_SEC) {
-            @Override
-            public void processALine(String line) throws DeviceNotAvailableException {
-                Log.i(TAG, "processALine " + line + " ignore " + mIgnoreLine);
-                if (mIgnoreLine) {
-                    return;
-                }
-                Matcher matchResult = result.matcher(line);
-                if (matchResult.find()) {
-                    mReport.printValue(matchResult.group(1),
-                            Double.parseDouble(matchResult.group(2)));
-                }
-                Matcher matchSummary = summary.matcher(line);
-                if (matchSummary.find()) {
-                    mResult = Double.parseDouble(matchSummary.group(2));
-                    mReport.printValue(matchSummary.group(1), mResult);
-                    mDevice.executeShellCommand(BROADCAST_CMD);
-                }
-            }
-        };
-        mReceiver.start();
-    }
-
-    void stopLogMonitoring() {
-        mReceiver.stop();
-    }
-}
diff --git a/suite/pts/hostTests/uihost/Android.mk b/suite/pts/hostTests/uihost/Android.mk
index 77673a2..65742e2 100644
--- a/suite/pts/hostTests/uihost/Android.mk
+++ b/suite/pts/hostTests/uihost/Android.mk
@@ -24,8 +24,8 @@
 
 LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed-prebuilt ddmlib-prebuilt junit ptscommonutilhost
 
-LOCAL_PTS_TEST_PACKAGE := com.android.pts.uihost
+LOCAL_CTS_TEST_PACKAGE := com.android.pts.uihost
 
-include $(BUILD_PTS_HOST_JAVA_LIBRARY)
+include $(BUILD_CTS_HOST_JAVA_LIBRARY)
 
 include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/suite/pts/hostTests/uihost/appA/Android.mk b/suite/pts/hostTests/uihost/appA/Android.mk
index 7dce44c..4d31cb4 100644
--- a/suite/pts/hostTests/uihost/appA/Android.mk
+++ b/suite/pts/hostTests/uihost/appA/Android.mk
@@ -30,6 +30,6 @@
 
 LOCAL_SDK_VERSION := 16
 
-include $(BUILD_PTS_PACKAGE)
+include $(BUILD_CTS_PACKAGE)
 
 
diff --git a/suite/pts/hostTests/uihost/appB/Android.mk b/suite/pts/hostTests/uihost/appB/Android.mk
index caa1090..bb1b5d7 100644
--- a/suite/pts/hostTests/uihost/appB/Android.mk
+++ b/suite/pts/hostTests/uihost/appB/Android.mk
@@ -30,6 +30,6 @@
 
 LOCAL_SDK_VERSION := 16
 
-include $(BUILD_PTS_PACKAGE)
+include $(BUILD_CTS_PACKAGE)
 
 
diff --git a/suite/pts/hostTests/uihost/control/Android.mk b/suite/pts/hostTests/uihost/control/Android.mk
index 86ceb7c..dec5ad1 100644
--- a/suite/pts/hostTests/uihost/control/Android.mk
+++ b/suite/pts/hostTests/uihost/control/Android.mk
@@ -30,4 +30,4 @@
 
 LOCAL_SDK_VERSION := 16
 
-include $(BUILD_PTS_PACKAGE)
+include $(BUILD_CTS_PACKAGE)
diff --git a/suite/pts/hostTests/uihost/control/src/com/android/pts/taskswitching/control/TaskswitchingDeviceTest.java b/suite/pts/hostTests/uihost/control/src/com/android/pts/taskswitching/control/TaskswitchingDeviceTest.java
index fc5bc04..5147eaf 100644
--- a/suite/pts/hostTests/uihost/control/src/com/android/pts/taskswitching/control/TaskswitchingDeviceTest.java
+++ b/suite/pts/hostTests/uihost/control/src/com/android/pts/taskswitching/control/TaskswitchingDeviceTest.java
@@ -27,6 +27,8 @@
 
 import com.android.pts.util.MeasureRun;
 import com.android.pts.util.MeasureTime;
+import com.android.pts.util.ResultType;
+import com.android.pts.util.ResultUnit;
 import com.android.pts.util.PtsAndroidTestCase;
 import com.android.pts.util.Stat;
 
@@ -81,9 +83,11 @@
                 }
             }
         });
-        getReportLog().printArray("ms", results, false);
+        getReportLog().printArray("taskswitching time", results, ResultType.LOWER_BETTER,
+                ResultUnit.MS);
         Stat.StatResult stat = Stat.getStat(results);
-        getReportLog().printSummary("Time ms", stat.mAverage, stat.mStddev);
+        getReportLog().printSummary("taskswitching time", stat.mAverage,
+                ResultType.LOWER_BETTER, ResultUnit.MS);
     }
 
     private void startActivity(String packageName, String activityName) {
diff --git a/suite/pts/hostTests/uihost/src/com/android/pts/uihost/InstallTimeTest.java b/suite/pts/hostTests/uihost/src/com/android/pts/uihost/InstallTimeTest.java
index 4bfcde1..056e0f4 100644
--- a/suite/pts/hostTests/uihost/src/com/android/pts/uihost/InstallTimeTest.java
+++ b/suite/pts/hostTests/uihost/src/com/android/pts/uihost/InstallTimeTest.java
@@ -16,37 +16,26 @@
 
 package com.android.pts.uihost;
 
-import android.cts.util.TimeoutReq;
-
 import com.android.cts.tradefed.build.CtsBuildHelper;
-import com.android.ddmlib.Log;
-import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
-import com.android.ddmlib.testrunner.TestIdentifier;
 import com.android.pts.util.MeasureRun;
 import com.android.pts.util.MeasureTime;
-import com.android.pts.util.PtsException;
+import com.android.pts.util.ResultType;
+import com.android.pts.util.ResultUnit;
 import com.android.pts.util.ReportLog;
 import com.android.pts.util.Stat;
 import com.android.pts.util.Stat.StatResult;
 import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.device.TestDeviceOptions;
-import com.android.tradefed.result.CollectingTestListener;
-import com.android.tradefed.result.TestResult;
-import com.android.tradefed.result.TestRunResult;
 import com.android.tradefed.testtype.DeviceTestCase;
 import com.android.tradefed.testtype.IBuildReceiver;
 
 import java.io.File;
-import java.util.Map;
+
 
 /**
  * Test to measure installation time of a APK.
  */
 public class InstallTimeTest extends DeviceTestCase implements IBuildReceiver {
-    private static final String TAG = "InstallTimeTest";
-    private final static String CTS_RUNNER = "android.test.InstrumentationCtsTestRunner";
     private CtsBuildHelper mBuild;
     private ITestDevice mDevice;
     private ReportLog mReport = null;
@@ -89,9 +78,11 @@
                 device.installPackage(app, false);
             }
         });
-        mReport.printArray("time in ms", result, false);
+        mReport.printArray("install time", result, ResultType.LOWER_BETTER,
+                ResultUnit.MS);
         StatResult stat = Stat.getStat(result);
-        mReport.printSummary("time in ms", stat.mAverage, stat.mStddev);
+        mReport.printSummary("install time", stat.mAverage, ResultType.LOWER_BETTER,
+                ResultUnit.MS);
     }
 
 }
diff --git a/suite/pts/lib/commonutil/Android.mk b/suite/pts/lib/commonutil/Android.mk
index 27e0ba7..152f862 100644
--- a/suite/pts/lib/commonutil/Android.mk
+++ b/suite/pts/lib/commonutil/Android.mk
@@ -35,6 +35,8 @@
 
 LOCAL_MODULE_TAGS := optional
 
+LOCAL_JAVA_LIBRARIES := junit
+
 LOCAL_MODULE := ptscommonutilhost
 
 include $(BUILD_HOST_JAVA_LIBRARY)
\ No newline at end of file
diff --git a/suite/pts/lib/commonutil/src/com/android/pts/util/ReportLog.java b/suite/pts/lib/commonutil/src/com/android/pts/util/ReportLog.java
index 0d69c2f..8a5e624 100644
--- a/suite/pts/lib/commonutil/src/com/android/pts/util/ReportLog.java
+++ b/suite/pts/lib/commonutil/src/com/android/pts/util/ReportLog.java
@@ -19,17 +19,17 @@
 import java.util.LinkedList;
 import java.util.List;
 
+import junit.framework.Assert;
+
 
 /**
  * Utility class to print performance measurement result back to host.
  * For now, throws know exception with message.
  *
  * Format:
- * LOG_SEPARATOR : separates each log
- * Message = log [LOG_SEPARATOR log]*
- * log for single value = classMethodName:line_number|header|d|value
- * log for array = classMethodName:line_number|header|da|values|
- *                     average average_value min|max value stddev value
+ * Message = summary log SUMMARY_SEPARATOR [LOG_SEPARATOR log]*
+ * summary = message|target|unit|type|value, target can be " " if there is no target set.
+ * log for array = classMethodName:line_number|message|unit|type|space seSummaryparated values
  */
 public class ReportLog {
     private static final String LOG_SEPARATOR = "+++";
@@ -39,42 +39,80 @@
     private List<String> mMessages = new LinkedList<String> ();
     private String mSummary = null;
     protected static int mDepth = 3;
+
     /**
-     * print given value to the report
-     * @param header string to explain the contents. It can be unit for the value.
-     * @param val
+     * print array of values to output log
      */
-    public void printValue(String header, double val) {
-        String message = getClassMethodNames(mDepth, true) + LOG_ELEM_SEPARATOR + header +
-                LOG_ELEM_SEPARATOR + "d" + LOG_ELEM_SEPARATOR + val;
-        mMessages.add(message);
-        printLog(message);
+    public void printArray(String message, double[] values, ResultType type,
+            ResultUnit unit) {
+        doPrintArray(message, values, type, unit);
     }
 
     /**
-     * array version of printValue
-     * @param header
-     * @param val
-     * @param addMin add minimum to the result. If false, add maximum to the result
+     * Print a value to output log
      */
-    public void printArray(String header, double[] val, boolean addMin) {
+    public void printValue(String message, double value, ResultType type,
+            ResultUnit unit) {
+        double[] vals = { value };
+        doPrintArray(message, vals, type, unit);
+    }
+
+    private void doPrintArray(String message, double[] values, ResultType type,
+    ResultUnit unit) {
         StringBuilder builder = new StringBuilder();
-        builder.append(getClassMethodNames(mDepth, true) + LOG_ELEM_SEPARATOR + header +
-                LOG_ELEM_SEPARATOR + "da" + LOG_ELEM_SEPARATOR);
-        for (double v : val) {
+        // note mDepth + 1 as this function will be called by printVaue or printArray
+        // and we need caller of printValue / printArray
+        builder.append(getClassMethodNames(mDepth + 1, true) + LOG_ELEM_SEPARATOR + message +
+                LOG_ELEM_SEPARATOR + type.getXmlString() + LOG_ELEM_SEPARATOR +
+                unit.getXmlString() + LOG_ELEM_SEPARATOR);
+        for (double v : values) {
             builder.append(v);
             builder.append(" ");
         }
-        Stat.StatResult stat = Stat.getStat(val);
-        builder.append(LOG_ELEM_SEPARATOR + "average " + stat.mAverage +
-                (addMin ? (" min " + stat.mMin) : (" max " + stat.mMax)) + " stddev " + stat.mStddev);
         mMessages.add(builder.toString());
         printLog(builder.toString());
     }
 
-    public void printSummary(String header, double average, double stddev) {
-        mSummary = header + LOG_ELEM_SEPARATOR + "average " + average + LOG_ELEM_SEPARATOR +
-                "stddev " + stddev;
+    /**
+     * record the result of benchmarking with performance target.
+     * Depending on the ResultType, the function can fail if the result
+     * does not meet the target. For example, for the type of HIGHER_BETTER,
+     * value of 1.0 with target of 2.0 will fail.
+     *
+     * @param message message to be printed in the final report
+     * @param target target performance for the benchmarking
+     * @param value measured value
+     * @param type
+     * @param unit
+     */
+    public void printSummaryWithTarget(String message, double target, double value,
+            ResultType type, ResultUnit unit) {
+        mSummary = message + LOG_ELEM_SEPARATOR + target + LOG_ELEM_SEPARATOR + type.getXmlString()
+                + LOG_ELEM_SEPARATOR + unit.getXmlString() + LOG_ELEM_SEPARATOR + value;
+        boolean resultOk = true;
+        if (type == ResultType.HIGHER_BETTER) {
+            resultOk = value >= target;
+        } else if (type == ResultType.LOWER_BETTER) {
+            resultOk = value <= target;
+        }
+        if (!resultOk) {
+            Assert.fail("Measured result " + value + " does not meet perf target " + target +
+                    " with type " + type.getXmlString());
+        }
+    }
+
+    /**
+     * For standard report summary without target value.
+     * Note that this function will not fail as there is no target.
+     * @param messsage
+     * @param value
+     * @param type type of the value
+     * @param unit unit of the data
+     */
+    public void printSummary(String message, double value, ResultType type,
+            ResultUnit unit) {
+        mSummary = message + LOG_ELEM_SEPARATOR + " " + LOG_ELEM_SEPARATOR + type.getXmlString() +
+                LOG_ELEM_SEPARATOR + unit.getXmlString() + LOG_ELEM_SEPARATOR + value;
     }
 
     public void throwReportToHost() throws PtsException {
@@ -114,9 +152,6 @@
 
     /**
      * array version of calcRatePerSecArray
-     * @param change
-     * @param timeInMSec
-     * @return
      */
     public static double[] calcRatePerSecArray(double change, double[] timeInMSec) {
         double[] result = new double[timeInMSec.length];
@@ -134,9 +169,6 @@
     /**
      * copy array from src to dst with given offset in dst.
      * dst should be big enough to hold src
-     * @param src
-     * @param dst
-     * @param dstOffset
      */
     public static void copyArray(double[] src, double[] dst, int dstOffset) {
         for (int i = 0; i < src.length; i++) {
@@ -146,8 +178,6 @@
 
     /**
      * get classname.methodname from call stack of the current thread
-     *
-     * @return
      */
     public static String getClassMethodNames() {
         return getClassMethodNames(mDepth, false);
@@ -155,14 +185,13 @@
 
     private static String getClassMethodNames(int depth, boolean addLineNumber) {
         StackTraceElement[] elements = Thread.currentThread().getStackTrace();
-        String names = elements[depth].getClassName() + "." + elements[depth].getMethodName() +
+        String names = elements[depth].getClassName() + "#" + elements[depth].getMethodName() +
                 (addLineNumber ? ":" + elements[depth].getLineNumber() : "");
         return names;
     }
 
     /**
      * to be overridden by child to print message to be passed
-     * @param msg
      */
     protected void printLog(String msg) {
 
diff --git a/suite/pts/lib/commonutil/src/com/android/pts/util/ResultType.java b/suite/pts/lib/commonutil/src/com/android/pts/util/ResultType.java
new file mode 100644
index 0000000..cdf448b
--- /dev/null
+++ b/suite/pts/lib/commonutil/src/com/android/pts/util/ResultType.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2012 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.pts.util;
+
+/**
+ * Enum for distinguishing performance results.
+ */
+public enum ResultType {
+    /** lower score shows better performance */
+    LOWER_BETTER,
+    /** higher score shows better performance */
+    HIGHER_BETTER,
+    /** This value is not directly correlated with performance. */
+    NEUTRAL,
+    /** presence of this type requires some attention although it may not be an error. */
+    WARNING;
+
+    /**
+     * Return string used in CTS XML report
+     */
+    public String getXmlString() {
+        return name().toLowerCase();
+    }
+}
diff --git a/suite/pts/lib/commonutil/src/com/android/pts/util/ResultUnit.java b/suite/pts/lib/commonutil/src/com/android/pts/util/ResultUnit.java
new file mode 100644
index 0000000..b234cb5
--- /dev/null
+++ b/suite/pts/lib/commonutil/src/com/android/pts/util/ResultUnit.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2012 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.pts.util;
+
+/**
+ * Enum for representing the unit of performance results.
+ *
+ */
+public enum ResultUnit {
+    /** for value with no unit */
+    NONE,
+    /** milli-seconds */
+    MS,
+    /** frames per second */
+    FPS,
+    /** operations per second */
+    OPS,
+    /** kilo-bytes-per-second, not bits-per-second */
+    KBPS,
+    /** mega-bytes-per-second */
+    MBPS,
+    /** amount of data, bytes */
+    BYTE,
+    /** tell how many times it did happen. */
+    COUNT,
+    /** unit for benchmarking with generic score. */
+    SCORE;
+
+    /**
+     * Return string used in CTS XML report
+     */
+    public String getXmlString() {
+        return name().toLowerCase();
+    }
+}
+
diff --git a/suite/pts/lib/commonutil/src/com/android/pts/util/Stat.java b/suite/pts/lib/commonutil/src/com/android/pts/util/Stat.java
index aae51ec..d56f59e 100644
--- a/suite/pts/lib/commonutil/src/com/android/pts/util/Stat.java
+++ b/suite/pts/lib/commonutil/src/com/android/pts/util/Stat.java
@@ -22,6 +22,9 @@
  */
 public class Stat {
 
+    /**
+     * Collection of statistical propertirs like average, max, min, and stddev
+     */
     public static class StatResult {
         public double mAverage;
         public double mMin;
@@ -35,6 +38,9 @@
         }
     }
 
+    /**
+     * Calculate statistics properties likes average, min, max, and stddev for the given array
+     */
     public static StatResult getStat(double[] data) {
         double average = data[0];
         double min = data[0];
@@ -57,6 +63,20 @@
         return new StatResult(average, min, max, stddev);
     }
 
+    /**
+     * return the average value of the passed array
+     */
+    public static double getAverage(double[] data) {
+        double sum = data[0];
+        for (int i = 1; i < data.length; i++) {
+            sum += data[i];
+        }
+        return sum / data.length;
+    }
+
+    /**
+     * return the minimum value of the passed array
+     */
     public static double getMin(double[] data) {
         double min = data[0];
         for (int i = 1; i < data.length; i++) {
@@ -67,6 +87,9 @@
         return min;
     }
 
+    /**
+     * return the maximum value of the passed array
+     */
     public static double getMax(double[] data) {
         double max = data[0];
         for (int i = 1; i < data.length; i++) {
diff --git a/suite/pts/tools/tradefed/pts-tradefed b/suite/pts/tools/tradefed/pts-tradefed
deleted file mode 100755
index 901ad8c..0000000
--- a/suite/pts/tools/tradefed/pts-tradefed
+++ /dev/null
@@ -1,86 +0,0 @@
-#!/bin/bash
-
-# Copyright (C) 2011 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.
-
-# launcher script for cts-tradefed harness for PTS
-# can be used from an Android build environment, or a standalone pts zip
-
-checkFile() {
-    if [ ! -f "$1" ]; then
-        echo "Unable to locate $1"
-        exit
-    fi;
-}
-
-checkPath() {
-    if ! type -P $1 &> /dev/null; then
-        echo "Unable to find $1 in path."
-        exit
-    fi;
-}
-
-checkPath adb
-checkPath java
-
-# check java version
-JAVA_VERSION=$(java -version 2>&1 | head -n 1 | grep '[ "]1\.6[\. "$$]')
-if [ "${JAVA_VERSION}" == "" ]; then
-    echo "Wrong java version. 1.6 is required."
-    exit
-fi
-
-# check debug flag and set up remote debugging
-if [ -n "${TF_DEBUG}" ]; then
-  if [ -z "${TF_DEBUG_PORT}" ]; then
-    TF_DEBUG_PORT=10088
-  fi
-  RDBG_FLAG=-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=${TF_DEBUG_PORT}
-fi
-
-
-# check if in Android build env
-if [ ! -z ${ANDROID_BUILD_TOP} ]; then
-    HOST=`uname`
-    if [ "$HOST" == "Linux" ]; then
-        OS="linux-x86"
-    elif [ "$HOST" == "Darwin" ]; then
-        OS="darwin-x86"
-    else
-        echo "Unrecognized OS"
-        exit
-    fi;
-    PTS_ROOT=${ANDROID_BUILD_TOP}/out/host/${OS}/pts
-    if [ ! -d ${PTS_ROOT} ]; then
-        echo "Could not find $PTS_ROOT in Android build environment. Try 'make pts'"
-        exit
-    fi;
-fi;
-
-if [ -z ${PTS_ROOT} ]; then
-    # assume we're in an extracted pts install
-    PTS_ROOT="$(dirname $0)/../.."
-fi;
-
-JAR_DIR=${PTS_ROOT}/android-pts/tools
-JARS="ddmlib-prebuilt.jar tradefed-prebuilt.jar hosttestlib.jar cts-tradefed.jar ptscommonutilhost.jar ptshostutil.jar"
-
-for JAR in $JARS; do
-    checkFile ${JAR_DIR}/${JAR}
-    JAR_PATH=${JAR_PATH}:${JAR_DIR}/${JAR}
-done
-
-java $RDBG_FLAG \
-  -cp ${JAR_PATH} -DCTS_ROOT=${PTS_ROOT} -DPTS=1 com.android.cts.tradefed.command.CtsConsole "$@"
-
diff --git a/suite/pts/utils/get_csv_report.py b/suite/pts/utils/get_csv_report.py
index fe3dd74..4c496d2 100755
--- a/suite/pts/utils/get_csv_report.py
+++ b/suite/pts/utils/get_csv_report.py
@@ -1,20 +1,19 @@
 #!/usr/bin/env python
 #
-# Copyright (C) 2012 The Android Open Source Project
+# Copyright (C) 2013 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
+#    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.
-#
-import csv
+
 import os
 import re
 import subprocess
@@ -22,173 +21,191 @@
 from xml.dom import Node
 from xml.dom import minidom
 
-
 def getChildrenWithTag(parent, tagName):
-    children = []
-    for child in  parent.childNodes:
-        if (child.nodeType == Node.ELEMENT_NODE) and (child.tagName == tagName):
-            #print "parent " + parent.getAttribute("name") + " " + tagName +\
-            #    " " + child.getAttribute("name")
-            children.append(child)
-    return children
+  children = []
+  for child in  parent.childNodes:
+    if (child.nodeType == Node.ELEMENT_NODE) and (child.tagName == tagName):
+      #print "parent " + parent.getAttribute("name") + " " + tagName +\
+      #  " " + child.getAttribute("name")
+      children.append(child)
+  return children
+
+def getText(tag):
+  return str(tag.firstChild.nodeValue)
 
 class TestCase(object):
-    def __init__(self, name, average, stddev, passFail):
-        self.name = name
-        self.average = average
-        self.stddev = stddev
-        self.passFail = passFail
+  def __init__(self, name, summary, details, result):
+    self.name = name
+    self.summary = summary
+    self.details = details
+    self.result = result
 
-    def getName(self):
-        return self.name
+  def getName(self):
+    return self.name
 
-    def getStddev(self):
-        return self.stddev
+  def getSummary(self):
+    return self.summary
 
-    def getAverage(self):
-        return self.average
+  def getDetails(self):
+    return self.details
 
-    def getPassFail(self):
-        return self.passFail
+  def getResult(self):
+    return self.result
 
 def parseSuite(suite, parentName):
-    if parentName != "":
-        parentName += '.'
-    cases = {}
-    childSuites = getChildrenWithTag(suite, "TestSuite")
-    for child in childSuites:
-        cases.update(parseSuite(child, parentName + child.getAttribute("name")))
-    childTestCases = getChildrenWithTag(suite, "TestCase")
-    for child in childTestCases:
-        className = parentName + child.getAttribute("name")
-        for test in getChildrenWithTag(child, "Test"):
-            methodName = test.getAttribute("name")
-            # do not include this
-            if methodName == "testAndroidTestCaseSetupProperly":
-                continue
-            caseName = className + "#" + methodName
-            passFail = test.getAttribute("result")
-            average = ""
-            stddev = ""
-            failedScene = getChildrenWithTag(test, "FailedScene")
-            if len(failedScene) > 0:
-                message = failedScene[0].getAttribute("message")
-                #print message
-                messages = message.split('|')
-                if len(messages) > 2:
-                    average = messages[1].split()[1]
-                    stddev = messages[2].split()[1]
-            testCase = TestCase(caseName, average, stddev, passFail)
-            cases[caseName] = testCase
-    return cases
+  if parentName != "":
+    parentName += '.'
+  cases = {}
+  childSuites = getChildrenWithTag(suite, "TestSuite")
+  for child in childSuites:
+    cases.update(parseSuite(child, parentName + child.getAttribute("name")))
+  childTestCases = getChildrenWithTag(suite, "TestCase")
+  for child in childTestCases:
+    className = parentName + child.getAttribute("name")
+    for test in getChildrenWithTag(child, "Test"):
+      methodName = test.getAttribute("name")
+      # do not include this
+      if methodName == "testAndroidTestCaseSetupProperly":
+        continue
+      caseName = str(className + "#" + methodName)
+      result = str(test.getAttribute("result"))
+      summary = {}
+      details = {}
+      if result == "pass":
+        sts = getChildrenWithTag(test, "Summary")
+        dts = getChildrenWithTag(test, "Details")
+        if len(sts) == len(dts) == 1:
+          summary[sts[0].getAttribute("message")] = getText(sts[0])
+          for d in getChildrenWithTag(dts[0], "ValueArray"):
+            values = []
+            for c in getChildrenWithTag(d, "Value"):
+              values.append(getText(c))
+            details[d.getAttribute("message")] = values
+        else:
+          result = "no results"
+      testCase = TestCase(caseName, summary, details, result)
+      cases[caseName] = testCase
+  return cases
 
 
 class Result(object):
-    def __init__(self, reportXml):
-        self.results = {}
-        self.infoKeys = []
-        self.infoValues = []
-        doc = minidom.parse(reportXml)
-        testResult = doc.getElementsByTagName("TestResult")[0]
-        buildInfo = testResult.getElementsByTagName("BuildInfo")[0]
-        buildId = buildInfo.getAttribute("buildID")
-        deviceId = buildInfo.getAttribute("deviceID")
-        deviceName = buildInfo.getAttribute("build_device")
-        boardName = buildInfo.getAttribute("build_board")
-        partitions = buildInfo.getAttribute("partitions")
-        m = re.search(r'.*;/data\s+(\w+)\s+(\w+)\s+(\w+)\s+(\w+);', partitions)
-        dataPartitionSize = m.group(1)
-        self.addKV("device", deviceName)
-        self.addKV("board", boardName)
-        self.addKV("serial", deviceId)
-        self.addKV("build", buildId)
-        self.addKV("data size", dataPartitionSize)
-        packages = getChildrenWithTag(testResult, "TestPackage")
-        for package in packages:
-            casesFromChild = parseSuite(package, "")
-            self.results.update(casesFromChild)
-        #print self.results.keys()
+  def __init__(self, reportXml):
+    self.results = {}
+    self.infoKeys = []
+    self.infoValues = []
+    doc = minidom.parse(reportXml)
+    testResult = doc.getElementsByTagName("TestResult")[0]
+    buildInfo = testResult.getElementsByTagName("BuildInfo")[0]
+    buildId = buildInfo.getAttribute("buildID")
+    deviceId = buildInfo.getAttribute("deviceID")
+    deviceName = buildInfo.getAttribute("build_device")
+    boardName = buildInfo.getAttribute("build_board")
+    partitions = buildInfo.getAttribute("partitions")
+    m = re.search(r'.*;/data\s+([\w\.]+)\s+([\w\.]+)\s+([\w\.]+)\s+([\w\.]+);', partitions)
+    dataPartitionSize = m.group(1)
+    self.addKV("device", deviceName)
+    self.addKV("board", boardName)
+    self.addKV("serial", deviceId)
+    self.addKV("build", buildId)
+    self.addKV("data size", dataPartitionSize)
+    packages = getChildrenWithTag(testResult, "TestPackage")
+    for package in packages:
+      casesFromChild = parseSuite(package, "")
+      self.results.update(casesFromChild)
+    #print self.results.keys()
 
-    def addKV(self, key, value):
-        self.infoKeys.append(key)
-        self.infoValues.append(value)
+  def addKV(self, key, value):
+    self.infoKeys.append(key)
+    self.infoValues.append(value)
 
-    def getResults(self):
-        return self.results
+  def getResults(self):
+    return self.results
 
-    def getKeys(self):
-        return self.infoKeys
+  def getKeys(self):
+    return self.infoKeys
 
-    def getValues(self):
-        return self.infoValues
+  def getValues(self):
+    return self.infoValues
 
 def executeWithResult(command):
-    p = subprocess.Popen(command.split(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
-    out, err = p.communicate()
-    return out
+  p = subprocess.Popen(command.split(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+  out, err = p.communicate()
+  return out
+
+def parseReports(path):
+  deviceResults = []
+  xmls = executeWithResult("find " + path + " -name testResult.xml -print")
+  print "xml files found :"
+  print xmls
+  for xml in xmls.splitlines():
+    result = Result(xml)
+    deviceResults.append(result)
+  reportInfo = {}
+  keys = deviceResults[0].getKeys()
+  noDevices = len(deviceResults)
+  for i in xrange(len(keys)):
+    values = []
+    for j in xrange(noDevices):
+      values.append(str(deviceResults[j].getValues()[i]))
+    reportInfo[keys[i]] = values
+  #print reportInfo
+
+  tests = []
+  for deviceResult in deviceResults:
+    for key in deviceResult.getResults().keys():
+      if not key in tests:
+        tests.append(key)
+  tests.sort()
+  #print tests
+
+  reportTests = {}
+  for i in xrange(len(tests)):
+    test = tests[i]
+    reportTests[test] = []
+    for j in xrange(noDevices):
+      values = {}
+      if deviceResults[j].getResults().has_key(test):
+        result = deviceResults[j].getResults()[test]
+        values["result"] = result.getResult()
+        values["summary"] = result.getSummary()
+        values["details"] = result.getDetails()
+        values["device"] = deviceResults[j].getValues()[0]
+        reportTests[test].append(values)
+
+  #print reportTests
+  return (reportInfo, reportTests)
 
 def main(argv):
-    if len(argv) < 3:
-        print "get_csv_report.py pts_report_dir output_file"
-        sys.exit(1)
-    reportPath = os.path.abspath(argv[1])
-    outputCsv = os.path.abspath(argv[2])
+  if len(argv) < 3:
+    print "get_csv_report.py pts_report_dir output_file"
+    sys.exit(1)
+  reportPath = os.path.abspath(argv[1])
+  outputCsv = os.path.abspath(argv[2])
 
-    deviceResults = []
-    xmls = executeWithResult("find " + reportPath + " -name testResult.xml -print")
-    print "xml files found :"
-    print xmls
-    for xml in xmls.splitlines():
-        result = Result(xml)
-        deviceResults.append(result)
-    reportInfo = []
-    keys = deviceResults[0].getKeys()
-    noDevices = len(deviceResults)
-    for i in xrange(len(keys)):
-        reportInfo.append([])
-        reportInfo[i].append(keys[i])
-        # for worst/average
-        reportInfo[i].append("")
-        for j in xrange(noDevices):
-            reportInfo[i].append(deviceResults[j].getValues()[i])
-    #print reportInfo
+  (reportInfo, reportTests) = parseReports(reportPath)
 
-    tests = []
-    for deviceResult in deviceResults:
-        for key in deviceResult.getResults().keys():
-            if not key in tests:
-                tests.append(key)
-    tests.sort()
-    #print tests
-
-    reportTests = []
-    for i in xrange(len(tests)):
-        reportTests.append([])
-        reportTests.append([])
-        reportTests[2 * i].append(tests[i])
-        reportTests[2 * i + 1].append(tests[i])
-        reportTests[2 * i].append("average")
-        reportTests[2 * i + 1].append("stddev")
-        for j in xrange(noDevices):
-            if deviceResults[j].getResults().has_key(tests[i]):
-                result = deviceResults[j].getResults()[tests[i]]
-                if result.getPassFail() == "pass":
-                    reportTests[2 * i].append(result.getAverage())
-                    reportTests[2 * i + 1].append(result.getStddev())
-                else:
-                    reportTests[2 * i].append("fail")
-                    reportTests[2 * i + 1].append("fail")
-            else:
-                reportTests[2 * i].append("")
-                reportTests[2 * i + 1].append("")
-
-    #print reportTests
-
-    with open(outputCsv, 'wb') as f:
-        writer = csv.writer(f)
-        writer.writerows(reportInfo)
-        writer.writerows(reportTests)
-
+  with open(outputCsv, 'w') as f:
+    for key in reportInfo:
+      f.write(key)
+      for value in reportInfo[key]:
+        f.write(',')
+        f.write(value)
+      f.write('\n')
+    for test in reportTests:
+      for report in reportTests[test]:
+        if report.has_key('result'):
+          result = report['result'] 
+          f.write(test)
+          f.write(',')
+          f.write(result)
+          for key in report['summary']:
+            f.write(',')
+            f.write(report['summary'][key])
+          for key in report['details']:
+            for value in report['details'][key]:
+              f.write(',')
+              f.write(value)
+          f.write('\n')
 
 if __name__ == '__main__':
-    main(sys.argv)
+  main(sys.argv)
diff --git a/suite/pts/utils/grapher.py b/suite/pts/utils/grapher.py
new file mode 100755
index 0000000..42b5f11
--- /dev/null
+++ b/suite/pts/utils/grapher.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2013 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.
+
+import os, sys
+import get_csv_report as psr
+import matplotlib.pyplot as plt
+import matplotlib.mlab as mlab
+import matplotlib.cbook as cbook
+import matplotlib.ticker as ticker
+"""
+A simple script to render the data from the benchmark as a graph.
+This uses MatPlotLib (http://matplotlib.org/) to plot which can be installed on linux with;
+  sudo apt-get install python-matplotlib
+"""
+
+def main(argv):
+  if len(argv) != 2:
+    print "grapher.py pts_report_dir"
+    sys.exit(1)
+
+  (_, tests) = psr.parseReports(os.path.abspath(argv[1]))
+
+  # For each of the benchmarks
+  for benchmark in tests:
+    if benchmark.startswith('com.android.pts.opengl'):
+      results = tests[benchmark]
+      legend = []
+      # Create a new figure
+      fig = plt.figure()
+      # Set the title of the graph
+      plt.title(benchmark)
+      # For each result in the data set
+      for r in results:
+        score = r['result']
+        x = []
+        y = []
+        if score == 'pass':
+          y = r['details']['Fps Values']
+          x = range(1, len(y) + 1)
+          # Get the score, then trim it to 2 decimal places
+          score = r['summary']['Score']
+          score = score[0:score.index('.') + 3]
+        if score != 'no results':
+          # Create a plot
+          ax = fig.add_subplot(111)
+          # Plot the workload vs the values
+          ax.plot(x, y, 'o-', label=r['device'] + ' (%s)'%score)
+          # Add a legend
+          ax.legend(loc='upper right').get_frame().set_fill(False)
+      plt.xlabel('Iteration')
+      plt.ylabel('FPS')
+      fig.autofmt_xdate()
+  # Show the plots
+  plt.show()
+
+if __name__ == '__main__':
+  main(sys.argv)
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index 590ee36..6a07ef8 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -244,6 +244,14 @@
             </intent-filter>
         </activity>
 
+        <activity android:name="android.widget.cts.GridLayoutStubActivity"
+            android:label="GridLayoutStubActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+            </intent-filter>
+        </activity>
+
         <activity android:name="android.widget.cts.LayoutDirectionStubActivity"
             android:label="LayoutDirectionStubActivity">
             <intent-filter>
@@ -419,6 +427,14 @@
             </intent-filter>
         </activity>
 
+        <activity android:name="android.text.cts.EmojiStubActivity"
+            android:label="AvailableIntentsActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+            </intent-filter>
+        </activity>
+
         <activity android:name="android.widget.cts.TableStubActivity"
             android:label="TableStubActivity">
             <intent-filter>
diff --git a/tests/SignatureTest/Android.mk b/tests/SignatureTest/Android.mk
index cc0d53c..0b51f25 100644
--- a/tests/SignatureTest/Android.mk
+++ b/tests/SignatureTest/Android.mk
@@ -33,7 +33,11 @@
 # To be passed in on command line
 CTS_API_VERSION ?= current
 
+ifeq (current,$(CTS_API_VERSION))
+android_api_description := frameworks/base/api/$(CTS_API_VERSION).txt
+else
 android_api_description := $(SRC_API_DIR)/$(CTS_API_VERSION).txt
+endif
 
 # Can't call local-intermediates-dir directly here because we have to
 # include BUILD_PACAKGE first.  Can't include BUILD_PACKAGE first
@@ -41,75 +45,50 @@
 # hack.
 intermediates.COMMON := $(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),,COMMON)
 signature_res_dir := $(intermediates.COMMON)/genres
-LOCAL_RESOURCE_DIR := $(signature_res_dir)
-
-# These shell commands allow us to get around the package.mk check for
-# empty/non-existent resouce dirs (which ours would be).  If it finds
-# an empty/non-existent resource dir, R_file_stamp doesn't contain a
-# target and our stuff never gets copied.
-# TODO: fix package.mk so we can avoid this hack
-fake_resource_check := $(signature_res_dir)/raw/fake_resource_check
-
-$(shell \
- if [ ! -f $(fake_resource_check) ]; then \
-   mkdir -p $(dir $(fake_resource_check)); \
-   touch $(fake_resource_check); \
- fi \
- )
+LOCAL_RESOURCE_DIR := $(signature_res_dir) $(LOCAL_PATH)/res
 
 include $(BUILD_PACKAGE)
 
-copied_res_stamp := $(intermediates.COMMON)/copyres.stamp
 generated_res_stamp := $(intermediates.COMMON)/genres.stamp
-api_ver_path := $(intermediates.COMMON)
-api_ver_file := $(api_ver_path)/api_ver_is_$(CTS_API_VERSION)
+api_ver_file := $(intermediates.COMMON)/api_ver_is_$(CTS_API_VERSION)
 
 # The api_ver_file keeps track of which api version was last built.
 # By only ever having one of these magic files in existance and making
 # sure the generated resources rule depend on it, we can ensure that
 # the proper version of the api resource gets generated.
 $(api_ver_file):
-	$(hide) rm -f $(api_ver_path)/api_ver_is_*
-	$(hide) touch $@
+	$(hide) rm -f $(dir $@)/api_ver_is_* \
+		&& mkdir -p $(dir $@) && touch $@
 
 android_api_xml_description := $(intermediates.COMMON)/api.xml
-$(android_api_xml_description): PRIVATE_INPUT_FILE := $(android_api_description)
-$(android_api_xml_description): $(android_api_description) $(APICHECK)
-	$(hide) echo "Convert api file to xml: $@"
-	$(hide) $(APICHECK_COMMAND) -convert2xml $(PRIVATE_INPUT_FILE) $@
-
-static_res_deps := $(call find-subdir-assets,$(LOCAL_PATH)/res)
-$(copied_res_stamp): PRIVATE_PATH := $(LOCAL_PATH)
-$(copied_res_stamp): PRIVATE_MODULE := $(LOCAL_MODULE)
-$(copied_res_stamp): PRIVATE_RES_DIR := $(signature_res_dir)
-$(copied_res_stamp): FAKE_RESOURCE_DIR := $(dir $(fake_resource_check))
-$(copied_res_stamp): FAKE_RESOURCE_CHECK := $(fake_resource_check)
-$(copied_res_stamp): $(foreach res,$(static_res_deps),$(LOCAL_PATH)/res/${res}) | $(ACP)
-	$(hide) echo "Copy resources: $(PRIVATE_MODULE)"
-	$(hide) rm -f $@
-	$(hide) rm -rf $(PRIVATE_RES_DIR)
-	$(hide) mkdir -p $(PRIVATE_RES_DIR)
-	$(hide) if [ ! -f $(FAKE_RESOURCE_CHECK) ]; \
-	  then mkdir -p $(FAKE_RESOURCE_DIR); \
-	  touch $(FAKE_RESOURCE_CHECK); \
-	fi
-	$(hide) $(ACP) -rd $(PRIVATE_PATH)/res/* $(PRIVATE_RES_DIR)/
-	$(hide) touch $@
+$(android_api_xml_description): $(android_api_description) | $(APICHECK)
+	@ echo "Convert api file to xml: $@"
+	@ mkdir -p $(dir $@)
+	$(hide) $(APICHECK_COMMAND) -convert2xml $< $@
 
 # Split up config/api/1.xml by "package" and save the files as the
 # resource files of SignatureTest.
 $(generated_res_stamp): PRIVATE_PATH := $(LOCAL_PATH)
 $(generated_res_stamp): PRIVATE_MODULE := $(LOCAL_MODULE)
 $(generated_res_stamp): PRIVATE_RES_DIR := $(signature_res_dir)
+$(generated_res_stamp): PRIVATE_API_XML_DESC := $(android_api_xml_description)
 $(generated_res_stamp): $(api_ver_file)
-$(generated_res_stamp): $(copied_res_stamp) $(android_api_xml_description)
-	$(hide) echo "Copy generated resources: $(PRIVATE_MODULE)"
-	$(hide) rm -f $@
+$(generated_res_stamp): $(android_api_xml_description)
+	@ echo "Copy generated resources: $(PRIVATE_MODULE)"
 	$(hide) python cts/tools/utils/android_api_description_splitter.py \
-		$(android_api_xml_description) $(PRIVATE_RES_DIR) package
+		$(PRIVATE_API_XML_DESC) $(PRIVATE_RES_DIR) package
 	$(hide) touch $@
 
-$(R_file_stamp): $(generated_res_stamp) $(copied_res_stamp)
+$(R_file_stamp): $(generated_res_stamp)
+
+# clean up temp vars
+android_api_xml_description :=
+api_ver_file :=
+generated_res_stamp :=
+signature_res_dir :=
+android_api_description :=
+CTS_API_VERSION :=
+
 
 # Use the folloing include to make our test apk.
 include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/accessibility/AndroidManifest.xml b/tests/accessibility/AndroidManifest.xml
index dde1de8..0d18cef 100644
--- a/tests/accessibility/AndroidManifest.xml
+++ b/tests/accessibility/AndroidManifest.xml
@@ -19,6 +19,8 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.view.accessibility.services">
 
+  <uses-permission android:name="android.permission.CAN_REQUEST_TOUCH_EXPLORATION_MODE"/>
+
   <application>
 
     <service android:name=".SpeakingAccessibilityService"
diff --git a/tests/accessibility/res/xml/speaking_accessibilityservice.xml b/tests/accessibility/res/xml/speaking_accessibilityservice.xml
index 630940b..d43d3e7 100644
--- a/tests/accessibility/res/xml/speaking_accessibilityservice.xml
+++ b/tests/accessibility/res/xml/speaking_accessibilityservice.xml
@@ -16,5 +16,5 @@
 <accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
     android:accessibilityEventTypes="typeAllMask"
     android:accessibilityFeedbackType="feedbackSpoken"
-    android:accessibilityFlags="flagDefault"
+    android:accessibilityFlags="flagDefault|flagRequestTouchExplorationMode"
     android:canRetrieveWindowContent="true" />
diff --git a/tests/accessibility/res/xml/vibrating_accessibilityservice.xml b/tests/accessibility/res/xml/vibrating_accessibilityservice.xml
index 4ccec76..c2f8799 100644
--- a/tests/accessibility/res/xml/vibrating_accessibilityservice.xml
+++ b/tests/accessibility/res/xml/vibrating_accessibilityservice.xml
@@ -14,7 +14,7 @@
      limitations under the License.
 -->
 <accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
-    android:accessibilityEventTypes="typeAllMask"
+    android:accessibilityEventTypes="typeAllMask|"
     android:accessibilityFeedbackType="feedbackHaptic"
-    android:accessibilityFlags="flagDefault"
+    android:accessibilityFlags="flagDefault|flagRequestTouchExplorationMode"
     android:canRetrieveWindowContent="true" />
diff --git a/tests/accessibilityservice/AndroidManifest.xml b/tests/accessibilityservice/AndroidManifest.xml
index 805f697..117578e 100644
--- a/tests/accessibilityservice/AndroidManifest.xml
+++ b/tests/accessibilityservice/AndroidManifest.xml
@@ -19,6 +19,8 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.accessibilityservice.delegate">
 
+  <uses-permission android:name="android.permission.CAN_REQUEST_TOUCH_EXPLORATION_MODE"/>
+
   <application>
 
     <uses-library android:name="android.test.runner"/>
diff --git a/tests/accessibilityservice/res/xml/accessibilityservice.xml b/tests/accessibilityservice/res/xml/accessibilityservice.xml
index 395d022..69e0651 100644
--- a/tests/accessibilityservice/res/xml/accessibilityservice.xml
+++ b/tests/accessibilityservice/res/xml/accessibilityservice.xml
@@ -16,7 +16,7 @@
 <accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
     android:accessibilityEventTypes="typeAllMask"
     android:accessibilityFeedbackType="feedbackGeneric"
-    android:accessibilityFlags="flagDefault"
+    android:accessibilityFlags="flagDefault|flagRequestTouchExplorationMode"
     android:canRetrieveWindowContent="true"
     android:notificationTimeout="50"
     android:settingsActivity="android.accessibilityservice.delegate.SomeActivity"
diff --git a/tests/assets/webkit/jswindow.html b/tests/assets/webkit/jswindow.html
index 534a683..7075d6e 100644
--- a/tests/assets/webkit/jswindow.html
+++ b/tests/assets/webkit/jswindow.html
@@ -23,7 +23,6 @@
             childWindow = window.open();
             childWindow.document.title = "javascript child window";
             childWindow.document.write("javascript child window");
-            childWindow.focus();
             setTimeout(function(){childWindow.close();}, 2000);
         }
     </script>
diff --git a/tests/assets/webkit/network_state.html b/tests/assets/webkit/network_state.html
index 20358f4..4712b83 100644
--- a/tests/assets/webkit/network_state.html
+++ b/tests/assets/webkit/network_state.html
@@ -26,6 +26,6 @@
             }
         </script>
     </head>
-    <body onload="getNetworkState()">
+    <body onload="getNetworkState()" ononline="getNetworkState()" onoffline="getNetworkState()">
     </body>
 </html>
diff --git a/tests/assets/webkit/test_databaseaccess.html b/tests/assets/webkit/test_databaseaccess.html
new file mode 100644
index 0000000..dcf7b098
--- /dev/null
+++ b/tests/assets/webkit/test_databaseaccess.html
@@ -0,0 +1,27 @@
+<!-- Copyright (C) 2013 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.
+-->
+
+<html>
+    <head><title>None</title><script>
+        function checkDatabase() {
+            if ('openDatabase' in window) {
+                var db = window.openDatabase('test', '1.0', 'results', 1*1024*1024);
+            }
+            document.title = db ? "Has database" : "No database";
+        }
+    </script></head>
+    <body onload='checkDatabase()'>
+    </body>
+</html>
diff --git a/tests/assets/webkit/test_firstPage.html b/tests/assets/webkit/test_firstPage.html
index 4a2f9b0..18a5548 100644
--- a/tests/assets/webkit/test_firstPage.html
+++ b/tests/assets/webkit/test_firstPage.html
@@ -17,6 +17,6 @@
     <title>First page</title>
     <body>
         <h1>First page</h1>
-        <a herf="webView_goBackAndForward_test_secondPage.html">Go to second page</a>
+        <a href="webView_goBackAndForward_test_secondPage.html">Go to second page</a>
     </body>
 </html>
diff --git a/tests/assets/webkit/test_secondPage.html b/tests/assets/webkit/test_secondPage.html
index 952d43f..ea8e2a1 100644
--- a/tests/assets/webkit/test_secondPage.html
+++ b/tests/assets/webkit/test_secondPage.html
@@ -17,6 +17,6 @@
     <title>Second page</title>
     <body>
         <h1>Second page</h1>
-        <a herf="webView_goBackAndForward_test_thirdPage.html">Go to third page</a>
+        <a href="webView_goBackAndForward_test_thirdPage.html">Go to third page</a>
     </body>
 </html>
diff --git a/tests/tests/openglperf/res/values/strings.xml b/tests/assets/webkit/test_stop_loading.html
similarity index 70%
rename from tests/tests/openglperf/res/values/strings.xml
rename to tests/assets/webkit/test_stop_loading.html
index 2cdba7f..1f0fc72 100644
--- a/tests/tests/openglperf/res/values/strings.xml
+++ b/tests/assets/webkit/test_stop_loading.html
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 The Android Open Source Project
+<!-- Copyright (C) 2013 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.
@@ -13,6 +12,12 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<resources>
-    <string name="app_name">OpenGlPerfTest</string>
-</resources>
\ No newline at end of file
+
+<html>
+    <head>
+        <title>Stop Loading test</title>
+    </head>
+    <body onload="window.javabridge.pageLoaded()">
+        <h3>hello world!</h3><br>
+    </body>
+</html>
diff --git a/tests/expectations/knownfailures.txt b/tests/expectations/knownfailures.txt
index 1992b11..d7ef39f 100644
--- a/tests/expectations/knownfailures.txt
+++ b/tests/expectations/knownfailures.txt
@@ -2,5 +2,9 @@
 {
   name: "android.openglperf.cts.GlVboPerfTest#testVboWithVaryingIndexBufferNumbers",
   bug: 6950385
+},
+{
+  name: "android.holo.cts.HoloTest",
+  bug: 8148617
 }
 ]
diff --git a/tests/res/layout/gridlayout_layout.xml b/tests/res/layout/gridlayout_layout.xml
new file mode 100644
index 0000000..54b3b2c
--- /dev/null
+++ b/tests/res/layout/gridlayout_layout.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2012 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.
+ -->
+<GridLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    >
+</GridLayout>
diff --git a/tests/res/layout/viewtreeobserver_layout.xml b/tests/res/layout/viewtreeobserver_layout.xml
index 461a4bc..66e98b2 100644
--- a/tests/res/layout/viewtreeobserver_layout.xml
+++ b/tests/res/layout/viewtreeobserver_layout.xml
@@ -20,17 +20,19 @@
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content">
 
-    <ListView android:id="@+id/listview1"
+    <View android:id="@+id/view1"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"/>
+        android:layout_height="10dip"
+        android:focusable="true"/>
 
-    <ListView android:id="@+id/listview2"
+    <View android:id="@+id/view2"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"/>
+        android:layout_height="10dip"
+        android:focusable="true"/>
 
     <Button android:id="@+id/button1"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content" />
+        android:layout_width="wrap_content"
+        android:layout_height="10dip" />
 
 </LinearLayout>
 
diff --git a/tests/res/raw/set_target_api_11.bc b/tests/res/raw/set_target_api_11.bc
index babbd9e..fa04be6 100644
--- a/tests/res/raw/set_target_api_11.bc
+++ b/tests/res/raw/set_target_api_11.bc
Binary files differ
diff --git a/tests/res/raw/set_target_api_12.bc b/tests/res/raw/set_target_api_12.bc
index 92ee8de..abe314f 100644
--- a/tests/res/raw/set_target_api_12.bc
+++ b/tests/res/raw/set_target_api_12.bc
Binary files differ
diff --git a/tests/res/raw/set_target_api_13.bc b/tests/res/raw/set_target_api_13.bc
index c842b98..4fa5b75 100644
--- a/tests/res/raw/set_target_api_13.bc
+++ b/tests/res/raw/set_target_api_13.bc
Binary files differ
diff --git a/tests/res/raw/set_target_api_14.bc b/tests/res/raw/set_target_api_14.bc
index 24d2481..876b37c 100644
--- a/tests/res/raw/set_target_api_14.bc
+++ b/tests/res/raw/set_target_api_14.bc
Binary files differ
diff --git a/tests/res/raw/set_target_api_15.bc b/tests/res/raw/set_target_api_15.bc
index a6e86d4..d2de6d9 100644
--- a/tests/res/raw/set_target_api_15.bc
+++ b/tests/res/raw/set_target_api_15.bc
Binary files differ
diff --git a/tests/res/raw/set_target_api_16.bc b/tests/res/raw/set_target_api_16.bc
index 5ded6fd..ca9aa65 100644
--- a/tests/res/raw/set_target_api_16.bc
+++ b/tests/res/raw/set_target_api_16.bc
Binary files differ
diff --git a/tests/res/raw/set_target_api_17.bc b/tests/res/raw/set_target_api_17.bc
new file mode 100644
index 0000000..b08b389
--- /dev/null
+++ b/tests/res/raw/set_target_api_17.bc
Binary files differ
diff --git a/tests/res/raw/set_target_api_too_high.bc b/tests/res/raw/set_target_api_too_high.bc
new file mode 100644
index 0000000..95b2026
--- /dev/null
+++ b/tests/res/raw/set_target_api_too_high.bc
Binary files differ
diff --git a/tests/src/android/provider/cts/MediaStoreAudioTestHelper.java b/tests/src/android/provider/cts/MediaStoreAudioTestHelper.java
index 7bcd0c7..a87cd3a 100644
--- a/tests/src/android/provider/cts/MediaStoreAudioTestHelper.java
+++ b/tests/src/android/provider/cts/MediaStoreAudioTestHelper.java
@@ -221,6 +221,60 @@
         }
     }
 
+    public static class Audio3 extends Audio1 {
+        private Audio3() {
+        }
+
+        private static Audio3 sInstance = new Audio3();
+
+        public static Audio3 getInstance() {
+            return sInstance;
+        }
+
+        @Override
+        public ContentValues getContentValues(boolean isInternal) {
+            ContentValues values = super.getContentValues(isInternal);
+            values.put(Media.DATA, values.getAsString(Media.DATA) + "_3");
+            return values;
+        }
+    }
+
+    public static class Audio4 extends Audio1 {
+        private Audio4() {
+        }
+
+        private static Audio4 sInstance = new Audio4();
+
+        public static Audio4 getInstance() {
+            return sInstance;
+        }
+
+        @Override
+        public ContentValues getContentValues(boolean isInternal) {
+            ContentValues values = super.getContentValues(isInternal);
+            values.put(Media.DATA, values.getAsString(Media.DATA) + "_4");
+            return values;
+        }
+    }
+
+    public static class Audio5 extends Audio1 {
+        private Audio5() {
+        }
+
+        private static Audio5 sInstance = new Audio5();
+
+        public static Audio5 getInstance() {
+            return sInstance;
+        }
+
+        @Override
+        public ContentValues getContentValues(boolean isInternal) {
+            ContentValues values = super.getContentValues(isInternal);
+            values.put(Media.DATA, values.getAsString(Media.DATA) + "_5");
+            return values;
+        }
+    }
+
     // These constants are not part of the public API
     public static final String EXTERNAL_VOLUME_NAME = "external";
     public static final String INTERNAL_VOLUME_NAME = "internal";
diff --git a/tests/src/android/renderscript/cts/foreach.rs b/tests/src/android/renderscript/cts/foreach.rs
index ac527b5..8747961 100644
--- a/tests/src/android/renderscript/cts/foreach.rs
+++ b/tests/src/android/renderscript/cts/foreach.rs
@@ -1,6 +1,7 @@
 #include "shared.rsh"
 
 int *a;
+rs_allocation aRaw;
 int dimX;
 int dimY;
 static bool failed = false;
@@ -21,7 +22,8 @@
 
     for (j = 0; j < dimY; j++) {
         for (i = 0; i < dimX; i++) {
-            _RS_ASSERT(a[i + j * dimX] == (i + j * dimX));
+            int v = rsGetElementAt_int(aRaw, i, j);
+            _RS_ASSERT(v == (i + j * dimX));
         }
     }
 
@@ -41,7 +43,8 @@
 
     for (j = 0; j < dimY; j++) {
         for (i = 0; i < dimX; i++) {
-            _RS_ASSERT(a[i + j * dimX] == (99 + i + j * dimX));
+            int v = rsGetElementAt_int(aRaw, i, j);
+            _RS_ASSERT(v == (99 + i + j * dimX));
         }
     }
 
diff --git a/tests/src/android/renderscript/cts/instance.rs b/tests/src/android/renderscript/cts/instance.rs
new file mode 100644
index 0000000..097098a
--- /dev/null
+++ b/tests/src/android/renderscript/cts/instance.rs
@@ -0,0 +1,10 @@
+#include "shared.rsh"
+
+int i;
+rs_allocation ai;
+
+void instance_test() {
+    // Set our allocation based on the global input value.
+    rsSetElementAt(ai, &i, 0);
+}
+
diff --git a/tests/src/android/renderscript/cts/noroot.rs b/tests/src/android/renderscript/cts/noroot.rs
index 33944aa..f69effc 100644
--- a/tests/src/android/renderscript/cts/noroot.rs
+++ b/tests/src/android/renderscript/cts/noroot.rs
@@ -1,6 +1,7 @@
 #include "shared.rsh"
 
 int *a;
+rs_allocation aRaw;
 int dimX;
 int dimY;
 static bool failed = false;
@@ -15,7 +16,8 @@
 
     for (j = 0; j < dimY; j++) {
         for (i = 0; i < dimX; i++) {
-            _RS_ASSERT(a[i + j * dimX] == (99 + i + j * dimX));
+            int v = rsGetElementAt_int(aRaw, i, j);
+            _RS_ASSERT(v == (99 + i + j * dimX));
         }
     }
 
diff --git a/tests/src/android/renderscript/cts/setelementat.rs b/tests/src/android/renderscript/cts/setelementat.rs
new file mode 100644
index 0000000..75e96f5
--- /dev/null
+++ b/tests/src/android/renderscript/cts/setelementat.rs
@@ -0,0 +1,51 @@
+#pragma version(1)
+#pragma rs java_package_name(android.renderscript.cts)
+
+#include "shared.rsh"
+
+int memset_toValue = 0;
+
+int compare_value = 0;
+int compare_failure = 2;
+int failure_value = 0;
+
+uint32_t dimX = 0;
+uint32_t dimY = 0;
+rs_allocation array;
+
+void memset(int *aout) {
+    *aout = memset_toValue;
+    return;
+}
+
+void compare(const int *ain) {
+    if (*ain != compare_value) {
+        rsAtomicCas(&compare_failure, 2, -1);
+        failure_value = *ain;
+    }
+    return;
+}
+
+void getCompareResult(int* aout) {
+    *aout = compare_failure;
+}
+
+void setLargeArray(const int *ain, uint32_t x) {
+    int source = 10;
+    if (x == 0) {
+        for (uint32_t i = 0; i < dimX; i++) {
+            rsSetElementAt(array, &source, i);
+        }
+    }
+}
+
+void setLargeArray2D(const int *ain, uint32_t x) {
+    int source = 10;
+    if (x == 0) {
+        for (uint32_t y = 0; y < dimY; y++) {
+            for (uint32_t xtemp = 0; xtemp < dimX; xtemp++) {
+                rsSetElementAt(array, &source, xtemp, y);
+            }
+        }
+    }
+}
diff --git a/tests/src/android/text/EmojiStubActivity.java b/tests/src/android/text/EmojiStubActivity.java
new file mode 100755
index 0000000..1587c94
--- /dev/null
+++ b/tests/src/android/text/EmojiStubActivity.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2013 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.text.cts;
+
+import com.android.cts.stub.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.webkit.WebView;
+
+public class EmojiStubActivity extends Activity {
+    private WebView mWebView;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.webview_layout);
+        mWebView = (WebView) findViewById(R.id.web_page);
+    }
+
+    public WebView getWebView() {
+        return mWebView;
+    }
+
+    @Override
+    public void onDestroy() {
+        mWebView.destroy();
+        super.onDestroy();
+    }
+}
diff --git a/tests/src/android/webkit/cts/WebViewOnUiThread.java b/tests/src/android/webkit/cts/WebViewOnUiThread.java
index bb07d08..cbda733 100644
--- a/tests/src/android/webkit/cts/WebViewOnUiThread.java
+++ b/tests/src/android/webkit/cts/WebViewOnUiThread.java
@@ -341,6 +341,24 @@
         });
     }
 
+    public void loadUrl(final String url) {
+        runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mWebView.loadUrl(url);
+            }
+        });
+    }
+
+    public void stopLoading() {
+        runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mWebView.stopLoading();
+            }
+        });
+    }
+
     public void loadDataAndWaitForCompletion(final String data,
             final String mimeType, final String encoding) {
         callAndWait(new Runnable() {
diff --git a/tests/src/android/widget/cts/GridLayoutStubActivity.java b/tests/src/android/widget/cts/GridLayoutStubActivity.java
new file mode 100644
index 0000000..3fa0f28
--- /dev/null
+++ b/tests/src/android/widget/cts/GridLayoutStubActivity.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2012 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.widget.cts;
+
+import android.app.Activity;
+import android.os.Bundle;
+import com.android.cts.stub.R;
+
+/**
+ * A minimal application for {@link android.widget.GridLayout} test.
+ */
+public class GridLayoutStubActivity extends Activity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.gridlayout_layout);
+    }
+}
diff --git a/tests/tests/accessibility/src/android/view/accessibility/cts/AccessibilityEventTest.java b/tests/tests/accessibility/src/android/view/accessibility/cts/AccessibilityEventTest.java
index 9b46a9b..58b32d6 100644
--- a/tests/tests/accessibility/src/android/view/accessibility/cts/AccessibilityEventTest.java
+++ b/tests/tests/accessibility/src/android/view/accessibility/cts/AccessibilityEventTest.java
@@ -31,7 +31,7 @@
 public class AccessibilityEventTest extends TestCase {
 
     /** The number of properties of the {@link AccessibilityEvent} class. */
-    private static final int NON_STATIC_FIELD_COUNT = 30;
+    private static final int NON_STATIC_FIELD_COUNT = 28;
 
     /**
      * Test that no new fields have been added without updating the
@@ -253,7 +253,6 @@
                 ((Message) expectedEvent.getParcelableData()).what,
                 ((Message) receivedEvent.getParcelableData()).what);
 
-        AccessibilityRecord expectedRecord = expectedEvent.getRecord(0);
         AccessibilityRecord receivedRecord = receivedEvent.getRecord(0);
         AccessibilityRecordTest.assertEqualAccessibilityRecord(expectedEvent, receivedRecord);
     }
diff --git a/tests/tests/accessibility/src/android/view/accessibility/cts/AccessibilityManagerTest.java b/tests/tests/accessibility/src/android/view/accessibility/cts/AccessibilityManagerTest.java
index 28e26eb..7ac0572 100644
--- a/tests/tests/accessibility/src/android/view/accessibility/cts/AccessibilityManagerTest.java
+++ b/tests/tests/accessibility/src/android/view/accessibility/cts/AccessibilityManagerTest.java
@@ -52,10 +52,6 @@
         assertEquals("Accessibility should have been enabled by the test runner.",
                 1, Settings.Secure.getInt(mContext.getContentResolver(),
                         Settings.Secure.ACCESSIBILITY_ENABLED));
-
-        assertEquals("Touch exploration should have been enabled by the test runner.",
-                1, Settings.Secure.getInt(mContext.getContentResolver(),
-                        Settings.Secure.TOUCH_EXPLORATION_ENABLED));
     }
 
     public void testAddAndRemoveAccessibilityStateChangeListener() throws Exception {
diff --git a/tests/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java b/tests/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java
index e0ffaf7..70a25b2 100644
--- a/tests/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java
+++ b/tests/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java
@@ -30,7 +30,7 @@
 public class AccessibilityNodeInfoTest extends AndroidTestCase {
 
     /** The number of properties of the {@link AccessibilityNodeInfo} class. */
-    private static final int NON_STATIC_FIELD_COUNT = 19;
+    private static final int NON_STATIC_FIELD_COUNT = 20;
 
     @SmallTest
     public void testMarshaling() throws Exception {
@@ -124,6 +124,7 @@
         info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
         info.setLabeledBy(new View(getContext()));
         info.setLabelFor(new View(getContext()));
+        info.setViewIdResourceName("foo.bar:id/baz");
     }
 
     /**
@@ -180,6 +181,8 @@
         assertSame("movementGranularities has incorrect value",
                 expectedInfo.getMovementGranularities(),
                 receivedInfo.getMovementGranularities());
+        assertEquals("viewId has incorrect value", expectedInfo.getViewIdResourceName(),
+                receivedInfo.getViewIdResourceName());
     }
 
     /**
@@ -211,5 +214,6 @@
         assertFalse("accessibilityFocused not properly recycled", info.isAccessibilityFocused());
         assertSame("movementGranularities not properly recycled", 0,
                 info.getMovementGranularities());
+        assertNull("viewId not properly recycled", info.getViewIdResourceName());
     }
 }
diff --git a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityServiceInfoTest.java b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityServiceInfoTest.java
index 0f687d0..1d28470 100644
--- a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityServiceInfoTest.java
+++ b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityServiceInfoTest.java
@@ -110,7 +110,9 @@
         AccessibilityServiceInfo speakingService = enabledServices.get(0);
         assertSame(AccessibilityEvent.TYPES_ALL_MASK, speakingService.eventTypes);
         assertSame(AccessibilityServiceInfo.FEEDBACK_GENERIC, speakingService.feedbackType);
-        assertSame(AccessibilityServiceInfo.DEFAULT, speakingService.flags);
+        assertSame(AccessibilityServiceInfo.DEFAULT
+                | AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE,
+                speakingService.flags);
         assertSame(50l, speakingService.notificationTimeout);
         assertEquals("Delegating Accessibility Service", speakingService.getDescription());
         assertNull(speakingService.packageNames /*all packages*/);
diff --git a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextTraversalTest.java b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextTraversalTest.java
index 517b71a..17c7ebd 100644
--- a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextTraversalTest.java
+++ b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextTraversalTest.java
@@ -17,6 +17,7 @@
 import android.os.Bundle;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.text.Selection;
+import android.text.TextUtils;
 import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
@@ -86,7 +87,7 @@
                 (event.getEventType() ==
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
-                            AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                                AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
                         && event.getPackageName().equals(getActivity().getPackageName())
                         && event.getClassName().equals(View.class.getName())
                         && event.getContentDescription().toString().equals(
@@ -94,7 +95,7 @@
                         && event.getFromIndex() == 0
                         && event.getToIndex() == 1
                         && event.getMovementGranularity() ==
-                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
             }
         }, TIMEOUT_ASYNC_PROCESSING);
 
@@ -116,7 +117,7 @@
                 (event.getEventType() ==
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
-                            AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                                AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
                         && event.getPackageName().equals(getActivity().getPackageName())
                         && event.getClassName().equals(View.class.getName())
                         && event.getContentDescription().toString().equals(
@@ -124,7 +125,7 @@
                         && event.getFromIndex() == 1
                         && event.getToIndex() == 2
                         && event.getMovementGranularity() ==
-                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
             }
         }, TIMEOUT_ASYNC_PROCESSING);
 
@@ -146,7 +147,7 @@
                 (event.getEventType() ==
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
-                            AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                                AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
                         && event.getPackageName().equals(getActivity().getPackageName())
                         && event.getClassName().equals(View.class.getName())
                         && event.getContentDescription().toString().equals(
@@ -154,7 +155,7 @@
                         && event.getFromIndex() == 2
                         && event.getToIndex() == 3
                         && event.getMovementGranularity() ==
-                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
             }
         }, TIMEOUT_ASYNC_PROCESSING);
 
@@ -165,7 +166,37 @@
         assertFalse(getInteractionBridge().performAction(text,
                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments));
 
-        // Move to the next character and wait for an event.
+        // Move to the previous character and wait for an event.
+        AccessibilityEvent fourthExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(View.class.getName())
+                        && event.getContentDescription().toString().equals(
+                                getString(R.string.a_b))
+                        && event.getFromIndex() == 2
+                        && event.getToIndex() == 3
+                        && event.getMovementGranularity() ==
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(fourthExpected);
+
+        // Move to the previous character and wait for an event.
         AccessibilityEvent fifthExpected = getInteractionBridge()
                 .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
             @Override
@@ -180,7 +211,7 @@
                 (event.getEventType() ==
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
-                            AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
                         && event.getPackageName().equals(getActivity().getPackageName())
                         && event.getClassName().equals(View.class.getName())
                         && event.getContentDescription().toString().equals(
@@ -188,7 +219,7 @@
                         && event.getFromIndex() == 1
                         && event.getToIndex() == 2
                         && event.getMovementGranularity() ==
-                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
             }
         }, TIMEOUT_ASYNC_PROCESSING);
 
@@ -210,7 +241,7 @@
                 (event.getEventType() ==
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
-                            AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
                         && event.getPackageName().equals(getActivity().getPackageName())
                         && event.getClassName().equals(View.class.getName())
                         && event.getContentDescription().toString().equals(
@@ -218,7 +249,7 @@
                         && event.getFromIndex() == 0
                         && event.getToIndex() == 1
                         && event.getMovementGranularity() ==
-                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
             }
         }, TIMEOUT_ASYNC_PROCESSING);
 
@@ -269,7 +300,7 @@
                 (event.getEventType() ==
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
-                            AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                                AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
                         && event.getPackageName().equals(getActivity().getPackageName())
                         && event.getClassName().equals(View.class.getName())
                         && event.getContentDescription().toString().equals(
@@ -277,7 +308,7 @@
                         && event.getFromIndex() == 0
                         && event.getToIndex() == 3
                         && event.getMovementGranularity() ==
-                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
             }
         }, TIMEOUT_ASYNC_PROCESSING);
 
@@ -299,7 +330,7 @@
                 (event.getEventType() ==
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
-                            AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                                AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
                         && event.getPackageName().equals(getActivity().getPackageName())
                         && event.getClassName().equals(View.class.getName())
                         && event.getContentDescription().toString().equals(
@@ -307,7 +338,7 @@
                         && event.getFromIndex() == 4
                         && event.getToIndex() == 7
                         && event.getMovementGranularity() ==
-                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
             }
         }, TIMEOUT_ASYNC_PROCESSING);
 
@@ -329,7 +360,7 @@
                 (event.getEventType() ==
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
-                            AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                                AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
                         && event.getPackageName().equals(getActivity().getPackageName())
                         && event.getClassName().equals(View.class.getName())
                         && event.getContentDescription().toString().equals(
@@ -337,7 +368,7 @@
                         && event.getFromIndex() == 8
                         && event.getToIndex() == 11
                         && event.getMovementGranularity() ==
-                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+                               AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
             }
         }, TIMEOUT_ASYNC_PROCESSING);
 
@@ -363,7 +394,7 @@
                 (event.getEventType() ==
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
-                            AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
                         && event.getPackageName().equals(getActivity().getPackageName())
                         && event.getClassName().equals(View.class.getName())
                         && event.getContentDescription().toString().equals(
@@ -371,7 +402,7 @@
                         && event.getFromIndex() == 8
                         && event.getToIndex() == 11
                         && event.getMovementGranularity() ==
-                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
             }
         }, TIMEOUT_ASYNC_PROCESSING);
 
@@ -393,7 +424,7 @@
                 (event.getEventType() ==
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
-                            AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
                         && event.getPackageName().equals(getActivity().getPackageName())
                         && event.getClassName().equals(View.class.getName())
                         && event.getContentDescription().toString().equals(
@@ -401,7 +432,7 @@
                         && event.getFromIndex() == 4
                         && event.getToIndex() == 7
                         && event.getMovementGranularity() ==
-                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
             }
         }, TIMEOUT_ASYNC_PROCESSING);
 
@@ -423,7 +454,7 @@
                 (event.getEventType() ==
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
-                            AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
                         && event.getPackageName().equals(getActivity().getPackageName())
                         && event.getClassName().equals(View.class.getName())
                         && event.getContentDescription().toString().equals(
@@ -431,7 +462,7 @@
                         && event.getFromIndex() == 0
                         && event.getToIndex() == 3
                         && event.getMovementGranularity() ==
-                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
             }
         }, TIMEOUT_ASYNC_PROCESSING);
 
@@ -484,7 +515,7 @@
                 (event.getEventType() ==
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
-                            AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                                AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
                         && event.getPackageName().equals(getActivity().getPackageName())
                         && event.getClassName().equals(TextView.class.getName())
                         && event.getText().size() > 0
@@ -492,7 +523,7 @@
                         && event.getFromIndex() == 0
                         && event.getToIndex() == 1
                         && event.getMovementGranularity() ==
-                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
             }
         }, TIMEOUT_ASYNC_PROCESSING);
 
@@ -518,7 +549,7 @@
                 (event.getEventType() ==
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
-                            AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                                AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
                         && event.getPackageName().equals(getActivity().getPackageName())
                         && event.getClassName().equals(TextView.class.getName())
                         && event.getText().size() > 0
@@ -526,7 +557,7 @@
                         && event.getFromIndex() == 1
                         && event.getToIndex() == 2
                         && event.getMovementGranularity() ==
-                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
             }
         }, TIMEOUT_ASYNC_PROCESSING);
 
@@ -552,7 +583,7 @@
                 (event.getEventType() ==
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
-                            AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                                AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
                         && event.getPackageName().equals(getActivity().getPackageName())
                         && event.getClassName().equals(TextView.class.getName())
                         && event.getText().size() > 0
@@ -560,7 +591,7 @@
                         && event.getFromIndex() == 2
                         && event.getToIndex() == 3
                         && event.getMovementGranularity() ==
-                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
             }
         }, TIMEOUT_ASYNC_PROCESSING);
 
@@ -579,7 +610,7 @@
         assertEquals(3, Selection.getSelectionStart(textView.getText()));
         assertEquals(3, Selection.getSelectionEnd(textView.getText()));
 
-        // Move to the next character and wait for an event.
+        // Move to the previous character and wait for an event.
         AccessibilityEvent fifthExpected = getInteractionBridge()
                 .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
             @Override
@@ -594,15 +625,15 @@
                 (event.getEventType() ==
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
-                            AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
                         && event.getPackageName().equals(getActivity().getPackageName())
                         && event.getClassName().equals(TextView.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(R.string.a_b))
-                        && event.getFromIndex() == 1
-                        && event.getToIndex() == 2
+                        && event.getFromIndex() == 2
+                        && event.getToIndex() == 3
                         && event.getMovementGranularity() ==
-                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
             }
         }, TIMEOUT_ASYNC_PROCESSING);
 
@@ -613,7 +644,7 @@
         assertEquals(2, Selection.getSelectionStart(textView.getText()));
         assertEquals(2, Selection.getSelectionEnd(textView.getText()));
 
-        // Move to the next character and wait for an event.
+        // Move to the previous character and wait for an event.
         AccessibilityEvent sixthExpected = getInteractionBridge()
                 .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
             @Override
@@ -628,15 +659,15 @@
                 (event.getEventType() ==
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
-                            AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
                         && event.getPackageName().equals(getActivity().getPackageName())
                         && event.getClassName().equals(TextView.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(R.string.a_b))
-                        && event.getFromIndex() == 0
-                        && event.getToIndex() == 1
+                        && event.getFromIndex() == 1
+                        && event.getToIndex() == 2
                         && event.getMovementGranularity() ==
-                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
             }
         }, TIMEOUT_ASYNC_PROCESSING);
 
@@ -647,6 +678,40 @@
         assertEquals(1, Selection.getSelectionStart(textView.getText()));
         assertEquals(1, Selection.getSelectionEnd(textView.getText()));
 
+        // Move to the previous character and wait for an event.
+        AccessibilityEvent seventhExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(TextView.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(R.string.a_b))
+                        && event.getFromIndex() == 0
+                        && event.getToIndex() == 1
+                        && event.getMovementGranularity() ==
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(seventhExpected);
+
+        // Verify the selection position.
+        assertEquals(0, Selection.getSelectionStart(textView.getText()));
+        assertEquals(0, Selection.getSelectionEnd(textView.getText()));
+
         // Make sure there is no previous.
         assertFalse(getInteractionBridge().performAction(text,
                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments));
@@ -657,6 +722,284 @@
     }
 
     @MediumTest
+    public void testActionNextAndPreviousAtGranularityCharacterOverTextExtend()
+            throws Exception {
+        final EditText editText = (EditText) getActivity().findViewById(R.id.edit);
+
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                editText.setText(getString(R.string.a_b));
+                Selection.removeSelection(editText.getText());
+            }
+        });
+
+        final AccessibilityNodeInfo text = getInteractionBridge()
+               .findAccessibilityNodeInfoByTextFromRoot(getString(R.string.a_b));
+
+        final int granularities = text.getMovementGranularities();
+        assertEquals(granularities, AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
+                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
+                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
+                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
+                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+
+        final Bundle arguments = new Bundle();
+        arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
+                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+        arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN, true);
+
+        // Move to the next character and wait for an event.
+        AccessibilityEvent firstExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                                AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(EditText.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(R.string.a_b))
+                        && event.getFromIndex() == 0
+                        && event.getToIndex() == 1
+                        && event.getMovementGranularity() ==
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(firstExpected);
+
+        // Verify the selection position.
+        assertEquals(0, Selection.getSelectionStart(editText.getText()));
+        assertEquals(1, Selection.getSelectionEnd(editText.getText()));
+
+        // Move to the next character and wait for an event.
+        AccessibilityEvent secondExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                                AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(EditText.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(R.string.a_b))
+                        && event.getFromIndex() == 1
+                        && event.getToIndex() == 2
+                        && event.getMovementGranularity() ==
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(secondExpected);
+
+        // Verify the selection position.
+        assertEquals(0, Selection.getSelectionStart(editText.getText()));
+        assertEquals(2, Selection.getSelectionEnd(editText.getText()));
+
+        // Move to the next character and wait for an event.
+        AccessibilityEvent thirdExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                                AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(EditText.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(R.string.a_b))
+                        && event.getFromIndex() == 2
+                        && event.getToIndex() == 3
+                        && event.getMovementGranularity() ==
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(thirdExpected);
+
+        // Verify the selection position.
+        assertEquals(0, Selection.getSelectionStart(editText.getText()));
+        assertEquals(3, Selection.getSelectionEnd(editText.getText()));
+
+        // Make sure there is no next.
+        assertFalse(getInteractionBridge().performAction(text,
+                AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments));
+
+        // Verify the selection position.
+        assertEquals(0, Selection.getSelectionStart(editText.getText()));
+        assertEquals(3, Selection.getSelectionEnd(editText.getText()));
+
+        // Focus the view so we can change selection.
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                editText.setFocusable(true);
+                editText.requestFocus();
+            }
+        });
+
+        // Put selection at the end of the text.
+        Bundle setSelectionArgs = new Bundle();
+        setSelectionArgs.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 3);
+        setSelectionArgs.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, 3);
+        assertTrue(getInteractionBridge().performAction(text,
+                AccessibilityNodeInfo.ACTION_SET_SELECTION, setSelectionArgs));
+
+        // Verify the selection position.
+        assertEquals(3, Selection.getSelectionStart(editText.getText()));
+        assertEquals(3, Selection.getSelectionEnd(editText.getText()));
+
+        // Unfocus the view so we can get rid of the soft-keyboard.
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                editText.clearFocus();
+                editText.setFocusable(false);
+            }
+        });
+
+        // Move to the previous character and wait for an event.
+        AccessibilityEvent fifthExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(EditText.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(R.string.a_b))
+                        && event.getFromIndex() == 2
+                        && event.getToIndex() == 3
+                        && event.getMovementGranularity() ==
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(fifthExpected);
+
+        // Verify the selection position.
+        assertEquals(2, Selection.getSelectionStart(editText.getText()));
+        assertEquals(3, Selection.getSelectionEnd(editText.getText()));
+
+        // Move to the previous character and wait for an event.
+        AccessibilityEvent sixthExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(EditText.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(R.string.a_b))
+                        && event.getFromIndex() == 1
+                        && event.getToIndex() == 2
+                        && event.getMovementGranularity() ==
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(sixthExpected);
+
+        // Verify the selection position.
+        assertEquals(1, Selection.getSelectionStart(editText.getText()));
+        assertEquals(3, Selection.getSelectionEnd(editText.getText()));
+
+        // Move to the previous character and wait for an event.
+        AccessibilityEvent seventhExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(EditText.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(R.string.a_b))
+                        && event.getFromIndex() == 0
+                        && event.getToIndex() == 1
+                        && event.getMovementGranularity() ==
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(seventhExpected);
+
+        // Verify the selection position.
+        assertEquals(0, Selection.getSelectionStart(editText.getText()));
+        assertEquals(3, Selection.getSelectionEnd(editText.getText()));
+
+        // Make sure there is no previous.
+        assertFalse(getInteractionBridge().performAction(text,
+                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments));
+
+        // Verify the selection position.
+        assertEquals(0, Selection.getSelectionStart(editText.getText()));
+        assertEquals(3, Selection.getSelectionEnd(editText.getText()));
+    }
+
+    @MediumTest
     public void testActionNextAndPreviousAtGranularityWordOverText() throws Exception {
         final TextView textView = (TextView) getActivity().findViewById(R.id.text);
 
@@ -696,7 +1039,7 @@
                 (event.getEventType() ==
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
-                            AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                                AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
                         && event.getPackageName().equals(getActivity().getPackageName())
                         && event.getClassName().equals(TextView.class.getName())
                         && event.getText().size() > 0
@@ -704,7 +1047,7 @@
                         && event.getFromIndex() == 0
                         && event.getToIndex() == 3
                         && event.getMovementGranularity() ==
-                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
             }
         }, TIMEOUT_ASYNC_PROCESSING);
 
@@ -730,7 +1073,7 @@
                 (event.getEventType() ==
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
-                            AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                                AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
                         && event.getPackageName().equals(getActivity().getPackageName())
                         && event.getClassName().equals(TextView.class.getName())
                         && event.getText().size() > 0
@@ -738,7 +1081,7 @@
                         && event.getFromIndex() == 4
                         && event.getToIndex() == 7
                         && event.getMovementGranularity() ==
-                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
             }
         }, TIMEOUT_ASYNC_PROCESSING);
 
@@ -764,7 +1107,7 @@
                 (event.getEventType() ==
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
-                            AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                                AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
                         && event.getPackageName().equals(getActivity().getPackageName())
                         && event.getClassName().equals(TextView.class.getName())
                         && event.getText().size() > 0
@@ -772,7 +1115,7 @@
                         && event.getFromIndex() == 8
                         && event.getToIndex() == 11
                         && event.getMovementGranularity() ==
-                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
             }
         }, TIMEOUT_ASYNC_PROCESSING);
 
@@ -806,7 +1149,7 @@
                 (event.getEventType() ==
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
-                            AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
                         && event.getPackageName().equals(getActivity().getPackageName())
                         && event.getClassName().equals(TextView.class.getName())
                         && event.getText().size() > 0
@@ -814,7 +1157,7 @@
                         && event.getFromIndex() == 8
                         && event.getToIndex() == 11
                         && event.getMovementGranularity() ==
-                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
             }
         }, TIMEOUT_ASYNC_PROCESSING);
 
@@ -840,7 +1183,7 @@
                 (event.getEventType() ==
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
-                            AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
                         && event.getPackageName().equals(getActivity().getPackageName())
                         && event.getClassName().equals(TextView.class.getName())
                         && event.getText().size() > 0
@@ -848,7 +1191,7 @@
                         && event.getFromIndex() == 4
                         && event.getToIndex() == 7
                         && event.getMovementGranularity() ==
-                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
             }
         }, TIMEOUT_ASYNC_PROCESSING);
 
@@ -874,7 +1217,7 @@
                 (event.getEventType() ==
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
-                            AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
                         && event.getPackageName().equals(getActivity().getPackageName())
                         && event.getClassName().equals(TextView.class.getName())
                         && event.getText().size() > 0
@@ -882,7 +1225,7 @@
                         && event.getFromIndex() == 0
                         && event.getToIndex() == 3
                         && event.getMovementGranularity() ==
-                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
             }
         }, TIMEOUT_ASYNC_PROCESSING);
 
@@ -903,6 +1246,279 @@
     }
 
     @MediumTest
+    public void testActionNextAndPreviousAtGranularityWordOverTextExtend() throws Exception {
+        final EditText editText = (EditText) getActivity().findViewById(R.id.edit);
+
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                editText.setText(getString(R.string.foo_bar_baz));
+                Selection.removeSelection(editText.getText());
+            }
+        });
+
+        final AccessibilityNodeInfo text = getInteractionBridge()
+               .findAccessibilityNodeInfoByTextFromRoot(getString(R.string.foo_bar_baz));
+
+        final int granularities = text.getMovementGranularities();
+        assertEquals(granularities, AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
+                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
+                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
+                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
+                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+
+        final Bundle arguments = new Bundle();
+        arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
+                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+        arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN, true);
+
+        // Move to the next word and wait for an event.
+        AccessibilityEvent firstExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                                AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(EditText.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(R.string.foo_bar_baz))
+                        && event.getFromIndex() == 0
+                        && event.getToIndex() == 3
+                        && event.getMovementGranularity() ==
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(firstExpected);
+
+        // Verify the selection position.
+        assertEquals(0, Selection.getSelectionStart(editText.getText()));
+        assertEquals(3, Selection.getSelectionEnd(editText.getText()));
+
+        // Move to the next word and wait for an event.
+        AccessibilityEvent secondExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                                AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(EditText.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(R.string.foo_bar_baz))
+                        && event.getFromIndex() == 4
+                        && event.getToIndex() == 7
+                        && event.getMovementGranularity() ==
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(secondExpected);
+
+        // Verify the selection position.
+        assertEquals(0, Selection.getSelectionStart(editText.getText()));
+        assertEquals(7, Selection.getSelectionEnd(editText.getText()));
+
+        // Move to the next word and wait for an event.
+        AccessibilityEvent thirdExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                                AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(EditText.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(R.string.foo_bar_baz))
+                        && event.getFromIndex() == 8
+                        && event.getToIndex() == 11
+                        && event.getMovementGranularity() ==
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(thirdExpected);
+
+        // Verify the selection position.
+        assertEquals(0, Selection.getSelectionStart(editText.getText()));
+        assertEquals(11, Selection.getSelectionEnd(editText.getText()));
+
+        // Make sure there is no next.
+        assertFalse(getInteractionBridge().performAction(text,
+                AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments));
+
+        // Focus the view so we can change selection.
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                editText.setFocusable(true);
+                editText.requestFocus();
+            }
+        });
+
+        // Put selection at the end of the text.
+        Bundle setSelectionArgs = new Bundle();
+        setSelectionArgs.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 11);
+        setSelectionArgs.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, 11);
+        assertTrue(getInteractionBridge().performAction(text,
+                AccessibilityNodeInfo.ACTION_SET_SELECTION, setSelectionArgs));
+
+        // Verify the selection position.
+        assertEquals(11, Selection.getSelectionStart(editText.getText()));
+        assertEquals(11, Selection.getSelectionEnd(editText.getText()));
+
+        // Unfocus the view so we can get rid of the soft-keyboard.
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                editText.clearFocus();
+                editText.setFocusable(false);
+            }
+        });
+
+        // Move to the next word and wait for an event.
+        AccessibilityEvent fourthExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(EditText.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(R.string.foo_bar_baz))
+                        && event.getFromIndex() == 8
+                        && event.getToIndex() == 11
+                        && event.getMovementGranularity() ==
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(fourthExpected);
+
+        // Verify the selection position.
+        assertEquals(8, Selection.getSelectionStart(editText.getText()));
+        assertEquals(11, Selection.getSelectionEnd(editText.getText()));
+
+        // Move to the next word and wait for an event.
+        AccessibilityEvent fifthExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(EditText.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(R.string.foo_bar_baz))
+                        && event.getFromIndex() == 4
+                        && event.getToIndex() == 7
+                        && event.getMovementGranularity() ==
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(fifthExpected);
+
+        // Verify the selection position.
+        assertEquals(4, Selection.getSelectionStart(editText.getText()));
+        assertEquals(11, Selection.getSelectionEnd(editText.getText()));
+
+        // Move to the next character and wait for an event.
+        AccessibilityEvent sixthExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(EditText.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(R.string.foo_bar_baz))
+                        && event.getFromIndex() == 0
+                        && event.getToIndex() == 3
+                        && event.getMovementGranularity() ==
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(sixthExpected);
+
+        // Verify the selection position.
+        assertEquals(0, Selection.getSelectionStart(editText.getText()));
+        assertEquals(11, Selection.getSelectionEnd(editText.getText()));
+
+        // Make sure there is no previous.
+        assertFalse(getInteractionBridge().performAction(text,
+                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments));
+
+        // Verify the selection position.
+        assertEquals(0, Selection.getSelectionStart(editText.getText()));
+        assertEquals(11, Selection.getSelectionEnd(editText.getText()));
+    }
+
+    @MediumTest
     public void testActionNextAndPreviousAtGranularityLineOverText() throws Exception {
         final TextView textView = (TextView) getActivity().findViewById(R.id.text);
 
@@ -942,7 +1558,7 @@
                 (event.getEventType() ==
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
-                            AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                                AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
                         && event.getPackageName().equals(getActivity().getPackageName())
                         && event.getClassName().equals(TextView.class.getName())
                         && event.getText().size() > 0
@@ -951,7 +1567,7 @@
                         && event.getFromIndex() == 0
                         && event.getToIndex() == 25
                         && event.getMovementGranularity() ==
-                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
             }
         }, TIMEOUT_ASYNC_PROCESSING);
 
@@ -977,7 +1593,7 @@
                 (event.getEventType() ==
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
-                            AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                                AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
                         && event.getPackageName().equals(getActivity().getPackageName())
                         && event.getClassName().equals(TextView.class.getName())
                         && event.getText().size() > 0
@@ -986,7 +1602,7 @@
                         && event.getFromIndex() == 25
                         && event.getToIndex() == 53
                         && event.getMovementGranularity() ==
-                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
+                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
             }
         }, TIMEOUT_ASYNC_PROCESSING);
 
@@ -1012,7 +1628,7 @@
                 (event.getEventType() ==
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
-                            AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                                AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
                         && event.getPackageName().equals(getActivity().getPackageName())
                         && event.getClassName().equals(TextView.class.getName())
                         && event.getText().size() > 0
@@ -1021,7 +1637,7 @@
                         && event.getFromIndex() == 53
                         && event.getToIndex() == 60
                         && event.getMovementGranularity() ==
-                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
             }
         }, TIMEOUT_ASYNC_PROCESSING);
 
@@ -1055,7 +1671,7 @@
                 (event.getEventType() ==
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
-                            AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
                         && event.getPackageName().equals(getActivity().getPackageName())
                         && event.getClassName().equals(TextView.class.getName())
                         && event.getText().size() > 0
@@ -1064,7 +1680,7 @@
                         && event.getFromIndex() == 53
                         && event.getToIndex() == 60
                         && event.getMovementGranularity() ==
-                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
             }
         }, TIMEOUT_ASYNC_PROCESSING);
 
@@ -1090,7 +1706,7 @@
                 (event.getEventType() ==
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
-                            AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
                         && event.getPackageName().equals(getActivity().getPackageName())
                         && event.getClassName().equals(TextView.class.getName())
                         && event.getText().size() > 0
@@ -1099,7 +1715,7 @@
                         && event.getFromIndex() == 25
                         && event.getToIndex() == 53
                         && event.getMovementGranularity() ==
-                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
             }
         }, TIMEOUT_ASYNC_PROCESSING);
 
@@ -1125,7 +1741,7 @@
                 (event.getEventType() ==
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
-                            AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
                         && event.getPackageName().equals(getActivity().getPackageName())
                         && event.getClassName().equals(TextView.class.getName())
                         && event.getText().size() > 0
@@ -1134,7 +1750,7 @@
                         && event.getFromIndex() == 0
                         && event.getToIndex() == 25
                         && event.getMovementGranularity() ==
-                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
             }
         }, TIMEOUT_ASYNC_PROCESSING);
 
@@ -1155,6 +1771,281 @@
     }
 
     @MediumTest
+    public void testActionNextAndPreviousAtGranularityLineOverTextExtend() throws Exception {
+        final EditText editText = (EditText) getActivity().findViewById(R.id.edit);
+
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                editText.setText(getString(R.string.android_wiki_short));
+                Selection.removeSelection(editText.getText());
+            }
+        });
+
+        final AccessibilityNodeInfo text = getInteractionBridge()
+               .findAccessibilityNodeInfoByTextFromRoot(getString(R.string.android_wiki_short));
+
+        final int granularities = text.getMovementGranularities();
+        assertEquals(granularities, AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
+                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
+                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
+                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
+                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+
+        final Bundle arguments = new Bundle();
+        arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
+                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
+        arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN, true);
+
+        // Move to the next line and wait for an event.
+        AccessibilityEvent firstExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                                AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(EditText.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(
+                                R.string.android_wiki_short))
+                        && event.getFromIndex() == 0
+                        && event.getToIndex() == 19
+                        && event.getMovementGranularity() ==
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(firstExpected);
+
+        // Verify the selection position.
+        assertEquals(0, Selection.getSelectionStart(editText.getText()));
+        assertEquals(19, Selection.getSelectionEnd(editText.getText()));
+
+        // Move to the next line and wait for an event.
+        AccessibilityEvent secondExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                                AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(EditText.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(
+                                R.string.android_wiki_short))
+                        && event.getFromIndex() == 19
+                        && event.getToIndex() == 35
+                        && event.getMovementGranularity() ==
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(secondExpected);
+
+        // Verify the selection position.
+        assertEquals(0, Selection.getSelectionStart(editText.getText()));
+        assertEquals(35, Selection.getSelectionEnd(editText.getText()));
+
+        // Move to the next line and wait for an event.
+        AccessibilityEvent thirdExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                                AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(EditText.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(
+                                R.string.android_wiki_short))
+                        && event.getFromIndex() == 35
+                        && event.getToIndex() == 53
+                        && event.getMovementGranularity() ==
+                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(thirdExpected);
+
+        // Verify the selection position.
+        assertEquals(0, Selection.getSelectionStart(editText.getText()));
+        assertEquals(53, Selection.getSelectionEnd(editText.getText()));
+
+        // Focus the view so we can change selection.
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                editText.setFocusable(true);
+                editText.requestFocus();
+            }
+        });
+
+        // Put selection at the end of the text.
+        Bundle setSelectionArgs = new Bundle();
+        setSelectionArgs.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 53);
+        setSelectionArgs.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, 53);
+        assertTrue(getInteractionBridge().performAction(text,
+                AccessibilityNodeInfo.ACTION_SET_SELECTION, setSelectionArgs));
+
+        // Verify the selection position.
+        assertEquals(53, Selection.getSelectionStart(editText.getText()));
+        assertEquals(53, Selection.getSelectionEnd(editText.getText()));
+
+        // Unocus the view so we can hide the keyboard.
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                editText.clearFocus();
+                editText.setFocusable(false);
+            }
+        });
+
+        // Move to the previous line and wait for an event.
+        AccessibilityEvent fourthExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(EditText.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(
+                                R.string.android_wiki_short))
+                        && event.getFromIndex() == 35
+                        && event.getToIndex() == 53
+                        && event.getMovementGranularity() ==
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(fourthExpected);
+
+        // Verify the selection position.
+        assertEquals(35, Selection.getSelectionStart(editText.getText()));
+        assertEquals(53, Selection.getSelectionEnd(editText.getText()));
+
+        // Move to the previous line and wait for an event.
+        AccessibilityEvent fifthExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(EditText.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(
+                                R.string.android_wiki_short))
+                        && event.getFromIndex() == 19
+                        && event.getToIndex() == 35
+                        && event.getMovementGranularity() ==
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(fifthExpected);
+
+        // Verify the selection position.
+        assertEquals(19, Selection.getSelectionStart(editText.getText()));
+        assertEquals(53, Selection.getSelectionEnd(editText.getText()));
+
+        // Move to the previous line and wait for an event.
+        AccessibilityEvent sixthExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                               AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(EditText.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(
+                                R.string.android_wiki_short))
+                        && event.getFromIndex() == 0
+                        && event.getToIndex() == 19
+                        && event.getMovementGranularity() ==
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(sixthExpected);
+
+        // Verify the selection position.
+        assertEquals(0, Selection.getSelectionStart(editText.getText()));
+        assertEquals(53, Selection.getSelectionEnd(editText.getText()));
+
+        // Make sure there is no previous.
+        assertFalse(getInteractionBridge().performAction(text,
+                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments));
+
+        // Verify the selection position.
+        assertEquals(0, Selection.getSelectionStart(editText.getText()));
+        assertEquals(53, Selection.getSelectionEnd(editText.getText()));
+    }
+
+    @MediumTest
     public void testActionNextAndPreviousAtGranularityPageOverText() throws Exception {
         final EditText editText = (EditText) getActivity().findViewById(R.id.edit);
 
@@ -1195,7 +2086,7 @@
                 (event.getEventType() ==
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
-                            AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                                AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
                         && event.getPackageName().equals(getActivity().getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
@@ -1204,7 +2095,7 @@
                         && event.getFromIndex() == 0
                         && event.getToIndex() == 139
                         && event.getMovementGranularity() ==
-                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
             }
         }, TIMEOUT_ASYNC_PROCESSING);
 
@@ -1230,7 +2121,7 @@
                 (event.getEventType() ==
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
-                            AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                                AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
                         && event.getPackageName().equals(getActivity().getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
@@ -1239,7 +2130,7 @@
                         && event.getFromIndex() == 139
                         && event.getToIndex() == 285
                         && event.getMovementGranularity() ==
-                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
             }
         }, TIMEOUT_ASYNC_PROCESSING);
 
@@ -1265,7 +2156,7 @@
                 (event.getEventType() ==
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
-                            AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                                AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
                         && event.getPackageName().equals(getActivity().getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
@@ -1274,7 +2165,7 @@
                         && event.getFromIndex() == 285
                         && event.getToIndex() == 436
                         && event.getMovementGranularity() ==
-                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
             }
         }, TIMEOUT_ASYNC_PROCESSING);
 
@@ -1308,7 +2199,7 @@
                 (event.getEventType() ==
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
-                            AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
                         && event.getPackageName().equals(getActivity().getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
@@ -1317,7 +2208,7 @@
                         && event.getFromIndex() == 285
                         && event.getToIndex() == 436
                         && event.getMovementGranularity() ==
-                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
             }
         }, TIMEOUT_ASYNC_PROCESSING);
 
@@ -1343,7 +2234,7 @@
                 (event.getEventType() ==
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
-                            AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
                         && event.getPackageName().equals(getActivity().getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
@@ -1352,7 +2243,7 @@
                         && event.getFromIndex() == 139
                         && event.getToIndex() == 285
                         && event.getMovementGranularity() ==
-                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
             }
         }, TIMEOUT_ASYNC_PROCESSING);
 
@@ -1378,7 +2269,7 @@
                 (event.getEventType() ==
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
-                            AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
                         && event.getPackageName().equals(getActivity().getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
@@ -1387,7 +2278,7 @@
                         && event.getFromIndex() == 0
                         && event.getToIndex() == 139
                         && event.getMovementGranularity() ==
-                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
             }
         }, TIMEOUT_ASYNC_PROCESSING);
 
@@ -1408,6 +2299,285 @@
     }
 
     @MediumTest
+    public void testActionNextAndPreviousAtGranularityPageOverTextExtend() throws Exception {
+        final EditText editText = (EditText) getActivity().findViewById(R.id.edit);
+
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                editText.setText(getString(R.string.android_wiki));
+                Selection.removeSelection(editText.getText());
+            }
+        });
+
+        final AccessibilityNodeInfo text = getInteractionBridge()
+               .findAccessibilityNodeInfoByTextFromRoot(getString(R.string.android_wiki));
+
+        final int granularities = text.getMovementGranularities();
+        assertEquals(granularities, AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
+                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
+                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
+                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
+                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+
+        final Bundle arguments = new Bundle();
+        arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
+                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+        arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN, true);
+
+        // Move to the next page and wait for an event.
+        AccessibilityEvent firstExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                                AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(EditText.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(
+                                R.string.android_wiki))
+                        && event.getFromIndex() == 0
+                        && event.getToIndex() == 139
+                        && event.getMovementGranularity() ==
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(firstExpected);
+
+        // Verify the selection position.
+        assertEquals(0, Selection.getSelectionStart(editText.getText()));
+        assertEquals(139, Selection.getSelectionEnd(editText.getText()));
+
+        // Move to the next page and wait for an event.
+        AccessibilityEvent secondExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                                AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(EditText.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(
+                                R.string.android_wiki))
+                        && event.getFromIndex() == 139
+                        && event.getToIndex() == 285
+                        && event.getMovementGranularity() ==
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(secondExpected);
+
+        // Verify the selection position.
+        assertEquals(0, Selection.getSelectionStart(editText.getText()));
+        assertEquals(285, Selection.getSelectionEnd(editText.getText()));
+
+        // Move to the next page and wait for an event.
+        AccessibilityEvent thirdExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                                AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(EditText.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(
+                                R.string.android_wiki))
+                        && event.getFromIndex() == 285
+                        && event.getToIndex() == 436
+                        && event.getMovementGranularity() ==
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(thirdExpected);
+
+        // Verify the selection position.
+        assertEquals(0, Selection.getSelectionStart(editText.getText()));
+        assertEquals(436, Selection.getSelectionEnd(editText.getText()));
+
+        // Make sure there is no next.
+        assertFalse(getInteractionBridge().performAction(text,
+                AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments));
+
+        // Focus the view so we can change selection.
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                editText.setFocusable(true);
+                editText.requestFocus();
+            }
+        });
+
+        // Put selection at the end of the text.
+        Bundle setSelectionArgs = new Bundle();
+        setSelectionArgs.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 436);
+        setSelectionArgs.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, 436);
+        assertTrue(getInteractionBridge().performAction(text,
+                AccessibilityNodeInfo.ACTION_SET_SELECTION, setSelectionArgs));
+
+        // Verify the selection position.
+        assertEquals(436, Selection.getSelectionStart(editText.getText()));
+        assertEquals(436, Selection.getSelectionEnd(editText.getText()));
+
+        // Unfocus the view so we can hide the soft-keyboard.
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                editText.clearFocus();
+                editText.setFocusable(false);
+            }
+        });
+
+        // Move to the previous page and wait for an event.
+        AccessibilityEvent fourthExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(EditText.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(
+                                R.string.android_wiki))
+                        && event.getFromIndex() == 285
+                        && event.getToIndex() == 436
+                        && event.getMovementGranularity() ==
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(fourthExpected);
+
+        // Verify the selection position.
+        assertEquals(285, Selection.getSelectionStart(editText.getText()));
+        assertEquals(436, Selection.getSelectionEnd(editText.getText()));
+
+        // Move to the previous page and wait for an event.
+        AccessibilityEvent fifthExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(EditText.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(
+                                R.string.android_wiki))
+                        && event.getFromIndex() == 139
+                        && event.getToIndex() == 285
+                        && event.getMovementGranularity() ==
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(fifthExpected);
+
+        // Verify the selection position.
+        assertEquals(139, Selection.getSelectionStart(editText.getText()));
+        assertEquals(436, Selection.getSelectionEnd(editText.getText()));
+
+        // Move to the previous page and wait for an event.
+        AccessibilityEvent sixthExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(EditText.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(
+                                R.string.android_wiki))
+                        && event.getFromIndex() == 0
+                        && event.getToIndex() == 139
+                        && event.getMovementGranularity() ==
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(sixthExpected);
+
+        // Verify the selection position.
+        assertEquals(0, Selection.getSelectionStart(editText.getText()));
+        assertEquals(436, Selection.getSelectionEnd(editText.getText()));
+
+        // Make sure there is no previous.
+        assertFalse(getInteractionBridge().performAction(text,
+                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments));
+
+        // Verify the selection position.
+        assertEquals(0, Selection.getSelectionStart(editText.getText()));
+        assertEquals(436, Selection.getSelectionEnd(editText.getText()));
+    }
+
+    @MediumTest
     public void testActionNextAndPreviousAtGranularityParagraphOverText() throws Exception {
         final TextView textView = (TextView) getActivity().findViewById(R.id.edit);
 
@@ -1447,7 +2617,7 @@
                 (event.getEventType() ==
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
-                            AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                                AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
                         && event.getPackageName().equals(getActivity().getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
@@ -1455,8 +2625,8 @@
                                 R.string.android_wiki_paragraphs))
                         && event.getFromIndex() == 2
                         && event.getToIndex() == 104
-                        && event.getMovementGranularity()
-                            == AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
+                        && event.getMovementGranularity() ==
+                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
             }
         }, TIMEOUT_ASYNC_PROCESSING);
 
@@ -1482,7 +2652,7 @@
                 (event.getEventType() ==
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
-                            AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
                         && event.getPackageName().equals(getActivity().getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
@@ -1491,7 +2661,7 @@
                         && event.getFromIndex() == 106
                         && event.getToIndex() == 267
                         && event.getMovementGranularity() ==
-                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
             }
         }, TIMEOUT_ASYNC_PROCESSING);
 
@@ -1517,7 +2687,7 @@
                 (event.getEventType() ==
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
-                            AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                                AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
                         && event.getPackageName().equals(getActivity().getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
@@ -1526,7 +2696,7 @@
                         && event.getFromIndex() == 268
                         && event.getToIndex() == 582
                         && event.getMovementGranularity() ==
-                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
             }
         }, TIMEOUT_ASYNC_PROCESSING);
 
@@ -1560,7 +2730,7 @@
                 (event.getEventType() ==
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
-                            AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
                         && event.getPackageName().equals(getActivity().getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
@@ -1569,7 +2739,7 @@
                         && event.getFromIndex() == 268
                         && event.getToIndex() == 582
                         && event.getMovementGranularity() ==
-                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
+                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
             }
         }, TIMEOUT_ASYNC_PROCESSING);
 
@@ -1595,7 +2765,7 @@
                 (event.getEventType() ==
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
-                            AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
                         && event.getPackageName().equals(getActivity().getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
@@ -1604,7 +2774,7 @@
                         && event.getFromIndex() == 106
                         && event.getToIndex() == 267
                         && event.getMovementGranularity() ==
-                            AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
             }
         }, TIMEOUT_ASYNC_PROCESSING);
 
@@ -1630,7 +2800,7 @@
                 (event.getEventType() ==
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
-                            AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
                         && event.getPackageName().equals(getActivity().getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
@@ -1638,8 +2808,8 @@
                                 R.string.android_wiki_paragraphs))
                         && event.getFromIndex() == 2
                         && event.getToIndex() == 104
-                        && event.getMovementGranularity()
-                                == AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
+                        && event.getMovementGranularity() ==
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
             }
         }, TIMEOUT_ASYNC_PROCESSING);
 
@@ -1658,4 +2828,433 @@
         assertEquals(2, Selection.getSelectionStart(textView.getText()));
         assertEquals(2, Selection.getSelectionEnd(textView.getText()));
     }
+
+    @MediumTest
+    public void testActionNextAndPreviousAtGranularityParagraphOverTextExtend() throws Exception {
+        final EditText editText = (EditText) getActivity().findViewById(R.id.edit);
+
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                editText.setText(getString(R.string.android_wiki_paragraphs));
+                Selection.removeSelection(editText.getText());
+            }
+        });
+
+        final AccessibilityNodeInfo text = getInteractionBridge()
+               .findAccessibilityNodeInfoByTextFromRoot(getString(R.string.android_wiki_short));
+
+        final int granularities = text.getMovementGranularities();
+        assertEquals(granularities, AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
+                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
+                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
+                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
+                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+
+        final Bundle arguments = new Bundle();
+        arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
+                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
+        arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN, true);
+
+        // Move to the next paragraph and wait for an event.
+        AccessibilityEvent firstExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                                AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(EditText.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(
+                                R.string.android_wiki_paragraphs))
+                        && event.getFromIndex() == 2
+                        && event.getToIndex() == 104
+                        && event.getMovementGranularity() ==
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(firstExpected);
+
+        // Verify the selection position.
+        assertEquals(2, Selection.getSelectionStart(editText.getText()));
+        assertEquals(104, Selection.getSelectionEnd(editText.getText()));
+
+        // Move to the next paragraph and wait for an event.
+        AccessibilityEvent secondExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                                AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(EditText.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(
+                                R.string.android_wiki_paragraphs))
+                        && event.getFromIndex() == 106
+                        && event.getToIndex() == 267
+                        && event.getMovementGranularity() ==
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(secondExpected);
+
+        // Verify the selection position.
+        assertEquals(2, Selection.getSelectionStart(editText.getText()));
+        assertEquals(267, Selection.getSelectionEnd(editText.getText()));
+
+        // Move to the next paragraph and wait for an event.
+        AccessibilityEvent thirdExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                                AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(EditText.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(
+                                R.string.android_wiki_paragraphs))
+                        && event.getFromIndex() == 268
+                        && event.getToIndex() == 582
+                        && event.getMovementGranularity() ==
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(thirdExpected);
+
+        // Verify the selection position.
+        assertEquals(2, Selection.getSelectionStart(editText.getText()));
+        assertEquals(582, Selection.getSelectionEnd(editText.getText()));
+
+        // Make sure there is no next.
+        assertFalse(getInteractionBridge().performAction(text,
+                AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments));
+
+        // Verify the selection position.
+        assertEquals(2, Selection.getSelectionStart(editText.getText()));
+        assertEquals(582, Selection.getSelectionEnd(editText.getText()));
+
+        // Focus the view so we can change selection.
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                editText.setFocusable(true);
+                editText.requestFocus();
+            }
+        });
+
+        // Put selection at the end of the text.
+        Bundle setSelectionArgs = new Bundle();
+        setSelectionArgs.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 582);
+        setSelectionArgs.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, 582);
+        assertTrue(getInteractionBridge().performAction(text,
+                AccessibilityNodeInfo.ACTION_SET_SELECTION, setSelectionArgs));
+
+        // Verify the selection position.
+        assertEquals(582, Selection.getSelectionStart(editText.getText()));
+        assertEquals(582, Selection.getSelectionEnd(editText.getText()));
+
+        // Unfocus the view so we can get rid of the soft-keyboard.
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                editText.clearFocus();
+                editText.setFocusable(false);
+            }
+        });
+
+        // Move to the previous paragraph and wait for an event.
+        AccessibilityEvent fourthExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(EditText.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(
+                                R.string.android_wiki_paragraphs))
+                        && event.getFromIndex() == 268
+                        && event.getToIndex() == 582
+                        && event.getMovementGranularity() ==
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(fourthExpected);
+
+        // Verify the selection position.
+        assertEquals(268, Selection.getSelectionStart(editText.getText()));
+        assertEquals(582, Selection.getSelectionEnd(editText.getText()));
+
+        // Move to the previous paragraph and wait for an event.
+        AccessibilityEvent fifthExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(EditText.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(
+                                R.string.android_wiki_paragraphs))
+                        && event.getFromIndex() == 106
+                        && event.getToIndex() == 267
+                        && event.getMovementGranularity() ==
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(fifthExpected);
+
+        // Verify the selection position.
+        assertEquals(106, Selection.getSelectionStart(editText.getText()));
+        assertEquals(582, Selection.getSelectionEnd(editText.getText()));
+
+        // Move to the previous paragraph and wait for an event.
+        AccessibilityEvent sixthExpected = getInteractionBridge()
+                .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return
+                (event.getEventType() ==
+                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+                        && event.getAction() ==
+                                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getClassName().equals(EditText.class.getName())
+                        && event.getText().size() > 0
+                        && event.getText().get(0).toString().equals(getString(
+                                R.string.android_wiki_paragraphs))
+                        && event.getFromIndex() == 2
+                        && event.getToIndex() == 104
+                        && event.getMovementGranularity() ==
+                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Make sure we got the expected event.
+        assertNotNull(sixthExpected);
+
+        // Verify the selection position.
+        assertEquals(2, Selection.getSelectionStart(editText.getText()));
+        assertEquals(582, Selection.getSelectionEnd(editText.getText()));
+
+        // Make sure there is no previous.
+        assertFalse(getInteractionBridge().performAction(text,
+                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments));
+
+        // Verify the selection position.
+        assertEquals(2, Selection.getSelectionStart(editText.getText()));
+        assertEquals(582, Selection.getSelectionEnd(editText.getText()));
+    }
+
+    @MediumTest
+    public void testTextEditingActions() throws Exception {
+        final EditText editText = (EditText) getActivity().findViewById(R.id.edit);
+        final String textContent = getString(R.string.foo_bar_baz);
+
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                editText.setFocusable(true);
+                editText.requestFocus();
+                editText.setText(getString(R.string.foo_bar_baz));
+                Selection.removeSelection(editText.getText());
+            }
+        });
+
+        final AccessibilityNodeInfo text = getInteractionBridge()
+               .findAccessibilityNodeInfoByTextFromRoot(getString(R.string.foo_bar_baz));
+
+        // Select all text.
+        Bundle arguments = new Bundle();
+        arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 0);
+        arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT,
+                editText.getText().length());
+        assertTrue(getInteractionBridge().performAction(text,
+                AccessibilityNodeInfo.ACTION_SET_SELECTION, arguments));
+
+        // Copy the selected text.
+        getInteractionBridge().performAction(text, AccessibilityNodeInfo.ACTION_COPY);
+
+        // Set selection at the end.
+        final int textLength = editText.getText().length();
+        // Verify the selection position.
+        assertEquals(textLength, Selection.getSelectionStart(editText.getText()));
+        assertEquals(textLength, Selection.getSelectionEnd(editText.getText()));
+
+        // Paste the selected text.
+        assertTrue(getInteractionBridge().performAction(text,
+                AccessibilityNodeInfo.ACTION_PASTE));
+
+        // Verify the content.
+        assertEquals(editText.getText().toString(), textContent + " " + textContent);
+
+        // Select all text.
+        arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 0);
+        arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT,
+                editText.getText().length());
+        assertTrue(getInteractionBridge().performAction(text,
+                AccessibilityNodeInfo.ACTION_SET_SELECTION, arguments));
+
+        // Cut the selected text.
+        assertTrue(getInteractionBridge().performAction(text,
+                AccessibilityNodeInfo.ACTION_CUT));
+
+        // Verify the content.
+        assertTrue(TextUtils.isEmpty(editText.getText()));
+    }
+
+    public void testSetSelectionInContentDescription() throws Exception {
+        final View view = getActivity().findViewById(R.id.view);
+
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                view.setContentDescription(getString(R.string.foo_bar_baz));
+            }
+        });
+
+        AccessibilityNodeInfo text = getInteractionBridge()
+               .findAccessibilityNodeInfoByTextFromRoot(getString(R.string.foo_bar_baz));
+
+        // Set the cursor position.
+        Bundle arguments = new Bundle();
+        arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 4);
+        arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, 4);
+        assertTrue(getInteractionBridge().performAction(text,
+                AccessibilityNodeInfo.ACTION_SET_SELECTION, arguments));
+
+        // Try and fail to set the selection longer than zero.
+        arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 4);
+        arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, 5);
+        assertFalse(getInteractionBridge().performAction(text,
+                AccessibilityNodeInfo.ACTION_SET_SELECTION, arguments));
+    }
+
+    public void testSelectionPositionForNonEditableView() throws Exception {
+        final View view = getActivity().findViewById(R.id.view);
+
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                view.setContentDescription(getString(R.string.foo_bar_baz));
+            }
+        });
+
+        final AccessibilityNodeInfo text = getInteractionBridge()
+               .findAccessibilityNodeInfoByTextFromRoot(getString(R.string.foo_bar_baz));
+
+        // Check the initial node properties.
+        assertFalse(text.isEditable());
+        assertSame(text.getTextSelectionStart(), -1);
+        assertSame(text.getTextSelectionEnd(), -1);
+
+        // Set the cursor position.
+        getInteractionBridge().executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+            @Override
+            public void run() {
+                Bundle arguments = new Bundle();
+                arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 4);
+                arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, 4);
+                assertTrue(getInteractionBridge().performAction(text,
+                        AccessibilityNodeInfo.ACTION_SET_SELECTION, arguments));
+            }
+        }, new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return (event.getEventType()
+                        == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
+            }
+        }, TIMEOUT_ASYNC_PROCESSING);
+
+        // Refresh the node.
+        AccessibilityNodeInfo refreshedText = getInteractionBridge()
+                .findAccessibilityNodeInfoByTextFromRoot(getString(R.string.foo_bar_baz));
+
+        // Check the related node properties.
+        assertFalse(refreshedText.isEditable());
+        assertSame(refreshedText.getTextSelectionStart(), 4);
+        assertSame(refreshedText.getTextSelectionEnd(), 4);
+
+        // Try to set to an invalid cursor position.
+        Bundle arguments = new Bundle();
+        arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 4);
+        arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, 5);
+        assertFalse(getInteractionBridge().performAction(text,
+                AccessibilityNodeInfo.ACTION_SET_SELECTION, arguments));
+
+        // Refresh the node.
+        refreshedText = getInteractionBridge()
+                .findAccessibilityNodeInfoByTextFromRoot(getString(R.string.foo_bar_baz));
+
+        // Check the related node properties.
+        assertFalse(refreshedText.isEditable());
+        assertSame(refreshedText.getTextSelectionStart(), 4);
+        assertSame(refreshedText.getTextSelectionEnd(), 4);
+    }
 }
diff --git a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
index 2b6dab4..f83c35c 100644
--- a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
+++ b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
@@ -304,7 +304,7 @@
         // char sequence attributes
         assertEquals(button.getPackageName(), source.getPackageName());
         assertEquals(button.getClassName(), source.getClassName());
-        assertEquals(button.getText(), source.getText());
+        assertEquals(button.getText().toString(), source.getText().toString());
         assertSame(button.getContentDescription(), source.getContentDescription());
 
         // boolean attributes
diff --git a/tests/tests/app/Android.mk b/tests/tests/app/Android.mk
index 8d5877d..d4387b4 100644
--- a/tests/tests/app/Android.mk
+++ b/tests/tests/app/Android.mk
@@ -21,7 +21,7 @@
 # and when built explicitly put it in the data partition
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common
+LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common voip-common
 
 LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
 
diff --git a/tests/tests/content/src/android/content/cts/ContentProviderTest.java b/tests/tests/content/src/android/content/cts/ContentProviderTest.java
index 2baa904..34b1e8c 100644
--- a/tests/tests/content/src/android/content/cts/ContentProviderTest.java
+++ b/tests/tests/content/src/android/content/cts/ContentProviderTest.java
@@ -249,12 +249,12 @@
 
         IContentProvider iContentProvider = new IContentProvider() {
             @Override
-            public int bulkInsert(Uri url, ContentValues[] initialValues) {
+            public int bulkInsert(String callingPkg, Uri url, ContentValues[] initialValues) {
                 return 0;
             }
 
             @Override
-            public int delete(Uri url, String selection, String[] selectionArgs) {
+            public int delete(String callingPkg, Uri url, String selection, String[] selectionArgs) {
                 return 0;
             }
 
@@ -264,36 +264,36 @@
             }
 
             @Override
-            public Uri insert(Uri url, ContentValues initialValues) {
+            public Uri insert(String callingPkg, Uri url, ContentValues initialValues) {
                 return null;
             }
 
             @Override
-            public ParcelFileDescriptor openFile(Uri url, String mode) {
+            public ParcelFileDescriptor openFile(String callingPkg, Uri url, String mode) {
                 return null;
             }
 
             @Override
-            public AssetFileDescriptor openAssetFile(Uri url, String mode) {
+            public AssetFileDescriptor openAssetFile(String callingPkg, Uri url, String mode) {
                 return null;
             }
 
             @Override
-            public ContentProviderResult[] applyBatch(
+            public ContentProviderResult[] applyBatch(String callingPkg,
                     ArrayList<ContentProviderOperation> operations)
                     throws RemoteException, OperationApplicationException {
                 return null;
             }
 
             @Override
-            public Cursor query(Uri url, String[] projection, String selection,
+            public Cursor query(String callingPkg, Uri url, String[] projection, String selection,
                     String[] selectionArgs, String sortOrder,
                     ICancellationSignal cancellationSignal) {
                 return null;
             }
 
             @Override
-            public int update(Uri url, ContentValues values, String selection,
+            public int update(String callingPkg, Uri url, ContentValues values, String selection,
                     String[] selectionArgs) {
                 return 0;
             }
@@ -304,7 +304,7 @@
             }
 
             @Override
-            public Bundle call(String method, String request, Bundle args) {
+            public Bundle call(String callingPkg, String method, String request, Bundle args) {
                 return null;
             }
 
@@ -314,7 +314,8 @@
             }
 
             @Override
-            public AssetFileDescriptor openTypedAssetFile(Uri url, String mimeType, Bundle opts)
+            public AssetFileDescriptor openTypedAssetFile(String callingPkg, Uri url,
+                    String mimeType, Bundle opts)
                     throws RemoteException, FileNotFoundException {
                 return null;
             }
diff --git a/suite/pts/hostTests/browser/browserlauncher/Android.mk b/tests/tests/display/Android.mk
similarity index 66%
copy from suite/pts/hostTests/browser/browserlauncher/Android.mk
copy to tests/tests/display/Android.mk
index 0592017..ec5b40d 100644
--- a/suite/pts/hostTests/browser/browserlauncher/Android.mk
+++ b/tests/tests/display/Android.mk
@@ -13,21 +13,25 @@
 # limitations under the License.
 
 LOCAL_PATH:= $(call my-dir)
+
 include $(CLEAR_VARS)
 
 # don't include this package in any target
 LOCAL_MODULE_TAGS := optional
+# and when built explicitly put it in the data partition
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
 LOCAL_JAVA_LIBRARIES := android.test.runner
 
-LOCAL_STATIC_JAVA_LIBRARIES := ptsutil ctsutil ctstestrunner ctstestserver
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_PACKAGE_NAME := PtsDeviceBrowserLauncher
+LOCAL_PACKAGE_NAME := CtsDisplayTestCases
 
-LOCAL_SDK_VERSION := 16
+LOCAL_SDK_VERSION := current
 
-include $(BUILD_PTS_PACKAGE)
+# This test runner sets up/cleans up the device before/after running the tests.
+LOCAL_CTS_TEST_RUNNER := com.android.cts.tradefed.testtype.DisplayTestRunner
 
-include $(call all-makefiles-under,$(LOCAL_PATH))
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/display/AndroidManifest.xml b/tests/tests/display/AndroidManifest.xml
new file mode 100644
index 0000000..1ddce5a
--- /dev/null
+++ b/tests/tests/display/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2012 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.cts.display">
+
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+                     android:targetPackage="com.android.cts.display"
+                     android:label="CTS tests of android.view.display"/>
+
+</manifest>
+
diff --git a/tests/tests/display/src/android/display/cts/DisplayTest.java b/tests/tests/display/src/android/display/cts/DisplayTest.java
new file mode 100644
index 0000000..595f7148
--- /dev/null
+++ b/tests/tests/display/src/android/display/cts/DisplayTest.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2012 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.display.cts;
+
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.hardware.display.DisplayManager;
+import android.test.AndroidTestCase;
+import android.util.DisplayMetrics;
+import android.view.Display;
+import android.view.WindowManager;
+
+public class DisplayTest extends AndroidTestCase {
+    // This test is called from DisplayTestRunner which brings up an overlay display on the target
+    // device. The overlay display parameters must match the ones defined there which are
+    // 1281x721/214 (wxh/dpi).
+
+    private static final int SECONDARY_DISPLAY_WIDTH = 1281;
+    private static final int SECONDARY_DISPLAY_HEIGHT = 721;
+    private static final int SECONDARY_DISPLAY_DPI = 214;
+    private static final float SCALE_DENSITY_LOWER_BOUND =
+            (float)(SECONDARY_DISPLAY_DPI - 1) / DisplayMetrics.DENSITY_DEFAULT;
+    private static final float SCALE_DENSITY_UPPER_BOUND =
+            (float)(SECONDARY_DISPLAY_DPI + 1) / DisplayMetrics.DENSITY_DEFAULT;
+    // Matches com.android.internal.R.string.display_manager_overlay_display_name.
+    private static final String OVERLAY_DISPLAY_NAME_PREFIX = "Overlay #";
+
+    private DisplayManager mDisplayManager;
+    private WindowManager mWindowManager;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mDisplayManager = (DisplayManager)mContext.getSystemService(Context.DISPLAY_SERVICE);
+        mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
+    }
+
+    private boolean isSecondarySize(Display display) {
+        final Point p = new Point();
+        display.getSize(p);
+        return p.x == SECONDARY_DISPLAY_WIDTH && p.y == SECONDARY_DISPLAY_HEIGHT;
+    }
+
+    private Display getSecondaryDisplay(Display[] displays) {
+        for (Display display : displays) {
+            if (isSecondarySize(display)) {
+                return display;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Verify that the getDisplays method returns both a default and an overlay display.
+     */
+    public void testGetDisplays() {
+        Display[] displays = mDisplayManager.getDisplays();
+        assertNotNull(displays);
+        assertTrue(2 <= displays.length);
+        boolean hasDefaultDisplay = false;
+        boolean hasSecondaryDisplay = false;
+        for (Display display : displays) {
+            if (display.getDisplayId() == Display.DEFAULT_DISPLAY) {
+                hasDefaultDisplay = true;
+            }
+            if (isSecondarySize(display)) {
+                hasSecondaryDisplay = true;
+            }
+        }
+        assertTrue(hasDefaultDisplay);
+        assertTrue(hasSecondaryDisplay);
+    }
+
+    /**
+     * Verify that the WindowManager returns the default display.
+     */
+    public void testDefaultDisplay() {
+        assertEquals(Display.DEFAULT_DISPLAY, mWindowManager.getDefaultDisplay().getDisplayId());
+    }
+
+    /**
+     * Verify that there is a secondary display.
+     */
+    public void testSecondaryDisplay() {
+        Display display = getSecondaryDisplay(mDisplayManager.getDisplays());
+        assertNotNull(display);
+        assertTrue(Display.DEFAULT_DISPLAY != display.getDisplayId());
+    }
+
+    /**
+     * Test the properties of the secondary Display.
+     */
+    public void testGetDisplayAttrs() {
+        Display display = getSecondaryDisplay(mDisplayManager.getDisplays());
+
+        assertEquals(SECONDARY_DISPLAY_WIDTH, display.getWidth());
+        assertEquals(SECONDARY_DISPLAY_HEIGHT, display.getHeight());
+
+        Point outSize = new Point();
+        display.getSize(outSize);
+        assertEquals(SECONDARY_DISPLAY_WIDTH, outSize.x);
+        assertEquals(SECONDARY_DISPLAY_HEIGHT, outSize.y);
+
+        assertEquals(0, display.getOrientation());
+
+        assertEquals(PixelFormat.RGBA_8888, display.getPixelFormat());
+
+        assertTrue(0 < display.getRefreshRate());
+
+        assertTrue(display.getName().contains(OVERLAY_DISPLAY_NAME_PREFIX));
+    }
+
+    /**
+     * Test that the getMetrics method fills in correct values.
+     */
+    public void testGetMetrics() {
+        Display display = getSecondaryDisplay(mDisplayManager.getDisplays());
+
+        Point outSize = new Point();
+        display.getSize(outSize);
+
+        DisplayMetrics outMetrics = new DisplayMetrics();
+        outMetrics.setToDefaults();
+        display.getMetrics(outMetrics);
+
+        assertEquals(SECONDARY_DISPLAY_WIDTH, outMetrics.widthPixels);
+        assertEquals(SECONDARY_DISPLAY_HEIGHT, outMetrics.heightPixels);
+
+        // The scale is in [0.1, 3], and density is the scale factor.
+        assertTrue(SCALE_DENSITY_LOWER_BOUND <= outMetrics.density
+                && outMetrics.density <= SCALE_DENSITY_UPPER_BOUND);
+        assertTrue(SCALE_DENSITY_LOWER_BOUND <= outMetrics.scaledDensity
+                && outMetrics.scaledDensity <= SCALE_DENSITY_UPPER_BOUND);
+
+        assertEquals(SECONDARY_DISPLAY_DPI, outMetrics.densityDpi);
+        assertEquals((float)SECONDARY_DISPLAY_DPI, outMetrics.xdpi);
+        assertEquals((float)SECONDARY_DISPLAY_DPI, outMetrics.ydpi);
+    }
+
+    /**
+     * Test that the getCurrentSizeRange method returns correct values.
+     */
+    public void testGetCurrentSizeRange() {
+        Display display = getSecondaryDisplay(mDisplayManager.getDisplays());
+
+        Point smallest = new Point();
+        Point largest = new Point();
+        display.getCurrentSizeRange(smallest, largest);
+
+        assertEquals(SECONDARY_DISPLAY_WIDTH, smallest.x);
+        assertEquals(SECONDARY_DISPLAY_HEIGHT, smallest.y);
+        assertEquals(SECONDARY_DISPLAY_WIDTH, largest.x);
+        assertEquals(SECONDARY_DISPLAY_HEIGHT, largest.y);
+    }
+
+    /**
+     * Test that the getFlags method returns no flag bits set for the overlay display.
+     */
+    public void testFlags() {
+        Display display = getSecondaryDisplay(mDisplayManager.getDisplays());
+
+        assertEquals(0, display.getFlags());
+    }
+}
diff --git a/suite/pts/hostTests/browser/browserlauncher/Android.mk b/tests/tests/dreams/Android.mk
similarity index 67%
copy from suite/pts/hostTests/browser/browserlauncher/Android.mk
copy to tests/tests/dreams/Android.mk
index 0592017..c630d8a 100644
--- a/suite/pts/hostTests/browser/browserlauncher/Android.mk
+++ b/tests/tests/dreams/Android.mk
@@ -13,21 +13,25 @@
 # limitations under the License.
 
 LOCAL_PATH:= $(call my-dir)
+
 include $(CLEAR_VARS)
 
-# don't include this package in any target
+LOCAL_PACKAGE_NAME := CtsDreamsTestCases
+
+# Don't include this package in any target.
 LOCAL_MODULE_TAGS := optional
 
+# When built, explicitly put it in the data partition.
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+# All tests should include android.test.runner.
 LOCAL_JAVA_LIBRARIES := android.test.runner
 
-LOCAL_STATIC_JAVA_LIBRARIES := ptsutil ctsutil ctstestrunner ctstestserver
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_PACKAGE_NAME := PtsDeviceBrowserLauncher
+# Need access to ServiceManager
+#LOCAL_SDK_VERSION := current
 
-LOCAL_SDK_VERSION := 16
-
-include $(BUILD_PTS_PACKAGE)
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/dreams/AndroidManifest.xml b/tests/tests/dreams/AndroidManifest.xml
new file mode 100644
index 0000000..fb3e564
--- /dev/null
+++ b/tests/tests/dreams/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.cts.dreams">
+
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <!-- This is a self-instrumenting test package. -->
+    <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+                     android:targetPackage="com.android.cts.dreams"
+                     android:label="CTS tests for the android.service.dreams package"/>
+
+</manifest>
+
diff --git a/tests/tests/dreams/src/android/service/dreams/cts/DreamsFeatureTest.java b/tests/tests/dreams/src/android/service/dreams/cts/DreamsFeatureTest.java
new file mode 100644
index 0000000..358a7bf
--- /dev/null
+++ b/tests/tests/dreams/src/android/service/dreams/cts/DreamsFeatureTest.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2012 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.service.dreams.cts;
+
+import android.os.ServiceManager;
+import android.service.dreams.DreamService;
+import android.service.dreams.IDreamManager;
+
+import junit.framework.TestCase;
+
+public class DreamsFeatureTest extends TestCase {
+
+    public void testDreamManagerExists() {
+        IDreamManager service = IDreamManager.Stub.asInterface(
+                            ServiceManager.getService(DreamService.DREAM_SERVICE));
+        assertTrue("Dream manager service missing", service != null);
+    }
+
+}
diff --git a/tests/tests/graphics/src/android/graphics/cts/AvoidXfermodeTest.java b/tests/tests/graphics/src/android/graphics/cts/AvoidXfermodeTest.java
index d58336d..beb3621 100644
--- a/tests/tests/graphics/src/android/graphics/cts/AvoidXfermodeTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/AvoidXfermodeTest.java
@@ -62,7 +62,7 @@
 
         assertEquals(Color.GREEN, b.getPixel(BASE_SIZE / 2, BASE_SIZE / 2));
         assertEquals(Color.RED, b.getPixel(BASE_SIZE + BASE_SIZE / 2, BASE_SIZE / 2));
-        assertEquals(Color.CYAN, b.getPixel(BASE_SIZE / 2, BASE_SIZE + BASE_SIZE / 2));
+        assertEquals(Color.BLUE, b.getPixel(BASE_SIZE / 2, BASE_SIZE + BASE_SIZE / 2));
         assertEquals(Color.BLACK, b.getPixel(BASE_SIZE + BASE_SIZE / 2, BASE_SIZE + BASE_SIZE / 2));
     }
 }
diff --git a/tests/tests/graphics/src/android/graphics/cts/MatrixTest.java b/tests/tests/graphics/src/android/graphics/cts/MatrixTest.java
index ebe20a2..29e4f5f 100644
--- a/tests/tests/graphics/src/android/graphics/cts/MatrixTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/MatrixTest.java
@@ -302,7 +302,7 @@
         values[0] = 1000f;
         matrix.setValues(values);
         assertTrue(mMatrix.invert(matrix));
-        String expect = "[1.0, -0.0, 0.0][-0.0, 1.0, 0.0][0.0, 0.0, 1.0]";
+        String expect = "[1.0, 0.0, 0.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0]";
         assertEquals(expect, matrix.toShortString());
         expect = "[1.0, 0.0, 0.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0]";
         assertEquals(expect, mMatrix.toShortString());
diff --git a/tests/tests/hardware/src/android/hardware/cts/CameraTest.java b/tests/tests/hardware/src/android/hardware/cts/CameraTest.java
index bbb5643..31a72cf 100755
--- a/tests/tests/hardware/src/android/hardware/cts/CameraTest.java
+++ b/tests/tests/hardware/src/android/hardware/cts/CameraTest.java
@@ -53,6 +53,8 @@
 import java.util.Iterator;
 import java.util.List;
 
+import junit.framework.AssertionFailedError;
+
 /**
  * This test case must run with hardware. It can't be tested in emulator
  */
@@ -1713,6 +1715,9 @@
                 // ignore
             }
             mCamera.stopPreview();
+            // See if any frame duration violations occurred during preview run
+            AssertionFailedError e = callback.getDurationException();
+            if (e != null) throw(e);
         }
 
         // Test the invalid fps cases.
@@ -1741,6 +1746,7 @@
         // An array storing the arrival time of the frames in the last second.
         private ArrayList<Long> mFrames = new ArrayList<Long>();
         private long firstFrameArrivalTime;
+        private AssertionFailedError mDurationException = null;
 
         public void reset(double minFps, double maxFps) {
             this.mMinFps = minFps;
@@ -1752,6 +1758,7 @@
                     + ". Max frame interval=" + mMaxFrameInterval);
             mFrames.clear();
             firstFrameArrivalTime = 0;
+            mDurationException = null;
         }
 
         // This method tests if the actual fps is between minimum and maximum.
@@ -1783,34 +1790,50 @@
                 // variance is averaged out.
 
                 // Check if the frame interval is too large or too small.
-                // x100 = percent, intervalMargin should be bigger than fpsMargin considering that
-                // fps will be in the order of 10.
+                // x100 = percent, intervalMargin should be bigger than
+                // fpsMargin considering that fps will be in the order of 10.
                 double intervalMargin = 0.9;
                 long lastArrivalTime = mFrames.get(mFrames.size() - 1);
                 double interval = arrivalTime - lastArrivalTime;
                 if (LOGV) Log.v(TAG, "Frame interval=" + interval);
-                assertTrue("Frame interval (" + interval + "ms) is too large." +
-                        " mMaxFrameInterval=" + mMaxFrameInterval + "ms",
-                        interval < mMaxFrameInterval * (1.0 + intervalMargin));
-                assertTrue("Frame interval (" + interval + "ms) is too small." +
-                        " mMinFrameInterval=" + mMinFrameInterval + "ms",
-                        interval > mMinFrameInterval * (1.0 - intervalMargin));
+                try {
+                    assertTrue("Frame interval (" + interval + "ms) is too " +
+                            "large. mMaxFrameInterval=" +
+                             mMaxFrameInterval + "ms",
+                            interval < mMaxFrameInterval *
+                            (1.0 + intervalMargin));
+                    assertTrue("Frame interval (" + interval + "ms) is too " +
+                            "small. mMinFrameInterval=" +
+                            mMinFrameInterval + "ms",
+                            interval > mMinFrameInterval *
+                            (1.0 - intervalMargin));
 
-                // Check if the fps is within range.
-                double fpsMargin = 0.5; // x100 = percent
-                double avgInterval = (double)(arrivalTime - mFrames.get(0))
-                        / mFrames.size();
-                double fps = 1000.0 / avgInterval;
-                assertTrue("Actual fps (" + fps + ") should be larger than " +
-                           "min fps (" + mMinFps + ")",
-                           fps >= mMinFps * (1.0 - fpsMargin));
-                assertTrue("Actual fps (" + fps + ") should be smaller than " +
-                           "max fps (" + mMaxFps + ")",
-                           fps <= mMaxFps * (1.0 + fpsMargin));
+                    // Check if the fps is within range.
+                    double fpsMargin = 0.5; // x100 = percent
+                    double avgInterval = (double)(arrivalTime - mFrames.get(0))
+                            / mFrames.size();
+                    double fps = 1000.0 / avgInterval;
+                    assertTrue("Actual fps (" + fps + ") should be larger " +
+                            "than min fps (" + mMinFps + ")",
+                            fps >= mMinFps * (1.0 - fpsMargin));
+                    assertTrue("Actual fps (" + fps + ") should be smaller" +
+                            "than max fps (" + mMaxFps + ")",
+                            fps <= mMaxFps * (1.0 + fpsMargin));
+                } catch (AssertionFailedError e) {
+                    // Need to throw this only in the test body, instead of in
+                    // the callback
+                    if (mDurationException == null) {
+                        mDurationException = e;
+                    }
+                }
             }
             // Add the arrival time of this frame to the list.
             mFrames.add(arrivalTime);
         }
+
+        public AssertionFailedError getDurationException() {
+            return mDurationException;
+        }
     }
 
     private void assertEquals(Size expected, Size actual) {
diff --git a/tests/tests/hardware/src/android/hardware/cts/SensorTest.java b/tests/tests/hardware/src/android/hardware/cts/SensorTest.java
index 574e9e9..c865140 100644
--- a/tests/tests/hardware/src/android/hardware/cts/SensorTest.java
+++ b/tests/tests/hardware/src/android/hardware/cts/SensorTest.java
@@ -16,21 +16,36 @@
 
 package android.hardware.cts;
 
+import java.lang.IllegalArgumentException;
+import java.lang.Override;
 import java.util.List;
 
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
+import android.hardware.TriggerEvent;
+import android.hardware.TriggerEventListener;
 import android.test.AndroidTestCase;
 
 public class SensorTest extends AndroidTestCase {
+    private SensorManager mSensorManager;
+    private TriggerListener mTriggerListener;
+    private SensorListener mSensorListener;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mSensorManager = (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE);
+        mTriggerListener = new TriggerListener();
+        mSensorListener = new SensorListener();
+    }
 
     public void testSensorOperations() {
         // Because we can't know every sensors unit details, so we can't assert
         // get values with specified values.
-        final SensorManager mSensorManager =
-            (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE);
         List<Sensor> sensors = mSensorManager.getSensorList(Sensor.TYPE_ALL);
         assertNotNull(sensors);
         Sensor sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
@@ -70,7 +85,48 @@
         }
     }
 
-    private void assertSensorValues(Sensor sensor) {
+    public void testRequestTriggerWithNonTriggerSensor() {
+        Sensor sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+        boolean result;
+        if (sensor != null) {
+            result = mSensorManager.requestTriggerSensor(mTriggerListener, sensor);
+            assertFalse(result);
+        }
+    }
+
+    public void testCancelTriggerWithNonTriggerSensor() {
+        Sensor sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+        boolean result;
+        if (sensor != null) {
+            result = mSensorManager.cancelTriggerSensor(mTriggerListener, sensor);
+            assertFalse(result);
+        }
+    }
+
+    public void testRegisterWithTriggerSensor() {
+        Sensor sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION);
+        boolean result;
+        if (sensor != null) {
+            result = mSensorManager.registerListener(mSensorListener, sensor,
+                    SensorManager.SENSOR_DELAY_NORMAL);
+            assertFalse(result);
+        }
+    }
+
+    public void testRegisterTwiceWithSameSensor() {
+        Sensor sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+        boolean result;
+        if (sensor != null) {
+            result = mSensorManager.registerListener(mSensorListener, sensor,
+                    SensorManager.SENSOR_DELAY_NORMAL);
+            assertTrue(result);
+            result = mSensorManager.registerListener(mSensorListener, sensor,
+                    SensorManager.SENSOR_DELAY_NORMAL);
+            assertFalse(result);
+        }
+    }
+
+        private void assertSensorValues(Sensor sensor) {
         assertTrue(sensor.getMaximumRange() >= 0);
         assertTrue(sensor.getPower() >= 0);
         assertTrue(sensor.getResolution() >= 0);
@@ -96,4 +152,20 @@
         }
         assertEquals(sensors, mSensorManager.getSensors());
     }
+
+    class TriggerListener extends TriggerEventListener {
+        @Override
+        public void onTrigger(TriggerEvent event) {
+        }
+    }
+
+    class SensorListener implements SensorEventListener {
+        @Override
+        public void onSensorChanged(SensorEvent event) {
+        }
+
+        @Override
+        public void onAccuracyChanged(Sensor sensor, int accuracy) {
+        }
+    }
 }
diff --git a/tests/tests/location/src/android/location/cts/LocationManagerTest.java b/tests/tests/location/src/android/location/cts/LocationManagerTest.java
index edfda5a..5e823bc 100755
--- a/tests/tests/location/src/android/location/cts/LocationManagerTest.java
+++ b/tests/tests/location/src/android/location/cts/LocationManagerTest.java
@@ -291,7 +291,7 @@
     }
 
     /**
-     * Helper method to test a location update with given provider
+     * Helper method to test a location update with given mock location provider
      *
      * @param providerName name of provider to test. Must already exist.
      * @throws InterruptedException
@@ -322,6 +322,7 @@
         assertEquals(providerName, location.getProvider());
         assertEquals(latitude1, location.getLatitude());
         assertEquals(longitude1, location.getLongitude());
+        assertEquals(true, location.isFromMockProvider());
 
         // update location without notifying listener
         listener.reset();
@@ -373,6 +374,7 @@
         assertEquals(TEST_MOCK_PROVIDER_NAME, location.getProvider());
         assertEquals(latitude1, location.getLatitude());
         assertEquals(longitude1, location.getLongitude());
+        assertEquals(true, location.isFromMockProvider());
 
         // update location without notifying listener
         mManager.removeUpdates(listener);
@@ -420,6 +422,7 @@
         assertEquals(TEST_MOCK_PROVIDER_NAME, location.getProvider());
         assertEquals(latitude1, location.getLatitude());
         assertEquals(longitude1, location.getLongitude());
+        assertEquals(true, location.isFromMockProvider());
 
         // update location without receiving broadcast.
         mManager.removeUpdates(mPendingIntent);
@@ -509,6 +512,7 @@
         assertEquals(TEST_MOCK_PROVIDER_NAME, location.getProvider());
         assertEquals(latitude1, location.getLatitude());
         assertEquals(longitude1, location.getLongitude());
+        assertEquals(true, location.isFromMockProvider());
 
         mIntentReceiver.clearReceivedIntents();
         updateLocation(latitude2, longitude2);
@@ -519,6 +523,7 @@
         assertEquals(TEST_MOCK_PROVIDER_NAME, location.getProvider());
         assertEquals(latitude2, location.getLatitude());
         assertEquals(longitude2, location.getLongitude());
+        assertEquals(true, location.isFromMockProvider());
 
         try {
             mManager.getLastKnownLocation(null);
@@ -678,6 +683,7 @@
         assertEquals(providerName, location.getProvider());
         assertEquals(latitude, location.getLatitude());
         assertEquals(longitude, location.getLongitude());
+        assertEquals(true, location.isFromMockProvider());
 
         // Remove the listener.
         mManager.removeUpdates(listener);
diff --git a/tests/tests/media/res/raw/video_176x144_yv12.raw b/tests/tests/media/res/raw/video_176x144_yv12.raw
new file mode 100644
index 0000000..23af164
--- /dev/null
+++ b/tests/tests/media/res/raw/video_176x144_yv12.raw
Binary files differ
diff --git a/tests/tests/media/src/android/media/cts/DecodeEditEncodeTest.java b/tests/tests/media/src/android/media/cts/DecodeEditEncodeTest.java
new file mode 100644
index 0000000..8cd1100
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/DecodeEditEncodeTest.java
@@ -0,0 +1,914 @@
+/*
+ * Copyright (C) 2013 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.media.cts;
+
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecList;
+import android.media.MediaFormat;
+import android.opengl.GLES20;
+import android.test.AndroidTestCase;
+import android.util.Log;
+import android.view.Surface;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import javax.microedition.khronos.opengles.GL10;
+
+
+/**
+ * This test has three steps:
+ * <ol>
+ *   <li>Generate a video test stream.
+ *   <li>Decode the video from the stream, rendering frames into a SurfaceTexture.
+ *       Render the texture onto a Surface that feeds a video encoder, modifying
+ *       the output with a fragment shader.
+ *   <li>Decode the second video and compare it to the expected result.
+ * </ol><p>
+ * The second step is a typical scenario for video editing.  We could do all this in one
+ * step, feeding data through multiple stages of MediaCodec, but at some point we're
+ * no longer exercising the code in the way we expect it to be used (and the code
+ * gets a bit unwieldy).
+ */
+public class DecodeEditEncodeTest extends AndroidTestCase {
+    private static final String TAG = "DecodeEditEncode";
+    private static final boolean WORK_AROUND_BUGS = false;  // avoid fatal codec bugs
+    private static final boolean VERBOSE = false;           // lots of logging
+    private static final boolean DEBUG_SAVE_FILE = false;   // save copy of encoded movie
+    private static final String DEBUG_FILE_NAME_BASE = "/sdcard/test.";
+
+    // parameters for the encoder
+    private static final String MIME_TYPE = "video/avc";    // H.264 Advanced Video Coding
+    private static final int BIT_RATE = 2000000;            // 2Mbps
+    private static final int FRAME_RATE = 15;               // 15fps
+    private static final int IFRAME_INTERVAL = 10;          // 10 seconds between I-frames
+
+    // movie length, in frames
+    private static final int NUM_FRAMES = 30;               // two seconds of video
+
+    private static final int TEST_R0 = 0;                   // dull green background
+    private static final int TEST_G0 = 136;
+    private static final int TEST_B0 = 0;
+    private static final int TEST_R1 = 236;                 // pink; BT.601 YUV {120,160,200}
+    private static final int TEST_G1 = 50;
+    private static final int TEST_B1 = 186;
+
+    // Replaces TextureRender.FRAGMENT_SHADER during edit; swaps green and blue channels.
+    private static final String FRAGMENT_SHADER =
+            "#extension GL_OES_EGL_image_external : require\n" +
+            "precision mediump float;\n" +
+            "varying vec2 vTextureCoord;\n" +
+            "uniform samplerExternalOES sTexture;\n" +
+            "void main() {\n" +
+            "  gl_FragColor = texture2D(sTexture, vTextureCoord).rbga;\n" +
+            "}\n";
+
+    // size of a frame, in pixels
+    private int mWidth = -1;
+    private int mHeight = -1;
+
+
+    public void testVideoEditQCIF() throws Throwable {
+        setSize(176, 144);
+        VideoEditWrapper.runTest(this);
+    }
+    public void testVideoEditQVGA() throws Throwable {
+        setSize(320, 240);
+        VideoEditWrapper.runTest(this);
+    }
+    public void testVideoEdit720p() throws Throwable {
+        setSize(1280, 720);
+        VideoEditWrapper.runTest(this);
+    }
+
+    /**
+     * Wraps testEditVideo, running it in a new thread.  Required because of the way
+     * SurfaceTexture.OnFrameAvailableListener works when the current thread has a Looper
+     * configured.
+     */
+    private static class VideoEditWrapper implements Runnable {
+        private Throwable mThrowable;
+        private DecodeEditEncodeTest mTest;
+
+        private VideoEditWrapper(DecodeEditEncodeTest test) {
+            mTest = test;
+        }
+
+        public void run() {
+            try {
+                mTest.videoEditTest();
+            } catch (Throwable th) {
+                mThrowable = th;
+            }
+        }
+
+        /** Entry point. */
+        public static void runTest(DecodeEditEncodeTest obj) throws Throwable {
+            VideoEditWrapper wrapper = new VideoEditWrapper(obj);
+            Thread th = new Thread(wrapper, "codec test");
+            th.start();
+            th.join();
+            if (wrapper.mThrowable != null) {
+                throw wrapper.mThrowable;
+            }
+        }
+    }
+
+    /**
+     * Sets the desired frame size.
+     */
+    private void setSize(int width, int height) {
+        if ((width % 16) != 0 || (height % 16) != 0) {
+            Log.w(TAG, "WARNING: width or height not multiple of 16");
+        }
+        mWidth = width;
+        mHeight = height;
+    }
+
+    /**
+     * Tests editing of a video file with GL.
+     */
+    private void videoEditTest() {
+        VideoChunks sourceChunks = new VideoChunks();
+
+        if (!generateVideoFile(sourceChunks)) {
+            // No AVC codec?  Fail silently.
+            return;
+        }
+
+        if (DEBUG_SAVE_FILE) {
+            // Save a copy to a file.  We call it ".mp4", but it's actually just an elementary
+            // stream, so not all video players will know what to do with it.
+            String dirName = getContext().getFilesDir().getAbsolutePath();
+            String fileName = "vedit1_" + mWidth + "x" + mHeight + ".mp4";
+            sourceChunks.saveToFile(new File(dirName, fileName));
+        }
+
+        VideoChunks destChunks = editVideoFile(sourceChunks);
+
+        if (DEBUG_SAVE_FILE) {
+            String dirName = getContext().getFilesDir().getAbsolutePath();
+            String fileName = "vedit2_" + mWidth + "x" + mHeight + ".mp4";
+            destChunks.saveToFile(new File(dirName, fileName));
+        }
+
+        checkVideoFile(destChunks);
+    }
+
+    /**
+     * Generates a test video file, saving it as VideoChunks.  We generate frames with GL to
+     * avoid having to deal with multiple YUV formats.
+     *
+     * @return true on success, false on "soft" failure
+     */
+    private boolean generateVideoFile(VideoChunks output) {
+        if (VERBOSE) Log.d(TAG, "generateVideoFile " + mWidth + "x" + mHeight);
+        MediaCodec encoder = null;
+        InputSurface inputSurface = null;
+
+        try {
+            MediaCodecInfo codecInfo = selectCodec(MIME_TYPE);
+            if (codecInfo == null) {
+                // Don't fail CTS if they don't have an AVC codec (not here, anyway).
+                Log.e(TAG, "Unable to find an appropriate codec for " + MIME_TYPE);
+                return false;
+            }
+            if (VERBOSE) Log.d(TAG, "found codec: " + codecInfo.getName());
+
+            // We avoid the device-specific limitations on width and height by using values that
+            // are multiples of 16, which all tested devices seem to be able to handle.
+            MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, mWidth, mHeight);
+
+            // Set some properties.  Failing to specify some of these can cause the MediaCodec
+            // configure() call to throw an unhelpful exception.
+            format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+                    MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
+            format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
+            format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
+            format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
+            if (VERBOSE) Log.d(TAG, "format: " + format);
+            output.setMediaFormat(format);
+
+            // Create a MediaCodec for the desired codec, then configure it as an encoder with
+            // our desired properties.
+            encoder = MediaCodec.createByCodecName(codecInfo.getName());
+            encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+            inputSurface = new InputSurface(encoder.createInputSurface());
+            inputSurface.makeCurrent();
+            encoder.start();
+
+            generateVideoData(encoder, inputSurface, output);
+        } finally {
+            if (encoder != null) {
+                if (VERBOSE) Log.d(TAG, "releasing encoder");
+                encoder.stop();
+                encoder.release();
+                if (VERBOSE) Log.d(TAG, "released encoder");
+            }
+            if (inputSurface != null) {
+                inputSurface.release();
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Returns the first codec capable of encoding the specified MIME type, or null if no
+     * match was found.
+     */
+    private static MediaCodecInfo selectCodec(String mimeType) {
+        int numCodecs = MediaCodecList.getCodecCount();
+        for (int i = 0; i < numCodecs; i++) {
+            MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
+
+            if (!codecInfo.isEncoder()) {
+                continue;
+            }
+
+            String[] types = codecInfo.getSupportedTypes();
+            for (int j = 0; j < types.length; j++) {
+                if (types[j].equalsIgnoreCase(mimeType)) {
+                    return codecInfo;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Generates video frames, feeds them into the encoder, and writes the output to the
+     * VideoChunks instance.
+     */
+    private void generateVideoData(MediaCodec encoder, InputSurface inputSurface,
+            VideoChunks output) {
+        final int TIMEOUT_USEC = 10000;
+        ByteBuffer[] encoderOutputBuffers = encoder.getOutputBuffers();
+        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+        int generateIndex = 0;
+        int outputCount = 0;
+
+        // Loop until the output side is done.
+        boolean inputDone = false;
+        boolean outputDone = false;
+        while (!outputDone) {
+            if (VERBOSE) Log.d(TAG, "gen loop");
+
+            // If we're not done submitting frames, generate a new one and submit it.  The
+            // eglSwapBuffers call will block if the input is full.
+            if (!inputDone) {
+                if (generateIndex == NUM_FRAMES) {
+                    // Send an empty frame with the end-of-stream flag set.
+                    if (VERBOSE) Log.d(TAG, "signaling input EOS");
+                    if (WORK_AROUND_BUGS) {
+                        // Might drop a frame, but at least we won't crash mediaserver.
+                        try { Thread.sleep(500); } catch (InterruptedException ie) {}
+                        outputDone = true;
+                    } else {
+                        encoder.signalEndOfInputStream();
+                    }
+                    inputDone = true;
+                } else {
+                    generateSurfaceFrame(generateIndex);
+                    inputSurface.setPresentationTime(computePresentationTime(generateIndex));
+                    if (VERBOSE) Log.d(TAG, "inputSurface swapBuffers");
+                    inputSurface.swapBuffers();
+                }
+                generateIndex++;
+            }
+
+            // Check for output from the encoder.  If there's no output yet, we either need to
+            // provide more input, or we need to wait for the encoder to work its magic.  We
+            // can't actually tell which is the case, so if we can't get an output buffer right
+            // away we loop around and see if it wants more input.
+            //
+            // If we do find output, drain it all before supplying more input.
+            while (true) {
+                int encoderStatus = encoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
+                if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
+                    // no output available yet
+                    if (VERBOSE) Log.d(TAG, "no output from encoder available");
+                    break;      // out of while
+                } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                    // not expected for an encoder
+                    encoderOutputBuffers = encoder.getOutputBuffers();
+                    if (VERBOSE) Log.d(TAG, "encoder output buffers changed");
+                } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                    // not expected for an encoder
+                    MediaFormat newFormat = encoder.getOutputFormat();
+                    if (VERBOSE) Log.d(TAG, "encoder output format changed: " + newFormat);
+                } else if (encoderStatus < 0) {
+                    fail("unexpected result from encoder.dequeueOutputBuffer: " + encoderStatus);
+                } else { // encoderStatus >= 0
+                    ByteBuffer encodedData = encoderOutputBuffers[encoderStatus];
+                    if (encodedData == null) {
+                        fail("encoderOutputBuffer " + encoderStatus + " was null");
+                    }
+
+                    // Codec config flag must be set iff this is the first chunk of output.  This
+                    // may not hold for all codecs, but it appears to be the case for video/avc.
+                    assertTrue((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0 ||
+                            outputCount != 0);
+
+                    if (info.size != 0) {
+                        // Adjust the ByteBuffer values to match BufferInfo.
+                        encodedData.position(info.offset);
+                        encodedData.limit(info.offset + info.size);
+
+                        output.addChunk(encodedData, info.flags, info.presentationTimeUs);
+                        outputCount++;
+                    }
+
+                    encoder.releaseOutputBuffer(encoderStatus, false);
+                    if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                        outputDone = true;
+                        break;      // out of while
+                    }
+                }
+            }
+        }
+
+        // One chunk per frame, plus one for the config data.
+        assertEquals("Frame count", NUM_FRAMES + 1, outputCount);
+    }
+
+    /**
+     * Generates a frame of data using GL commands.
+     * <p>
+     * We have an 8-frame animation sequence that wraps around.  It looks like this:
+     * <pre>
+     *   0 1 2 3
+     *   7 6 5 4
+     * </pre>
+     * We draw one of the eight rectangles and leave the rest set to the zero-fill color.     */
+    private void generateSurfaceFrame(int frameIndex) {
+        frameIndex %= 8;
+
+        int startX, startY;
+        if (frameIndex < 4) {
+            // (0,0) is bottom-left in GL
+            startX = frameIndex * (mWidth / 4);
+            startY = mHeight / 2;
+        } else {
+            startX = (7 - frameIndex) * (mWidth / 4);
+            startY = 0;
+        }
+
+        GLES20.glDisable(GLES20.GL_SCISSOR_TEST);
+        GLES20.glClearColor(TEST_R0 / 255.0f, TEST_G0 / 255.0f, TEST_B0 / 255.0f, 1.0f);
+        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+        GLES20.glEnable(GLES20.GL_SCISSOR_TEST);
+        GLES20.glScissor(startX, startY, mWidth / 4, mHeight / 2);
+        GLES20.glClearColor(TEST_R1 / 255.0f, TEST_G1 / 255.0f, TEST_B1 / 255.0f, 1.0f);
+        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+    }
+
+    /**
+     * Edits a video file, saving the contents to a new file.  This involves decoding and
+     * re-encoding, not to mention conversions between YUV and RGB, and so may be lossy.
+     * <p>
+     * If we recognize the decoded format we can do this in Java code using the ByteBuffer[]
+     * output, but it's not practical to support all OEM formats.  By using a SurfaceTexture
+     * for output and a Surface for input, we can avoid issues with obscure formats and can
+     * use a fragment shader to do transformations.
+     */
+    private VideoChunks editVideoFile(VideoChunks inputData) {
+        if (VERBOSE) Log.d(TAG, "editVideoFile " + mWidth + "x" + mHeight);
+        VideoChunks outputData = new VideoChunks();
+        MediaCodec decoder = null;
+        MediaCodec encoder = null;
+        InputSurface inputSurface = null;
+        OutputSurface outputSurface = null;
+
+        try {
+            MediaFormat inputFormat = inputData.getMediaFormat();
+
+            // Create an encoder format that matches the input format.  (Might be able to just
+            // re-use the format used to generate the video, since we want it to be the same.)
+            MediaFormat outputFormat = MediaFormat.createVideoFormat(MIME_TYPE, mWidth, mHeight);
+            outputFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+                    MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
+            outputFormat.setInteger(MediaFormat.KEY_BIT_RATE,
+                    inputFormat.getInteger(MediaFormat.KEY_BIT_RATE));
+            outputFormat.setInteger(MediaFormat.KEY_FRAME_RATE,
+                    inputFormat.getInteger(MediaFormat.KEY_FRAME_RATE));
+            outputFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL,
+                    inputFormat.getInteger(MediaFormat.KEY_I_FRAME_INTERVAL));
+
+            outputData.setMediaFormat(outputFormat);
+
+            encoder = MediaCodec.createEncoderByType(MIME_TYPE);
+            encoder.configure(outputFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+            inputSurface = new InputSurface(encoder.createInputSurface());
+            inputSurface.makeCurrent();
+            encoder.start();
+
+            // OutputSurface uses the EGL context created by InputSurface.
+            decoder = MediaCodec.createDecoderByType(MIME_TYPE);
+            outputSurface = new OutputSurface();
+            outputSurface.changeFragmentShader(FRAGMENT_SHADER);
+            decoder.configure(inputFormat, outputSurface.getSurface(), null, 0);
+            decoder.start();
+
+            editVideoData(inputData, decoder, outputSurface, inputSurface, encoder, outputData);
+        } finally {
+            if (VERBOSE) Log.d(TAG, "shutting down encoder, decoder");
+            if (outputSurface != null) {
+                outputSurface.release();
+            }
+            if (inputSurface != null) {
+                inputSurface.release();
+            }
+            if (encoder != null) {
+                encoder.stop();
+                encoder.release();
+            }
+            if (decoder != null) {
+                decoder.stop();
+                decoder.release();
+            }
+        }
+
+        return outputData;
+    }
+
+    /**
+     * Edits a stream of video data.
+     */
+    private void editVideoData(VideoChunks inputData, MediaCodec decoder,
+            OutputSurface outputSurface, InputSurface inputSurface, MediaCodec encoder,
+            VideoChunks outputData) {
+        final int TIMEOUT_USEC = 10000;
+        ByteBuffer[] decoderInputBuffers = decoder.getInputBuffers();
+        ByteBuffer[] encoderOutputBuffers = encoder.getOutputBuffers();
+        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+        int inputChunk = 0;
+        int outputCount = 0;
+
+        boolean outputDone = false;
+        boolean inputDone = false;
+        boolean decoderDone = false;
+        while (!outputDone) {
+            if (VERBOSE) Log.d(TAG, "edit loop");
+
+            // Feed more data to the decoder.
+            if (!inputDone) {
+                int inputBufIndex = decoder.dequeueInputBuffer(TIMEOUT_USEC);
+                if (inputBufIndex >= 0) {
+                    if (inputChunk == inputData.getNumChunks()) {
+                        // End of stream -- send empty frame with EOS flag set.
+                        decoder.queueInputBuffer(inputBufIndex, 0, 0, 0L,
+                                MediaCodec.BUFFER_FLAG_END_OF_STREAM);
+                        inputDone = true;
+                        if (VERBOSE) Log.d(TAG, "sent input EOS (with zero-length frame)");
+                    } else {
+                        // Copy a chunk of input to the decoder.  The first chunk should have
+                        // the BUFFER_FLAG_CODEC_CONFIG flag set.
+                        ByteBuffer inputBuf = decoderInputBuffers[inputBufIndex];
+                        inputBuf.clear();
+                        inputData.getChunkData(inputChunk, inputBuf);
+                        int flags = inputData.getChunkFlags(inputChunk);
+                        long time = inputData.getChunkTime(inputChunk);
+                        decoder.queueInputBuffer(inputBufIndex, 0, inputBuf.position(),
+                                time, flags);
+                        if (VERBOSE) {
+                            Log.d(TAG, "submitted frame " + inputChunk + " to dec, size=" +
+                                    inputBuf.position() + " flags=" + flags);
+                        }
+                        inputChunk++;
+                    }
+                } else {
+                    if (VERBOSE) Log.d(TAG, "input buffer not available");
+                }
+            }
+
+            // Assume output is available.  Loop until both assumptions are false.
+            boolean decoderOutputAvailable = !decoderDone;
+            boolean encoderOutputAvailable = true;
+            while (decoderOutputAvailable || encoderOutputAvailable) {
+                // Start by draining any pending output from the encoder.  It's important to
+                // do this before we try to stuff any more data in.
+                int encoderStatus = encoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
+                if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
+                    // no output available yet
+                    if (VERBOSE) Log.d(TAG, "no output from encoder available");
+                    encoderOutputAvailable = false;
+                } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                    encoderOutputBuffers = encoder.getOutputBuffers();
+                    if (VERBOSE) Log.d(TAG, "encoder output buffers changed");
+                } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                    MediaFormat newFormat = encoder.getOutputFormat();
+                    if (VERBOSE) Log.d(TAG, "encoder output format changed: " + newFormat);
+                } else if (encoderStatus < 0) {
+                    fail("unexpected result from encoder.dequeueOutputBuffer: " + encoderStatus);
+                } else { // encoderStatus >= 0
+                    ByteBuffer encodedData = encoderOutputBuffers[encoderStatus];
+                    if (encodedData == null) {
+                        fail("encoderOutputBuffer " + encoderStatus + " was null");
+                    }
+
+                    // Write the data to the output "file".
+                    if (info.size != 0) {
+                        encodedData.position(info.offset);
+                        encodedData.limit(info.offset + info.size);
+
+                        outputData.addChunk(encodedData, info.flags, info.presentationTimeUs);
+                        outputCount++;
+
+                        if (VERBOSE) Log.d(TAG, "encoder output " + info.size + " bytes");
+                    }
+                    outputDone = (info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
+                    encoder.releaseOutputBuffer(encoderStatus, false);
+                }
+                if (encoderStatus != MediaCodec.INFO_TRY_AGAIN_LATER) {
+                    // Continue attempts to drain output.
+                    continue;
+                }
+
+                // Encoder is drained, check to see if we've got a new frame of output from
+                // the decoder.  (The output is going to a Surface, rather than a ByteBuffer,
+                // but we still get information through BufferInfo.)
+                if (!decoderDone) {
+                    int decoderStatus = decoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
+                    if (decoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
+                        // no output available yet
+                        if (VERBOSE) Log.d(TAG, "no output from decoder available");
+                        decoderOutputAvailable = false;
+                    } else if (decoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                        //decoderOutputBuffers = decoder.getOutputBuffers();
+                        if (VERBOSE) Log.d(TAG, "decoder output buffers changed (we don't care)");
+                    } else if (decoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                        // expected before first buffer of data
+                        MediaFormat newFormat = decoder.getOutputFormat();
+                        if (VERBOSE) Log.d(TAG, "decoder output format changed: " + newFormat);
+                    } else if (decoderStatus < 0) {
+                        fail("unexpected result from decoder.dequeueOutputBuffer: "+decoderStatus);
+                    } else { // decoderStatus >= 0
+                        if (VERBOSE) Log.d(TAG, "surface decoder given buffer "
+                                + decoderStatus + " (size=" + info.size + ")");
+                        // The ByteBuffers are null references, but we still get a nonzero
+                        // size for the decoded data.
+                        boolean doRender = (info.size != 0);
+
+                        // As soon as we call releaseOutputBuffer, the buffer will be forwarded
+                        // to SurfaceTexture to convert to a texture.  The API doesn't
+                        // guarantee that the texture will be available before the call
+                        // returns, so we need to wait for the onFrameAvailable callback to
+                        // fire.  If we don't wait, we risk rendering from the previous frame.
+                        decoder.releaseOutputBuffer(decoderStatus, doRender);
+                        if (doRender) {
+                            // This waits for the image and renders it after it arrives.
+                            if (VERBOSE) Log.d(TAG, "awaiting frame");
+                            outputSurface.awaitNewImage();
+                            outputSurface.drawImage();
+
+                            // Send it to the encoder.
+                            inputSurface.setPresentationTime(info.presentationTimeUs);
+                            if (VERBOSE) Log.d(TAG, "swapBuffers");
+                            inputSurface.swapBuffers();
+                        }
+                        if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                            // forward decoder EOS to encoder
+                            if (VERBOSE) Log.d(TAG, "signaling input EOS");
+                            if (WORK_AROUND_BUGS) {
+                                // Bail early, possibly dropping a frame.
+                                return;
+                            } else {
+                                encoder.signalEndOfInputStream();
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        if (inputChunk != outputCount) {
+            throw new RuntimeException("frame lost: " + inputChunk + " in, " +
+                    outputCount + " out");
+        }
+    }
+
+    /**
+     * Checks the video file to see if the contents match our expectations.  We decode the
+     * video to a Surface and check the pixels with GL.
+     */
+    private void checkVideoFile(VideoChunks inputData) {
+        OutputSurface surface = null;
+        MediaCodec decoder = null;
+
+        if (VERBOSE) Log.d(TAG, "checkVideoFile");
+
+        try {
+            surface = new OutputSurface(mWidth, mHeight);
+
+            MediaFormat format = inputData.getMediaFormat();
+            decoder = MediaCodec.createDecoderByType(MIME_TYPE);
+            decoder.configure(format, surface.getSurface(), null, 0);
+            decoder.start();
+
+            int badFrames = checkVideoData(inputData, decoder, surface);
+            if (badFrames != 0) {
+                fail("Found " + badFrames + " bad frames");
+            }
+        } finally {
+            if (surface != null) {
+                surface.release();
+            }
+            if (decoder != null) {
+                decoder.stop();
+                decoder.release();
+            }
+        }
+    }
+
+    /**
+     * Checks the video data.
+     *
+     * @return the number of bad frames
+     */
+    private int checkVideoData(VideoChunks inputData, MediaCodec decoder, OutputSurface surface) {
+        final int TIMEOUT_USEC = 1000;
+        ByteBuffer[] decoderInputBuffers = decoder.getInputBuffers();
+        ByteBuffer[] decoderOutputBuffers = decoder.getOutputBuffers();
+        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+        int inputChunk = 0;
+        int checkIndex = 0;
+        int badFrames = 0;
+
+        boolean outputDone = false;
+        boolean inputDone = false;
+        while (!outputDone) {
+            if (VERBOSE) Log.d(TAG, "check loop");
+
+            // Feed more data to the decoder.
+            if (!inputDone) {
+                int inputBufIndex = decoder.dequeueInputBuffer(TIMEOUT_USEC);
+                if (inputBufIndex >= 0) {
+                    if (inputChunk == inputData.getNumChunks()) {
+                        // End of stream -- send empty frame with EOS flag set.
+                        decoder.queueInputBuffer(inputBufIndex, 0, 0, 0L,
+                                MediaCodec.BUFFER_FLAG_END_OF_STREAM);
+                        inputDone = true;
+                        if (VERBOSE) Log.d(TAG, "sent input EOS");
+                    } else {
+                        // Copy a chunk of input to the decoder.  The first chunk should have
+                        // the BUFFER_FLAG_CODEC_CONFIG flag set.
+                        ByteBuffer inputBuf = decoderInputBuffers[inputBufIndex];
+                        inputBuf.clear();
+                        inputData.getChunkData(inputChunk, inputBuf);
+                        int flags = inputData.getChunkFlags(inputChunk);
+                        long time = inputData.getChunkTime(inputChunk);
+                        decoder.queueInputBuffer(inputBufIndex, 0, inputBuf.position(),
+                                time, flags);
+                        if (VERBOSE) {
+                            Log.d(TAG, "submitted frame " + inputChunk + " to dec, size=" +
+                                    inputBuf.position() + " flags=" + flags);
+                        }
+                        inputChunk++;
+                    }
+                } else {
+                    if (VERBOSE) Log.d(TAG, "input buffer not available");
+                }
+            }
+
+            if (!outputDone) {
+                int decoderStatus = decoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
+                if (decoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
+                    // no output available yet
+                    if (VERBOSE) Log.d(TAG, "no output from decoder available");
+                } else if (decoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                    decoderOutputBuffers = decoder.getOutputBuffers();
+                    if (VERBOSE) Log.d(TAG, "decoder output buffers changed");
+                } else if (decoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                    MediaFormat newFormat = decoder.getOutputFormat();
+                    if (VERBOSE) Log.d(TAG, "decoder output format changed: " + newFormat);
+                } else if (decoderStatus < 0) {
+                    fail("unexpected result from decoder.dequeueOutputBuffer: " + decoderStatus);
+                } else { // decoderStatus >= 0
+                    ByteBuffer decodedData = decoderOutputBuffers[decoderStatus];
+
+                    if (VERBOSE) Log.d(TAG, "surface decoder given buffer " + decoderStatus +
+                            " (size=" + info.size + ")");
+                    if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                        if (VERBOSE) Log.d(TAG, "output EOS");
+                        outputDone = true;
+                    }
+
+                    boolean doRender = (info.size != 0);
+
+                    // As soon as we call releaseOutputBuffer, the buffer will be forwarded
+                    // to SurfaceTexture to convert to a texture.  The API doesn't guarantee
+                    // that the texture will be available before the call returns, so we
+                    // need to wait for the onFrameAvailable callback to fire.
+                    decoder.releaseOutputBuffer(decoderStatus, doRender);
+                    if (doRender) {
+                        if (VERBOSE) Log.d(TAG, "awaiting frame " + checkIndex);
+                        assertEquals("Wrong time stamp", computePresentationTime(checkIndex),
+                                info.presentationTimeUs);
+                        surface.awaitNewImage();
+                        surface.drawImage();
+                        if (!checkSurfaceFrame(checkIndex++)) {
+                            badFrames++;
+                        }
+                    }
+                }
+            }
+        }
+
+        return badFrames;
+    }
+
+    /**
+     * Checks the frame for correctness, using GL to check RGB values.
+     *
+     * @return true if the frame looks good
+     */
+    private boolean checkSurfaceFrame(int frameIndex) {
+        ByteBuffer pixelBuf = ByteBuffer.allocateDirect(4); // TODO - reuse this
+        boolean frameFailed = false;
+
+        for (int i = 0; i < 8; i++) {
+            // Note the coordinates are inverted on the Y-axis in GL.
+            int x, y;
+            if (i < 4) {
+                x = i * (mWidth / 4) + (mWidth / 8);
+                y = (mHeight * 3) / 4;
+            } else {
+                x = (7 - i) * (mWidth / 4) + (mWidth / 8);
+                y = mHeight / 4;
+            }
+
+            GLES20.glReadPixels(x, y, 1, 1, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, pixelBuf);
+            int r = pixelBuf.get(0) & 0xff;
+            int g = pixelBuf.get(1) & 0xff;
+            int b = pixelBuf.get(2) & 0xff;
+            //Log.d(TAG, "GOT(" + frameIndex + "/" + i + "): r=" + r + " g=" + g + " b=" + b);
+
+            int expR, expG, expB;
+            if (i == frameIndex % 8) {
+                // colored rect (green/blue swapped)
+                expR = TEST_R1;
+                expG = TEST_B1;
+                expB = TEST_G1;
+            } else {
+                // zero background color (green/blue swapped)
+                expR = TEST_R0;
+                expG = TEST_B0;
+                expB = TEST_G0;
+            }
+            if (!isColorClose(r, expR) ||
+                    !isColorClose(g, expG) ||
+                    !isColorClose(b, expB)) {
+                Log.w(TAG, "Bad frame " + frameIndex + " (rect=" + i + ": rgb=" + r +
+                        "," + g + "," + b + " vs. expected " + expR + "," + expG +
+                        "," + expB + ")");
+                frameFailed = true;
+            }
+        }
+
+        return !frameFailed;
+    }
+
+    /**
+     * Returns true if the actual color value is close to the expected color value.
+     */
+    static boolean isColorClose(int actual, int expected) {
+        final int MAX_DELTA = 8;
+        if (expected < MAX_DELTA) {
+            return actual < (expected + MAX_DELTA);
+        } else if (expected > (255 - MAX_DELTA)) {
+            return actual > (expected - MAX_DELTA);
+        } else {
+            return actual > (expected - MAX_DELTA) && actual < (expected + MAX_DELTA);
+        }
+    }
+
+    /**
+     * Generates the presentation time for frame N.
+     */
+    private static long computePresentationTime(int frameIndex) {
+        return 123 + frameIndex * 1000000 / FRAME_RATE;
+    }
+
+
+    /**
+     * The elementary stream coming out of the "video/avc" encoder needs to be fed back into
+     * the decoder one chunk at a time.  If we just wrote the data to a file, we would lose
+     * the information about chunk boundaries.  This class stores the encoded data in memory,
+     * retaining the chunk organization.
+     */
+    private static class VideoChunks {
+        private MediaFormat mMediaFormat;
+        private ArrayList<byte[]> mChunks = new ArrayList<byte[]>();
+        private ArrayList<Integer> mFlags = new ArrayList<Integer>();
+        private ArrayList<Long> mTimes = new ArrayList<Long>();
+
+        /**
+         * Sets the MediaFormat, for the benefit of a future decoder.
+         */
+        public void setMediaFormat(MediaFormat format) {
+            mMediaFormat = format;
+        }
+
+        /**
+         * Gets the MediaFormat that was used by the encoder.
+         */
+        public MediaFormat getMediaFormat() {
+            return mMediaFormat;
+        }
+
+        /**
+         * Adds a new chunk.  Advances buf.position to buf.limit.
+         */
+        public void addChunk(ByteBuffer buf, int flags, long time) {
+            byte[] data = new byte[buf.remaining()];
+            buf.get(data);
+            mChunks.add(data);
+            mFlags.add(flags);
+            mTimes.add(time);
+        }
+
+        /**
+         * Returns the number of chunks currently held.
+         */
+        public int getNumChunks() {
+            return mChunks.size();
+        }
+
+        /**
+         * Copies the data from chunk N into "dest".  Advances dest.position.
+         */
+        public void getChunkData(int chunk, ByteBuffer dest) {
+            byte[] data = mChunks.get(chunk);
+            dest.put(data);
+        }
+
+        /**
+         * Returns the flags associated with chunk N.
+         */
+        public int getChunkFlags(int chunk) {
+            return mFlags.get(chunk);
+        }
+
+        /**
+         * Returns the timestamp associated with chunk N.
+         */
+        public long getChunkTime(int chunk) {
+            return mTimes.get(chunk);
+        }
+
+        /**
+         * Writes the chunks to a file as a contiguous stream.  Useful for debugging.
+         */
+        public void saveToFile(File file) {
+            Log.d(TAG, "saving chunk data to file " + file);
+            FileOutputStream fos = null;
+            BufferedOutputStream bos = null;
+
+            try {
+                fos = new FileOutputStream(file);
+                bos = new BufferedOutputStream(fos);
+                fos = null;     // closing bos will also close fos
+
+                int numChunks = getNumChunks();
+                for (int i = 0; i < numChunks; i++) {
+                    byte[] chunk = mChunks.get(i);
+                    bos.write(chunk);
+                }
+            } catch (IOException ioe) {
+                throw new RuntimeException(ioe);
+            } finally {
+                try {
+                    if (bos != null) {
+                        bos.close();
+                    }
+                    if (fos != null) {
+                        fos.close();
+                    }
+                } catch (IOException ioe) {
+                    throw new RuntimeException(ioe);
+                }
+            }
+        }
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/DecoderTest.java b/tests/tests/media/src/android/media/cts/DecoderTest.java
index ac80538..54a670d 100644
--- a/tests/tests/media/src/android/media/cts/DecoderTest.java
+++ b/tests/tests/media/src/android/media/cts/DecoderTest.java
@@ -18,29 +18,28 @@
 
 import com.android.cts.media.R;
 
-import android.content.Context;
 import android.content.res.AssetFileDescriptor;
 import android.content.res.Resources;
 import android.media.MediaCodec;
 import android.media.MediaExtractor;
 import android.media.MediaFormat;
-import android.test.AndroidTestCase;
 import android.util.Log;
+import android.view.Surface;
 
 import java.io.BufferedInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.ByteBuffer;
-import java.util.Map;
+import java.util.zip.CRC32;
 
-public class DecoderTest extends AndroidTestCase {
+public class DecoderTest extends MediaPlayerTestBase {
     private static final String TAG = "DecoderTest";
 
     private Resources mResources;
 
     @Override
-    public void setContext(Context context) {
-        super.setContext(context);
+    protected void setUp() throws Exception {
+        super.setUp();
         mResources = mContext.getResources();
     }
 
@@ -91,6 +90,7 @@
             masterBuffer[i] = (short) sample;
         }
         bis.close();
+        masterFd.close();
 
         AssetFileDescriptor testFd = mResources.openRawResourceFd(testinput);
 
@@ -102,6 +102,7 @@
         extractor = new MediaExtractor();
         extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(),
                 testFd.getLength());
+        testFd.close();
 
         assertEquals("wrong number of tracks", 1, extractor.getTrackCount());
         MediaFormat format = extractor.getTrackFormat(0);
@@ -194,8 +195,6 @@
 
         codec.stop();
         codec.release();
-        testFd.close();
-        masterFd.close();
 
         assertEquals("wrong data size", masterLength, numBytesDecoded);
         long avgErrorSquared = (totalErrorSquared / (numBytesDecoded / 2));
@@ -203,5 +202,494 @@
         assertTrue("decoding error too big: " + rmse, rmse <= maxerror);
     }
 
+    public void testCodecBasicH264() throws Exception {
+        Surface s = getActivity().getSurfaceHolder().getSurface();
+        int frames1 = countFrames(
+                R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz,
+                false, s);
+        assertEquals("wrong number of frames decoded", 240, frames1);
+
+        int frames2 = countFrames(
+                R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz,
+                false, null);
+        assertEquals("different number of frames when using Surface", frames1, frames2);
+    }
+
+    public void testCodecBasicH263() throws Exception {
+        Surface s = getActivity().getSurfaceHolder().getSurface();
+        int frames1 = countFrames(
+                R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz,
+                false, s);
+        assertEquals("wrong number of frames decoded", 122, frames1);
+
+        int frames2 = countFrames(
+                R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz,
+                false, null);
+        assertEquals("different number of frames when using Surface", frames1, frames2);
+    }
+
+    public void testCodecReconfigH264WithoutSurface() throws Exception {
+        testCodecReconfig(
+                R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz, null);
+    }
+
+    public void testCodecReconfigH264WithSurface() throws Exception {
+        Surface s = getActivity().getSurfaceHolder().getSurface();
+        testCodecReconfig(
+                R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz, s);
+    }
+
+    public void testCodecReconfigH263WithoutSurface() throws Exception {
+        testCodecReconfig(
+                R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz, null);
+    }
+
+    public void testCodecReconfigH263WithSurface() throws Exception {
+        Surface s = getActivity().getSurfaceHolder().getSurface();
+        testCodecReconfig(
+                R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz, s);
+    }
+
+//    public void testCodecReconfigOgg() throws Exception {
+//        testCodecReconfig(R.raw.sinesweepogg, null);
+//    }
+//
+    public void testCodecReconfigMp3() throws Exception {
+        testCodecReconfig(R.raw.sinesweepmp3lame, null);
+    }
+
+    public void testCodecReconfigM4a() throws Exception {
+        testCodecReconfig(R.raw.sinesweepm4a, null);
+    }
+
+    private void testCodecReconfig(int video, Surface s) throws Exception {
+        int frames1 = countFrames(video, false, s);
+        int frames2 = countFrames(video, true, s);
+        assertEquals("different number of frames when reusing codec", frames1, frames2);
+    }
+
+    private int countFrames(int video, boolean reconfigure, Surface s) throws Exception {
+        int numframes = 0;
+
+        AssetFileDescriptor testFd = mResources.openRawResourceFd(video);
+
+        MediaExtractor extractor;
+        MediaCodec codec = null;
+        ByteBuffer[] codecInputBuffers;
+        ByteBuffer[] codecOutputBuffers;
+
+        extractor = new MediaExtractor();
+        extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(),
+                testFd.getLength());
+
+        MediaFormat format = extractor.getTrackFormat(0);
+        String mime = format.getString(MediaFormat.KEY_MIME);
+        boolean isAudio = mime.startsWith("audio/");
+
+        codec = MediaCodec.createDecoderByType(mime);
+//        if (mime.contains("avc")) {
+//            codec = MediaCodec.createByCodecName("OMX.google.h264.decoder");
+//        } else if (mime.contains("3gpp")) {
+//            codec = MediaCodec.createByCodecName("OMX.google.h263.decoder");
+//        }
+        assertNotNull("couldn't find codec", codec);
+        Log.i("@@@@", "using codec: " + codec.getName());
+        codec.configure(format, s /* surface */, null /* crypto */, 0 /* flags */);
+        codec.start();
+        codecInputBuffers = codec.getInputBuffers();
+        codecOutputBuffers = codec.getOutputBuffers();
+
+        if (reconfigure) {
+            codec.stop();
+            codec.configure(format, s /* surface */, null /* crypto */, 0 /* flags */);
+            codec.start();
+            codecInputBuffers = codec.getInputBuffers();
+            codecOutputBuffers = codec.getOutputBuffers();
+        }
+        Log.i("@@@@", "format: " + format);
+
+        extractor.selectTrack(0);
+
+        // start decoding
+        final long kTimeOutUs = 5000;
+        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+        boolean sawInputEOS = false;
+        boolean sawOutputEOS = false;
+        int deadDecoderCounter = 0;
+        while (!sawOutputEOS && deadDecoderCounter < 100) {
+            if (!sawInputEOS) {
+                int inputBufIndex = codec.dequeueInputBuffer(kTimeOutUs);
+
+                if (inputBufIndex >= 0) {
+                    ByteBuffer dstBuf = codecInputBuffers[inputBufIndex];
+
+                    int sampleSize =
+                        extractor.readSampleData(dstBuf, 0 /* offset */);
+
+                    long presentationTimeUs = 0;
+
+                    if (sampleSize < 0) {
+                        Log.d(TAG, "saw input EOS.");
+                        sawInputEOS = true;
+                        sampleSize = 0;
+                    } else {
+                        presentationTimeUs = extractor.getSampleTime();
+                    }
+
+                    int flags = extractor.getSampleFlags();
+
+                    codec.queueInputBuffer(
+                            inputBufIndex,
+                            0 /* offset */,
+                            sampleSize,
+                            presentationTimeUs,
+                            sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
+
+                    if (!sawInputEOS) {
+                        extractor.advance();
+                    }
+                }
+            }
+
+            int res = codec.dequeueOutputBuffer(info, kTimeOutUs);
+
+            deadDecoderCounter++;
+            if (res >= 0) {
+                //Log.d(TAG, "got frame, size " + info.size + "/" + info.presentationTimeUs);
+
+                // Some decoders output a 0-sized buffer at the end. Disregard those.
+                if (info.size > 0) {
+                    deadDecoderCounter = 0;
+                    if (reconfigure) {
+                        // once we've gotten some data out of the decoder, reconfigure it again
+                        reconfigure = false;
+                        numframes = 0;
+                        extractor.seekTo(0, MediaExtractor.SEEK_TO_NEXT_SYNC);
+                        sawInputEOS = false;
+                        codec.stop();
+                        codec.configure(format, s /* surface */, null /* crypto */, 0 /* flags */);
+                        codec.start();
+                        codecInputBuffers = codec.getInputBuffers();
+                        codecOutputBuffers = codec.getOutputBuffers();
+                        continue;
+                    }
+
+                    if (isAudio) {
+                        // for audio, count the number of bytes that were decoded, not the number
+                        // of access units
+                        numframes += info.size;
+                    } else {
+                        // for video, count the number of video frames
+                        numframes++;
+                    }
+                }
+                int outputBufIndex = res;
+                codec.releaseOutputBuffer(outputBufIndex, true /* render */);
+
+                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                    Log.d(TAG, "saw output EOS.");
+                    sawOutputEOS = true;
+                }
+            } else if (res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                codecOutputBuffers = codec.getOutputBuffers();
+
+                Log.d(TAG, "output buffers have changed.");
+            } else if (res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                MediaFormat oformat = codec.getOutputFormat();
+
+                Log.d(TAG, "output format has changed to " + oformat);
+            } else {
+                Log.d(TAG, "no output");
+            }
+        }
+
+        codec.stop();
+        codec.release();
+        testFd.close();
+        return numframes;
+    }
+
+    public void testEOSBehaviorH264() throws Exception {
+        // this video has an I frame at 44
+        testEOSBehavior(R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz, 44);
+        testEOSBehavior(R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz, 45);
+        testEOSBehavior(R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz, 55);
+    }
+
+    public void testEOSBehaviorH263() throws Exception {
+        // this video has an I frame every 12 frames.
+        testEOSBehavior(R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz, 24);
+        testEOSBehavior(R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz, 25);
+        testEOSBehavior(R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz, 48);
+        testEOSBehavior(R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz, 50);
+    }
+
+    private void testEOSBehavior(int movie, int stopatsample) throws Exception {
+
+        int numframes = 0;
+
+        long [] checksums = new long[stopatsample];
+
+        AssetFileDescriptor testFd = mResources.openRawResourceFd(movie);
+
+        MediaExtractor extractor;
+        MediaCodec codec = null;
+        ByteBuffer[] codecInputBuffers;
+        ByteBuffer[] codecOutputBuffers;
+
+        extractor = new MediaExtractor();
+        extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(),
+                testFd.getLength());
+
+        MediaFormat format = extractor.getTrackFormat(0);
+        String mime = format.getString(MediaFormat.KEY_MIME);
+        boolean isAudio = mime.startsWith("audio/");
+
+        codec = MediaCodec.createDecoderByType(mime);
+//        if (mime.contains("avc")) {
+//            codec = MediaCodec.createByCodecName("OMX.google.h264.decoder");
+//        } else if (mime.contains("3gpp")) {
+//            codec = MediaCodec.createByCodecName("OMX.google.h263.decoder");
+//        }
+        assertNotNull("couldn't find codec", codec);
+        Log.i("@@@@", "using codec: " + codec.getName());
+        codec.configure(format, null /* surface */, null /* crypto */, 0 /* flags */);
+        codec.start();
+        codecInputBuffers = codec.getInputBuffers();
+        codecOutputBuffers = codec.getOutputBuffers();
+
+        extractor.selectTrack(0);
+
+        // start decoding
+        final long kTimeOutUs = 5000;
+        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+        boolean sawInputEOS = false;
+        boolean sawOutputEOS = false;
+        int deadDecoderCounter = 0;
+        int samplenum = 0;
+        while (!sawOutputEOS && deadDecoderCounter < 100) {
+            if (!sawInputEOS) {
+                int inputBufIndex = codec.dequeueInputBuffer(kTimeOutUs);
+
+                if (inputBufIndex >= 0) {
+                    ByteBuffer dstBuf = codecInputBuffers[inputBufIndex];
+
+                    int sampleSize =
+                        extractor.readSampleData(dstBuf, 0 /* offset */);
+//                    Log.i("@@@@", "read sample " + samplenum + ":" + extractor.getSampleFlags()
+//                            + " @ " + extractor.getSampleTime() + " size " + sampleSize);
+                    samplenum++;
+
+                    long presentationTimeUs = 0;
+
+                    if (sampleSize < 0 || samplenum >= (stopatsample + 100)) {
+                        Log.d(TAG, "saw input EOS.");
+                        sawInputEOS = true;
+                        sampleSize = 0;
+                    } else {
+                        presentationTimeUs = extractor.getSampleTime();
+                    }
+
+                    int flags = extractor.getSampleFlags();
+
+                    codec.queueInputBuffer(
+                            inputBufIndex,
+                            0 /* offset */,
+                            sampleSize,
+                            presentationTimeUs,
+                            sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
+
+                    if (!sawInputEOS) {
+                        extractor.advance();
+                    }
+                }
+            }
+
+            int res = codec.dequeueOutputBuffer(info, kTimeOutUs);
+
+            deadDecoderCounter++;
+            if (res >= 0) {
+
+                // Some decoders output a 0-sized buffer at the end. Disregard those.
+                if (info.size > 0) {
+                    deadDecoderCounter = 0;
+
+                    if (isAudio) {
+                        // for audio, count the number of bytes that were decoded, not the number
+                        // of access units
+                        numframes += info.size;
+                    } else {
+                        // for video, count the number of video frames
+                        long sum = checksum(codecOutputBuffers[res], info.size);
+                        if (numframes < checksums.length) {
+                            checksums[numframes] = sum;
+                        }
+                        numframes++;
+                    }
+                }
+//                Log.d(TAG, "got frame, size " + info.size + "/" + info.presentationTimeUs +
+//                        "/" + numframes + "/" + info.flags);
+
+                int outputBufIndex = res;
+                codec.releaseOutputBuffer(outputBufIndex, true /* render */);
+
+                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                    Log.d(TAG, "saw output EOS.");
+                    sawOutputEOS = true;
+                }
+            } else if (res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                codecOutputBuffers = codec.getOutputBuffers();
+
+                Log.d(TAG, "output buffers have changed.");
+            } else if (res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                MediaFormat oformat = codec.getOutputFormat();
+
+                Log.d(TAG, "output format has changed to " + oformat);
+            } else {
+                Log.d(TAG, "no output");
+            }
+        }
+
+        codec.stop();
+        codec.release();
+        extractor.release();
+
+
+        // We now have checksums for every frame.
+        // Now decode again, but signal EOS right before an index frame, and ensure the frames
+        // prior to that are the same.
+
+        extractor = new MediaExtractor();
+        extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(),
+                testFd.getLength());
+
+        codec = MediaCodec.createDecoderByType(mime);
+//        if (mime.contains("avc")) {
+//            codec = MediaCodec.createByCodecName("OMX.google.h264.decoder");
+//        } else if (mime.contains("3gpp")) {
+//            codec = MediaCodec.createByCodecName("OMX.google.h263.decoder");
+//        }
+        codec.configure(format, null /* surface */, null /* crypto */, 0 /* flags */);
+        codec.start();
+        codecInputBuffers = codec.getInputBuffers();
+        codecOutputBuffers = codec.getOutputBuffers();
+
+        extractor.selectTrack(0);
+
+        // start decoding
+        info = new MediaCodec.BufferInfo();
+        sawInputEOS = false;
+        sawOutputEOS = false;
+        deadDecoderCounter = 0;
+        samplenum = 0;
+        numframes = 0;
+        while (!sawOutputEOS && deadDecoderCounter < 100) {
+            if (!sawInputEOS) {
+                int inputBufIndex = codec.dequeueInputBuffer(kTimeOutUs);
+
+                if (inputBufIndex >= 0) {
+                    ByteBuffer dstBuf = codecInputBuffers[inputBufIndex];
+
+                    int sampleSize =
+                        extractor.readSampleData(dstBuf, 0 /* offset */);
+//                    Log.i("@@@@", "read sample " + samplenum + ":" + extractor.getSampleFlags()
+//                            + " @ " + extractor.getSampleTime() + " size " + sampleSize);
+                    samplenum++;
+
+                    long presentationTimeUs = extractor.getSampleTime();
+
+                    if (sampleSize < 0 || samplenum >= stopatsample) {
+                        Log.d(TAG, "saw input EOS.");
+                        sawInputEOS = true;
+                        if (sampleSize < 0) {
+                            sampleSize = 0;
+                        }
+                    }
+
+                    int flags = extractor.getSampleFlags();
+
+                    codec.queueInputBuffer(
+                            inputBufIndex,
+                            0 /* offset */,
+                            sampleSize,
+                            presentationTimeUs,
+                            sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
+
+                    if (!sawInputEOS) {
+                        extractor.advance();
+                    }
+                }
+            }
+
+            int res = codec.dequeueOutputBuffer(info, kTimeOutUs);
+
+            deadDecoderCounter++;
+            if (res >= 0) {
+
+                // Some decoders output a 0-sized buffer at the end. Disregard those.
+                if (info.size > 0) {
+                    deadDecoderCounter = 0;
+
+                    if (isAudio) {
+                        // for audio, count the number of bytes that were decoded, not the number
+                        // of access units
+                        numframes += info.size;
+                    } else {
+                        // for video, count the number of video frames
+                        long sum = checksum(codecOutputBuffers[res], info.size);
+                        if (numframes < checksums.length) {
+                            assertEquals("frame data mismatch at frame " + numframes,
+                                    checksums[numframes], sum);
+                        }
+                        numframes++;
+                    }
+                }
+//                Log.d(TAG, "got frame, size " + info.size + "/" + info.presentationTimeUs +
+//                        "/" + numframes + "/" + info.flags);
+
+                int outputBufIndex = res;
+                codec.releaseOutputBuffer(outputBufIndex, true /* render */);
+
+                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                    Log.d(TAG, "saw output EOS.");
+                    sawOutputEOS = true;
+                }
+            } else if (res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                codecOutputBuffers = codec.getOutputBuffers();
+
+                Log.d(TAG, "output buffers have changed.");
+            } else if (res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                MediaFormat oformat = codec.getOutputFormat();
+
+                Log.d(TAG, "output format has changed to " + oformat);
+            } else {
+                Log.d(TAG, "no output");
+            }
+        }
+
+        codec.stop();
+        codec.release();
+        extractor.release();
+
+        assertEquals("I!=O", samplenum, numframes);
+        assertTrue("last frame didn't have EOS", sawOutputEOS);
+        assertEquals(stopatsample, numframes);
+
+        testFd.close();
+    }
+
+    private long checksum(ByteBuffer buf, int size) {
+        assertTrue(size != 0);
+        assertTrue(size <= buf.capacity());
+        CRC32 crc = new CRC32();
+        int pos = buf.position();
+        buf.rewind();
+        for (int i = 0; i < buf.capacity(); i++) {
+            crc.update(buf.get());
+        }
+        buf.position(pos);
+        return crc.getValue();
+    }
+
 }
 
diff --git a/tests/tests/media/src/android/media/cts/EncodeDecodeTest.java b/tests/tests/media/src/android/media/cts/EncodeDecodeTest.java
new file mode 100644
index 0000000..c07427c
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/EncodeDecodeTest.java
@@ -0,0 +1,1116 @@
+/*
+ * Copyright (C) 2013 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.media.cts;
+
+import android.graphics.SurfaceTexture;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecList;
+import android.media.MediaFormat;
+import android.opengl.GLES20;
+import android.test.AndroidTestCase;
+import android.util.Log;
+import android.view.Surface;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+import javax.microedition.khronos.opengles.GL10;
+
+
+/**
+ * Generates a series of video frames, encodes them, decodes them, and tests for significant
+ * divergence from the original.
+ * <p>
+ * We copy the data from the encoder's output buffers to the decoder's input buffers, running
+ * them in parallel.  The first buffer output for video/avc contains codec configuration data,
+ * which we must carefully forward to the decoder.
+ * <p>
+ * An alternative approach would be to save the output of the decoder as an mpeg4 video
+ * file, and read it back in from disk.  The data we're generating is just an elementary
+ * stream, so we'd need to perform additional steps to make that happen.
+ */
+public class EncodeDecodeTest extends AndroidTestCase {
+    private static final String TAG = "EncodeDecodeTest";
+    private static final boolean VERBOSE = false;           // lots of logging
+    private static final boolean DEBUG_SAVE_FILE = false;   // save copy of encoded movie
+    private static final String DEBUG_FILE_NAME_BASE = "/sdcard/test.";
+
+    // parameters for the encoder
+    private static final String MIME_TYPE = "video/avc";    // H.264 Advanced Video Coding
+    private static final int BIT_RATE = 2000000;            // 2Mbps
+    private static final int FRAME_RATE = 15;               // 15fps
+    private static final int IFRAME_INTERVAL = 10;          // 10 seconds between I-frames
+
+    // movie length, in frames
+    private static final int NUM_FRAMES = 30;               // two seconds of video
+
+    private static final int TEST_Y = 120;                  // YUV values for colored rect
+    private static final int TEST_U = 160;
+    private static final int TEST_V = 200;
+    private static final int TEST_R0 = 0;                   // RGB equivalent of {0,0,0}
+    private static final int TEST_G0 = 136;
+    private static final int TEST_B0 = 0;
+    private static final int TEST_R1 = 236;                 // RGB equivalent of {120,160,200}
+    private static final int TEST_G1 = 50;
+    private static final int TEST_B1 = 186;
+
+    // size of a frame, in pixels
+    private int mWidth = -1;
+    private int mHeight = -1;
+
+
+    /**
+     * Tests streaming of AVC video through the encoder and decoder.  Data is encoded from
+     * a series of byte[] buffers and decoded into ByteBuffers.  The output is checked for
+     * validity.
+     */
+    public void testEncodeDecodeVideoFromBufferToBufferQCIF() throws Exception {
+        setSize(176, 144);
+        encodeDecodeVideoFromBuffer(false);
+    }
+    public void testEncodeDecodeVideoFromBufferToBufferQVGA() throws Exception {
+        setSize(320, 240);
+        encodeDecodeVideoFromBuffer(false);
+    }
+    public void testEncodeDecodeVideoFromBufferToBuffer720p() throws Exception {
+        setSize(1280, 720);
+        encodeDecodeVideoFromBuffer(false);
+    }
+
+    /**
+     * Tests streaming of AVC video through the encoder and decoder.  Data is encoded from
+     * a series of byte[] buffers and decoded into Surfaces.  The output is checked for
+     * validity.
+     * <p>
+     * Because of the way SurfaceTexture.OnFrameAvailableListener works, we need to run this
+     * test on a thread that doesn't have a Looper configured.  If we don't, the test will
+     * pass, but we won't actually test the output because we'll never receive the "frame
+     * available" notifications".  The CTS test framework seems to be configuring a Looper on
+     * the test thread, so we have to hand control off to a new thread for the duration of
+     * the test.
+     */
+    public void testEncodeDecodeVideoFromBufferToSurfaceQCIF() throws Throwable {
+        setSize(176, 144);
+        BufferToSurfaceWrapper.runTest(this);
+    }
+    public void testEncodeDecodeVideoFromBufferToSurfaceQVGA() throws Throwable {
+        setSize(320, 240);
+        BufferToSurfaceWrapper.runTest(this);
+    }
+    public void testEncodeDecodeVideoFromBufferToSurface720p() throws Throwable {
+        setSize(1280, 720);
+        BufferToSurfaceWrapper.runTest(this);
+    }
+
+    /** Wraps testEncodeDecodeVideoFromBuffer(true) */
+    private static class BufferToSurfaceWrapper implements Runnable {
+        private Throwable mThrowable;
+        private EncodeDecodeTest mTest;
+
+        private BufferToSurfaceWrapper(EncodeDecodeTest test) {
+            mTest = test;
+        }
+
+        public void run() {
+            try {
+                mTest.encodeDecodeVideoFromBuffer(true);
+            } catch (Throwable th) {
+                mThrowable = th;
+            }
+        }
+
+        /**
+         * Entry point.
+         */
+        public static void runTest(EncodeDecodeTest obj) throws Throwable {
+            BufferToSurfaceWrapper wrapper = new BufferToSurfaceWrapper(obj);
+            Thread th = new Thread(wrapper, "codec test");
+            th.start();
+            th.join();
+            if (wrapper.mThrowable != null) {
+                throw wrapper.mThrowable;
+            }
+        }
+    }
+
+    /**
+     * Tests streaming of AVC video through the encoder and decoder.  Data is provided through
+     * a Surface and decoded onto a Surface.  The output is checked for validity.
+     */
+    public void testEncodeDecodeVideoFromSurfaceToSurfaceQCIF() throws Throwable {
+        setSize(176, 144);
+        SurfaceToSurfaceWrapper.runTest(this);
+    }
+    public void testEncodeDecodeVideoFromSurfaceToSurfaceQVGA() throws Throwable {
+        setSize(320, 240);
+        SurfaceToSurfaceWrapper.runTest(this);
+    }
+    public void testEncodeDecodeVideoFromSurfaceToSurface720p() throws Throwable {
+        setSize(1280, 720);
+        SurfaceToSurfaceWrapper.runTest(this);
+    }
+
+    /** Wraps testEncodeDecodeVideoFromSurfaceToSurface() */
+    private static class SurfaceToSurfaceWrapper implements Runnable {
+        private Throwable mThrowable;
+        private EncodeDecodeTest mTest;
+
+        private SurfaceToSurfaceWrapper(EncodeDecodeTest test) {
+            mTest = test;
+        }
+
+        public void run() {
+            try {
+                mTest.encodeDecodeVideoFromSurfaceToSurface();
+            } catch (Throwable th) {
+                mThrowable = th;
+            }
+        }
+
+        /**
+         * Entry point.
+         */
+        public static void runTest(EncodeDecodeTest obj) throws Throwable {
+            SurfaceToSurfaceWrapper wrapper = new SurfaceToSurfaceWrapper(obj);
+            Thread th = new Thread(wrapper, "codec test");
+            th.start();
+            th.join();
+            if (wrapper.mThrowable != null) {
+                throw wrapper.mThrowable;
+            }
+        }
+    }
+
+    /**
+     * Sets the desired frame size.
+     */
+    private void setSize(int width, int height) {
+        if ((width % 16) != 0 || (height % 16) != 0) {
+            Log.w(TAG, "WARNING: width or height not multiple of 16");
+        }
+        mWidth = width;
+        mHeight = height;
+    }
+
+    /**
+     * Tests encoding and subsequently decoding video from frames generated into a buffer.
+     * <p>
+     * We encode several frames of a video test pattern using MediaCodec, then decode the
+     * output with MediaCodec and do some simple checks.
+     * <p>
+     * See http://b.android.com/37769 for a discussion of input format pitfalls.
+     */
+    private void encodeDecodeVideoFromBuffer(boolean toSurface) throws Exception {
+        MediaCodec encoder = null;
+        MediaCodec decoder = null;
+
+        try {
+            MediaCodecInfo codecInfo = selectCodec(MIME_TYPE);
+            if (codecInfo == null) {
+                // Don't fail CTS if they don't have an AVC codec (not here, anyway).
+                Log.e(TAG, "Unable to find an appropriate codec for " + MIME_TYPE);
+                return;
+            }
+            if (VERBOSE) Log.d(TAG, "found codec: " + codecInfo.getName());
+
+            int colorFormat = selectColorFormat(codecInfo, MIME_TYPE);
+            if (VERBOSE) Log.d(TAG, "found colorFormat: " + colorFormat);
+
+            // We avoid the device-specific limitations on width and height by using values that
+            // are multiples of 16, which all tested devices seem to be able to handle.
+            MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, mWidth, mHeight);
+
+            // Set some properties.  Failing to specify some of these can cause the MediaCodec
+            // configure() call to throw an unhelpful exception.
+            format.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);
+            format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
+            format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
+            format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
+            if (VERBOSE) Log.d(TAG, "format: " + format);
+
+            // Create a MediaCodec for the desired codec, then configure it as an encoder with
+            // our desired properties.
+            encoder = MediaCodec.createByCodecName(codecInfo.getName());
+            encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+            encoder.start();
+
+            // Create a MediaCodec for the decoder, just based on the MIME type.  The various
+            // format details will be passed through the csd-0 meta-data later on.
+            decoder = MediaCodec.createDecoderByType(MIME_TYPE);
+
+            doEncodeDecodeVideoFromBuffer(encoder, colorFormat, decoder, toSurface);
+        } finally {
+            if (VERBOSE) Log.d(TAG, "releasing codecs");
+            if (encoder != null) {
+                encoder.stop();
+                encoder.release();
+            }
+            if (decoder != null) {
+                decoder.stop();
+                decoder.release();
+            }
+        }
+    }
+
+    /**
+     * Tests encoding and subsequently decoding video from frames generated into a buffer.
+     * <p>
+     * We encode several frames of a video test pattern using MediaCodec, then decode the
+     * output with MediaCodec and do some simple checks.
+     */
+    private void encodeDecodeVideoFromSurfaceToSurface() throws Exception {
+        MediaCodec encoder = null;
+        MediaCodec decoder = null;
+        InputSurface inputSurface = null;
+        OutputSurface outputSurface = null;
+
+        try {
+            MediaCodecInfo codecInfo = selectCodec(MIME_TYPE);
+            if (codecInfo == null) {
+                // Don't fail CTS if they don't have an AVC codec (not here, anyway).
+                Log.e(TAG, "Unable to find an appropriate codec for " + MIME_TYPE);
+                return;
+            }
+            if (VERBOSE) Log.d(TAG, "found codec: " + codecInfo.getName());
+
+            int colorFormat = MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface;
+
+            // We avoid the device-specific limitations on width and height by using values that
+            // are multiples of 16, which all tested devices seem to be able to handle.
+            MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, mWidth, mHeight);
+
+            // Set some properties.  Failing to specify some of these can cause the MediaCodec
+            // configure() call to throw an unhelpful exception.
+            format.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);
+            format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
+            format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
+            format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
+            if (VERBOSE) Log.d(TAG, "format: " + format);
+
+            // Create the output surface.
+            outputSurface = new OutputSurface(mWidth, mHeight);
+
+            // Create a MediaCodec for the decoder, just based on the MIME type.  The various
+            // format details will be passed through the csd-0 meta-data later on.
+            decoder = MediaCodec.createDecoderByType(MIME_TYPE);
+            MediaFormat decoderFormat = MediaFormat.createVideoFormat(MIME_TYPE, mWidth, mHeight);
+            decoder.configure(format, outputSurface.getSurface(), null, 0);
+            decoder.start();
+
+            // Create a MediaCodec for the desired codec, then configure it as an encoder with
+            // our desired properties.  Request a Surface to use for input.
+            encoder = MediaCodec.createByCodecName(codecInfo.getName());
+            encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+            inputSurface = new InputSurface(encoder.createInputSurface());
+            encoder.start();
+
+            doEncodeDecodeVideoFromSurfaceToSurface(encoder, inputSurface, colorFormat, decoder, outputSurface);
+        } finally {
+            if (VERBOSE) Log.d(TAG, "releasing codecs");
+            if (inputSurface != null) {
+                inputSurface.release();
+            }
+            if (outputSurface != null) {
+                outputSurface.release();
+            }
+            if (encoder != null) {
+                encoder.stop();
+                encoder.release();
+            }
+            if (decoder != null) {
+                decoder.stop();
+                decoder.release();
+            }
+        }
+    }
+
+    /**
+     * Returns the first codec capable of encoding the specified MIME type, or null if no
+     * match was found.
+     */
+    private static MediaCodecInfo selectCodec(String mimeType) {
+        int numCodecs = MediaCodecList.getCodecCount();
+        for (int i = 0; i < numCodecs; i++) {
+            MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
+
+            if (!codecInfo.isEncoder()) {
+                continue;
+            }
+
+            String[] types = codecInfo.getSupportedTypes();
+            for (int j = 0; j < types.length; j++) {
+                if (types[j].equalsIgnoreCase(mimeType)) {
+                    return codecInfo;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns a color format that is supported by the codec and by this test code.  If no
+     * match is found, this throws a test failure -- the set of formats known to the test
+     * should be expanded for new platforms.
+     */
+    private static int selectColorFormat(MediaCodecInfo codecInfo, String mimeType) {
+        MediaCodecInfo.CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(mimeType);
+        for (int i = 0; i < capabilities.colorFormats.length; i++) {
+            int colorFormat = capabilities.colorFormats[i];
+            if (isRecognizedFormat(colorFormat)) {
+                return colorFormat;
+            }
+        }
+        fail("couldn't find a good color format for " + codecInfo.getName() + " / " + mimeType);
+        return 0;   // not reached
+    }
+
+    /**
+     * Returns true if this is a color format that this test code understands (i.e. we know how
+     * to read and generate frames in this format).
+     */
+    private static boolean isRecognizedFormat(int colorFormat) {
+        switch (colorFormat) {
+            // these are the formats we know how to handle for this test
+            case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar:
+            case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar:
+            case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar:
+            case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar:
+            case MediaCodecInfo.CodecCapabilities.COLOR_TI_FormatYUV420PackedSemiPlanar:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * Returns true if the specified color format is semi-planar YUV.  Throws an exception
+     * if the color format is not recognized (e.g. not YUV).
+     */
+    private static boolean isSemiPlanarYUV(int colorFormat) {
+        switch (colorFormat) {
+            case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar:
+            case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar:
+                return false;
+            case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar:
+            case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar:
+            case MediaCodecInfo.CodecCapabilities.COLOR_TI_FormatYUV420PackedSemiPlanar:
+                return true;
+            default:
+                throw new RuntimeException("unknown format " + colorFormat);
+        }
+    }
+
+    /**
+     * Does the actual work for encoding frames from buffers of byte[].
+     */
+    private void doEncodeDecodeVideoFromBuffer(MediaCodec encoder, int encoderColorFormat,
+            MediaCodec decoder, boolean toSurface) {
+        final int TIMEOUT_USEC = 10000;
+        ByteBuffer[] encoderInputBuffers = encoder.getInputBuffers();
+        ByteBuffer[] encoderOutputBuffers = encoder.getOutputBuffers();
+        ByteBuffer[] decoderInputBuffers = null;
+        ByteBuffer[] decoderOutputBuffers = null;
+        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+        int decoderColorFormat = -12345;    // init to invalid value
+        int generateIndex = 0;
+        int checkIndex = 0;
+        int badFrames = 0;
+        boolean decoderConfigured = false;
+        OutputSurface outputSurface = null;
+
+        // The size of a frame of video data, in the formats we handle, is stride*sliceHeight
+        // for Y, and (stride/2)*(sliceHeight/2) for each of the Cb and Cr channels.  Application
+        // of algebra and assuming that stride==width and sliceHeight==height yields:
+        byte[] frameData = new byte[mWidth * mHeight * 3 / 2];
+
+        // Just out of curiosity.
+        long rawSize = 0;
+        long encodedSize = 0;
+
+        // Save a copy to disk.  Useful for debugging the test.  Note this is a raw elementary
+        // stream, not a .mp4 file, so not all players will know what to do with it.
+        FileOutputStream outputStream = null;
+        if (DEBUG_SAVE_FILE) {
+            String fileName = DEBUG_FILE_NAME_BASE + mWidth + "x" + mHeight + ".mp4";
+            try {
+                outputStream = new FileOutputStream(fileName);
+                Log.d(TAG, "encoded output will be saved as " + fileName);
+            } catch (IOException ioe) {
+                Log.w(TAG, "Unable to create debug output file " + fileName);
+                throw new RuntimeException(ioe);
+            }
+        }
+
+        if (toSurface) {
+            outputSurface = new OutputSurface(mWidth, mHeight);
+        }
+
+        // Loop until the output side is done.
+        boolean inputDone = false;
+        boolean encoderDone = false;
+        boolean outputDone = false;
+        while (!outputDone) {
+            if (VERBOSE) Log.d(TAG, "loop");
+
+            // If we're not done submitting frames, generate a new one and submit it.  By
+            // doing this on every loop we're working to ensure that the encoder always has
+            // work to do.
+            //
+            // We don't really want a timeout here, but sometimes there's a delay opening
+            // the encoder device, so a short timeout can keep us from spinning hard.
+            if (!inputDone) {
+                int inputBufIndex = encoder.dequeueInputBuffer(TIMEOUT_USEC);
+                if (VERBOSE) Log.d(TAG, "inputBufIndex=" + inputBufIndex);
+                if (inputBufIndex >= 0) {
+                    long ptsUsec = computePresentationTime(generateIndex);
+                    if (generateIndex == NUM_FRAMES) {
+                        // Send an empty frame with the end-of-stream flag set.  If we set EOS
+                        // on a frame with data, that frame data will be ignored, and the
+                        // output will be short one frame.
+                        encoder.queueInputBuffer(inputBufIndex, 0, 0, ptsUsec,
+                                MediaCodec.BUFFER_FLAG_END_OF_STREAM);
+                        inputDone = true;
+                        if (VERBOSE) Log.d(TAG, "sent input EOS (with zero-length frame)");
+                    } else {
+                        generateFrame(generateIndex, encoderColorFormat, frameData);
+
+                        ByteBuffer inputBuf = encoderInputBuffers[inputBufIndex];
+                        // the buffer should be sized to hold one full frame
+                        assertTrue(inputBuf.capacity() >= frameData.length);
+                        inputBuf.clear();
+                        inputBuf.put(frameData);
+
+                        encoder.queueInputBuffer(inputBufIndex, 0, frameData.length, ptsUsec, 0);
+                        if (VERBOSE) Log.d(TAG, "submitted frame " + generateIndex + " to enc");
+                    }
+                    generateIndex++;
+                } else {
+                    // either all in use, or we timed out during initial setup
+                    if (VERBOSE) Log.d(TAG, "input buffer not available");
+                }
+            }
+
+            // Check for output from the encoder.  If there's no output yet, we either need to
+            // provide more input, or we need to wait for the encoder to work its magic.  We
+            // can't actually tell which is the case, so if we can't get an output buffer right
+            // away we loop around and see if it wants more input.
+            //
+            // Once we get EOS from the encoder, we don't need to do this anymore.
+            if (!encoderDone) {
+                int encoderStatus = encoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
+                if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
+                    // no output available yet
+                    if (VERBOSE) Log.d(TAG, "no output from encoder available");
+                } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                    // not expected for an encoder
+                    encoderOutputBuffers = encoder.getOutputBuffers();
+                    if (VERBOSE) Log.d(TAG, "encoder output buffers changed");
+                } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                    // not expected for an encoder
+                    MediaFormat newFormat = encoder.getOutputFormat();
+                    if (VERBOSE) Log.d(TAG, "encoder output format changed: " + newFormat);
+                } else if (encoderStatus < 0) {
+                    fail("unexpected result from encoder.dequeueOutputBuffer: " + encoderStatus);
+                } else { // encoderStatus >= 0
+                    ByteBuffer encodedData = encoderOutputBuffers[encoderStatus];
+                    if (encodedData == null) {
+                        fail("encoderOutputBuffer " + encoderStatus + " was null");
+                    }
+
+                    // It's usually necessary to adjust the ByteBuffer values to match BufferInfo.
+                    encodedData.position(info.offset);
+                    encodedData.limit(info.offset + info.size);
+
+                    encodedSize += info.size;
+                    if (outputStream != null) {
+                        byte[] data = new byte[info.size];
+                        encodedData.get(data);
+                        encodedData.position(info.offset);
+                        try {
+                            outputStream.write(data);
+                        } catch (IOException ioe) {
+                            Log.w(TAG, "failed writing debug data to file");
+                            throw new RuntimeException(ioe);
+                        }
+                    }
+                    if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
+                        // Codec config info.  Only expected on first packet.  One way to
+                        // handle this is to manually stuff the data into the MediaFormat
+                        // and pass that to configure().  We do that here to exercise the API.
+                        assertFalse(decoderConfigured);
+                        MediaFormat format =
+                                MediaFormat.createVideoFormat(MIME_TYPE, mWidth, mHeight);
+                        format.setByteBuffer("csd-0", encodedData);
+                        decoder.configure(format, toSurface ? outputSurface.getSurface() : null,
+                                null, 0);
+                        decoder.start();
+                        decoderInputBuffers = decoder.getInputBuffers();
+                        decoderOutputBuffers = decoder.getOutputBuffers();
+                        decoderConfigured = true;
+                        if (VERBOSE) Log.d(TAG, "decoder configured (" + info.size + " bytes)");
+                    } else {
+                        // Get a decoder input buffer, blocking until it's available.
+                        assertTrue(decoderConfigured);
+                        int inputBufIndex = decoder.dequeueInputBuffer(-1);
+                        ByteBuffer inputBuf = decoderInputBuffers[inputBufIndex];
+                        inputBuf.clear();
+                        inputBuf.put(encodedData);
+                        decoder.queueInputBuffer(inputBufIndex, 0, info.size,
+                                info.presentationTimeUs, info.flags);
+
+                        encoderDone = (info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
+                        if (VERBOSE) Log.d(TAG, "passed " + info.size + " bytes to decoder"
+                                + (encoderDone ? " (EOS)" : ""));
+                    }
+
+                    encoder.releaseOutputBuffer(encoderStatus, false);
+                }
+            }
+
+            // Check for output from the decoder.  We want to do this on every loop to avoid
+            // the possibility of stalling the pipeline.  We use a short timeout to avoid
+            // burning CPU if the decoder is hard at work but the next frame isn't quite ready.
+            //
+            // If we're decoding to a Surface, we'll get notified here as usual but the
+            // ByteBuffer references will be null.  The data is sent to Surface instead.
+            if (decoderConfigured) {
+                int decoderStatus = decoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
+                if (decoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
+                    // no output available yet
+                    if (VERBOSE) Log.d(TAG, "no output from decoder available");
+                } else if (decoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                    // The storage associated with the direct ByteBuffer may already be unmapped,
+                    // so attempting to access data through the old output buffer array could
+                    // lead to a native crash.
+                    if (VERBOSE) Log.d(TAG, "decoder output buffers changed");
+                    decoderOutputBuffers = decoder.getOutputBuffers();
+                } else if (decoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                    // this happens before the first frame is returned
+                    MediaFormat decoderOutputFormat = decoder.getOutputFormat();
+                    decoderColorFormat =
+                            decoderOutputFormat.getInteger(MediaFormat.KEY_COLOR_FORMAT);
+                    if (VERBOSE) Log.d(TAG, "decoder output format changed: " +
+                            decoderOutputFormat);
+                } else if (decoderStatus < 0) {
+                    fail("unexpected result from deocder.dequeueOutputBuffer: " + decoderStatus);
+                } else {  // decoderStatus >= 0
+                    if (!toSurface) {
+                        ByteBuffer outputFrame = decoderOutputBuffers[decoderStatus];
+
+                        outputFrame.position(info.offset);
+                        outputFrame.limit(info.offset + info.size);
+
+                        rawSize += info.size;
+                        if (info.size == 0) {
+                            if (VERBOSE) Log.d(TAG, "got empty frame");
+                        } else {
+                            if (VERBOSE) Log.d(TAG, "decoded, checking frame " + checkIndex);
+                            assertEquals("Wrong time stamp", computePresentationTime(checkIndex),
+                                    info.presentationTimeUs);
+                            if (!checkFrame(checkIndex++, decoderColorFormat, outputFrame)) {
+                                badFrames++;
+                            }
+                        }
+
+                        if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                            if (VERBOSE) Log.d(TAG, "output EOS");
+                            outputDone = true;
+                        }
+                        decoder.releaseOutputBuffer(decoderStatus, false /*render*/);
+                    } else {
+                        if (VERBOSE) Log.d(TAG, "surface decoder given buffer " + decoderStatus +
+                                " (size=" + info.size + ")");
+                        rawSize += info.size;
+                        if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                            if (VERBOSE) Log.d(TAG, "output EOS");
+                            outputDone = true;
+                        }
+
+                        boolean doRender = (info.size != 0);
+
+                        // As soon as we call releaseOutputBuffer, the buffer will be forwarded
+                        // to SurfaceTexture to convert to a texture.  The API doesn't guarantee
+                        // that the texture will be available before the call returns, so we
+                        // need to wait for the onFrameAvailable callback to fire.
+                        decoder.releaseOutputBuffer(decoderStatus, doRender);
+                        if (doRender) {
+                            if (VERBOSE) Log.d(TAG, "awaiting frame " + checkIndex);
+                            assertEquals("Wrong time stamp", computePresentationTime(checkIndex),
+                                    info.presentationTimeUs);
+                            outputSurface.awaitNewImage();
+                            outputSurface.drawImage();
+                            if (!checkSurfaceFrame(checkIndex++)) {
+                                badFrames++;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        if (VERBOSE) Log.d(TAG, "decoded " + checkIndex + " frames at "
+                + mWidth + "x" + mHeight + ": raw=" + rawSize + ", enc=" + encodedSize);
+        if (outputStream != null) {
+            try {
+                outputStream.close();
+            } catch (IOException ioe) {
+                Log.w(TAG, "failed closing debug file");
+                throw new RuntimeException(ioe);
+            }
+        }
+
+        if (outputSurface != null) {
+            outputSurface.release();
+        }
+
+        if (checkIndex != NUM_FRAMES) {
+            fail("expected " + NUM_FRAMES + " frames, only decoded " + checkIndex);
+        }
+        if (badFrames != 0) {
+            fail("Found " + badFrames + " bad frames");
+        }
+    }
+
+    /**
+     * Does the actual work for encoding and decoding from Surface to Surface.
+     */
+    private void doEncodeDecodeVideoFromSurfaceToSurface(MediaCodec encoder,
+            InputSurface inputSurface, int encoderColorFormat, MediaCodec decoder,
+            OutputSurface outputSurface) {
+        final int TIMEOUT_USEC = 10000;
+        ByteBuffer[] encoderOutputBuffers = encoder.getOutputBuffers();
+        ByteBuffer[] decoderInputBuffers = decoder.getInputBuffers();
+        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+        int generateIndex = 0;
+        int checkIndex = 0;
+        int badFrames = 0;
+
+        // Save a copy to disk.  Useful for debugging the test.  Note this is a raw elementary
+        // stream, not a .mp4 file, so not all players will know what to do with it.
+        FileOutputStream outputStream = null;
+        if (DEBUG_SAVE_FILE) {
+            String fileName = DEBUG_FILE_NAME_BASE + mWidth + "x" + mHeight + ".mp4";
+            try {
+                outputStream = new FileOutputStream(fileName);
+                Log.d(TAG, "encoded output will be saved as " + fileName);
+            } catch (IOException ioe) {
+                Log.w(TAG, "Unable to create debug output file " + fileName);
+                throw new RuntimeException(ioe);
+            }
+        }
+
+        // Loop until the output side is done.
+        boolean inputDone = false;
+        boolean encoderDone = false;
+        boolean outputDone = false;
+        while (!outputDone) {
+            if (VERBOSE) Log.d(TAG, "loop");
+
+            // If we're not done submitting frames, generate a new one and submit it.  The
+            // eglSwapBuffers call will block if the input is full.
+            if (!inputDone) {
+                if (generateIndex == NUM_FRAMES) {
+                    // Send an empty frame with the end-of-stream flag set.
+                    if (VERBOSE) Log.d(TAG, "signaling input EOS");
+                    encoder.signalEndOfInputStream();
+                    inputDone = true;
+                } else {
+                    inputSurface.makeCurrent();
+                    generateSurfaceFrame(generateIndex);
+                    inputSurface.setPresentationTime(computePresentationTime(generateIndex));
+                    if (VERBOSE) Log.d(TAG, "inputSurface swapBuffers");
+                    inputSurface.swapBuffers();
+                }
+                generateIndex++;
+            }
+
+            // Assume output is available.  Loop until both assumptions are false.
+            boolean decoderOutputAvailable = true;
+            boolean encoderOutputAvailable = !encoderDone;
+            while (decoderOutputAvailable || encoderOutputAvailable) {
+                // Start by draining any pending output from the decoder.  It's important to
+                // do this before we try to stuff any more data in.
+                int decoderStatus = decoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
+                if (decoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
+                    // no output available yet
+                    if (VERBOSE) Log.d(TAG, "no output from decoder available");
+                    decoderOutputAvailable = false;
+                } else if (decoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                    if (VERBOSE) Log.d(TAG, "decoder output buffers changed (but we don't care)");
+                } else if (decoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                    // this happens before the first frame is returned
+                    MediaFormat decoderOutputFormat = decoder.getOutputFormat();
+                    if (VERBOSE) Log.d(TAG, "decoder output format changed: " +
+                            decoderOutputFormat);
+                } else if (decoderStatus < 0) {
+                    fail("unexpected result from deocder.dequeueOutputBuffer: " + decoderStatus);
+                } else {  // decoderStatus >= 0
+                    if (VERBOSE) Log.d(TAG, "surface decoder given buffer " + decoderStatus +
+                            " (size=" + info.size + ")");
+                    if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                        if (VERBOSE) Log.d(TAG, "output EOS");
+                        outputDone = true;
+                    }
+
+                    // The ByteBuffers are null references, but we still get a nonzero size for
+                    // the decoded data.
+                    boolean doRender = (info.size != 0);
+
+                    // As soon as we call releaseOutputBuffer, the buffer will be forwarded
+                    // to SurfaceTexture to convert to a texture.  The API doesn't guarantee
+                    // that the texture will be available before the call returns, so we
+                    // need to wait for the onFrameAvailable callback to fire.  If we don't
+                    // wait, we risk dropping frames.
+                    outputSurface.makeCurrent();
+                    decoder.releaseOutputBuffer(decoderStatus, doRender);
+                    if (doRender) {
+                        assertEquals("Wrong time stamp", computePresentationTime(checkIndex),
+                                info.presentationTimeUs);
+                        if (VERBOSE) Log.d(TAG, "awaiting frame " + checkIndex);
+                        outputSurface.awaitNewImage();
+                        outputSurface.drawImage();
+                        if (!checkSurfaceFrame(checkIndex++)) {
+                            badFrames++;
+                        }
+                    }
+                }
+                if (decoderStatus != MediaCodec.INFO_TRY_AGAIN_LATER) {
+                    // Continue attempts to drain output.
+                    continue;
+                }
+
+                // Decoder is drained, check to see if we've got a new buffer of output from
+                // the encoder.
+                if (!encoderDone) {
+                    int encoderStatus = encoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
+                    if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
+                        // no output available yet
+                        if (VERBOSE) Log.d(TAG, "no output from encoder available");
+                        encoderOutputAvailable = false;
+                    } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                        // not expected for an encoder
+                        encoderOutputBuffers = encoder.getOutputBuffers();
+                        if (VERBOSE) Log.d(TAG, "encoder output buffers changed");
+                    } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                        // not expected for an encoder
+                        MediaFormat newFormat = encoder.getOutputFormat();
+                        if (VERBOSE) Log.d(TAG, "encoder output format changed: " + newFormat);
+                    } else if (encoderStatus < 0) {
+                        fail("unexpected result from encoder.dequeueOutputBuffer: " + encoderStatus);
+                    } else { // encoderStatus >= 0
+                        ByteBuffer encodedData = encoderOutputBuffers[encoderStatus];
+                        if (encodedData == null) {
+                            fail("encoderOutputBuffer " + encoderStatus + " was null");
+                        }
+
+                        // It's usually necessary to adjust the ByteBuffer values to match BufferInfo.
+                        encodedData.position(info.offset);
+                        encodedData.limit(info.offset + info.size);
+
+                        if (outputStream != null) {
+                            byte[] data = new byte[info.size];
+                            encodedData.get(data);
+                            encodedData.position(info.offset);
+                            try {
+                                outputStream.write(data);
+                            } catch (IOException ioe) {
+                                Log.w(TAG, "failed writing debug data to file");
+                                throw new RuntimeException(ioe);
+                            }
+                        }
+
+                        // Get a decoder input buffer, blocking until it's available.  We just
+                        // drained the decoder output, so we expect there to be a free input
+                        // buffer now or in the near future (i.e. this should never deadlock
+                        // if the codec is meeting requirements).
+                        //
+                        // The first buffer of data we get will have the BUFFER_FLAG_CODEC_CONFIG
+                        // flag set; the decoder will see this and finish configuring itself.
+                        int inputBufIndex = decoder.dequeueInputBuffer(-1);
+                        ByteBuffer inputBuf = decoderInputBuffers[inputBufIndex];
+                        inputBuf.clear();
+                        inputBuf.put(encodedData);
+                        decoder.queueInputBuffer(inputBufIndex, 0, info.size,
+                                info.presentationTimeUs, info.flags);
+
+                        // If everything from the encoder has been passed to the decoder, we
+                        // can stop polling the encoder output.  (This just an optimization.)
+                        if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                            encoderDone = true;
+                            encoderOutputAvailable = false;
+                        }
+                        if (VERBOSE) Log.d(TAG, "passed " + info.size + " bytes to decoder"
+                                + (encoderDone ? " (EOS)" : ""));
+
+                        encoder.releaseOutputBuffer(encoderStatus, false);
+                    }
+                }
+            }
+        }
+
+        if (outputStream != null) {
+            try {
+                outputStream.close();
+            } catch (IOException ioe) {
+                Log.w(TAG, "failed closing debug file");
+                throw new RuntimeException(ioe);
+            }
+        }
+
+        if (checkIndex != NUM_FRAMES) {
+            fail("expected " + NUM_FRAMES + " frames, only decoded " + checkIndex);
+        }
+        if (badFrames != 0) {
+            fail("Found " + badFrames + " bad frames");
+        }
+    }
+
+
+    /**
+     * Generates data for frame N into the supplied buffer.  We have an 8-frame animation
+     * sequence that wraps around.  It looks like this:
+     * <pre>
+     *   0 1 2 3
+     *   7 6 5 4
+     * </pre>
+     * We draw one of the eight rectangles and leave the rest set to the zero-fill color.
+     */
+    private void generateFrame(int frameIndex, int colorFormat, byte[] frameData) {
+        final int HALF_WIDTH = mWidth / 2;
+        boolean semiPlanar = isSemiPlanarYUV(colorFormat);
+
+        // Set to zero.  In YUV this is a dull green.
+        Arrays.fill(frameData, (byte) 0);
+
+        int startX, startY, countX, countY;
+
+        frameIndex %= 8;
+        //frameIndex = (frameIndex / 8) % 8;    // use this instead for debug -- easier to see
+        if (frameIndex < 4) {
+            startX = frameIndex * (mWidth / 4);
+            startY = 0;
+        } else {
+            startX = (7 - frameIndex) * (mWidth / 4);
+            startY = mHeight / 2;
+        }
+
+        for (int y = startY + (mHeight/2) - 1; y >= startY; --y) {
+            for (int x = startX + (mWidth/4) - 1; x >= startX; --x) {
+                if (semiPlanar) {
+                    // full-size Y, followed by UV pairs at half resolution
+                    // e.g. Nexus 4 OMX.qcom.video.encoder.avc COLOR_FormatYUV420SemiPlanar
+                    // e.g. Galaxy Nexus OMX.TI.DUCATI1.VIDEO.H264E
+                    //        OMX_TI_COLOR_FormatYUV420PackedSemiPlanar
+                    frameData[y * mWidth + x] = (byte) TEST_Y;
+                    if ((x & 0x01) == 0 && (y & 0x01) == 0) {
+                        frameData[mWidth*mHeight + y * HALF_WIDTH + x] = (byte) TEST_U;
+                        frameData[mWidth*mHeight + y * HALF_WIDTH + x + 1] = (byte) TEST_V;
+                    }
+                } else {
+                    // full-size Y, followed by quarter-size U and quarter-size V
+                    // e.g. Nexus 10 OMX.Exynos.AVC.Encoder COLOR_FormatYUV420Planar
+                    // e.g. Nexus 7 OMX.Nvidia.h264.encoder COLOR_FormatYUV420Planar
+                    frameData[y * mWidth + x] = (byte) TEST_Y;
+                    if ((x & 0x01) == 0 && (y & 0x01) == 0) {
+                        frameData[mWidth*mHeight + (y/2) * HALF_WIDTH + (x/2)] = (byte) TEST_U;
+                        frameData[mWidth*mHeight + HALF_WIDTH * (mHeight / 2) +
+                                  (y/2) * HALF_WIDTH + (x/2)] = (byte) TEST_V;
+                    }
+                }
+            }
+        }
+
+        if (false) {
+            // make sure that generate and check agree
+            if (!checkFrame(frameIndex, colorFormat, ByteBuffer.wrap(frameData))) {
+                fail("spot check failed");
+            }
+        }
+    }
+
+    /**
+     * Performs a simple check to see if the frame is more or less right.
+     * <p>
+     * See {@link generateFrame} for a description of the layout.  The idea is to sample
+     * one pixel from the middle of the 8 regions, and verify that the correct one has
+     * the non-background color.  We can't know exactly what the video encoder has done
+     * with our frames, so we just check to see if it looks like more or less the right thing.
+     *
+     * @return true if the frame looks good
+     */
+    private boolean checkFrame(int frameIndex, int colorFormat, ByteBuffer frameData) {
+        // Check for color formats we don't understand.  There is no requirement for video
+        // decoders to use a "mundane" format, so we just give a pass on proprietary formats.
+        // e.g. Nexus 4 0x7FA30C03 OMX_QCOM_COLOR_FormatYUV420PackedSemiPlanar64x32Tile2m8ka
+        if (!isRecognizedFormat(colorFormat)) {
+            Log.d(TAG, "unable to check frame contents for colorFormat=" +
+                    Integer.toHexString(colorFormat));
+            return true;
+        }
+
+        final int HALF_WIDTH = mWidth / 2;
+        boolean frameFailed = false;
+        boolean semiPlanar = isSemiPlanarYUV(colorFormat);
+
+        for (int i = 0; i < 8; i++) {
+            int x, y;
+            if (i < 4) {
+                x = i * (mWidth / 4) + (mWidth / 8);
+                y = mHeight / 4;
+            } else {
+                x = (7 - i) * (mWidth / 4) + (mWidth / 8);
+                y = (mHeight * 3) / 4;
+            }
+
+            int testY, testU, testV;
+            if (semiPlanar) {
+                // Galaxy Nexus uses OMX_TI_COLOR_FormatYUV420PackedSemiPlanar
+                testY = frameData.get(y * mWidth + x) & 0xff;
+                testU = frameData.get(mWidth*mHeight + 2*(y/2) * HALF_WIDTH + 2*(x/2)) & 0xff;
+                testV = frameData.get(mWidth*mHeight + 2*(y/2) * HALF_WIDTH + 2*(x/2) + 1) & 0xff;
+            } else {
+                // Nexus 10, Nexus 7 use COLOR_FormatYUV420Planar
+                testY = frameData.get(y * mWidth + x) & 0xff;
+                testU = frameData.get(mWidth*mHeight + (y/2) * HALF_WIDTH + (x/2)) & 0xff;
+                testV = frameData.get(mWidth*mHeight + HALF_WIDTH * (mHeight / 2) +
+                        (y/2) * HALF_WIDTH + (x/2)) & 0xff;
+            }
+
+            int expY, expU, expV;
+            if (i == frameIndex % 8) {
+                // colored rect
+                expY = TEST_Y;
+                expU = TEST_U;
+                expV = TEST_V;
+            } else {
+                // should be our zeroed-out buffer
+                expY = expU = expV = 0;
+            }
+            if (!isColorClose(testY, expY) ||
+                    !isColorClose(testU, expU) ||
+                    !isColorClose(testV, expV)) {
+                Log.w(TAG, "Bad frame " + frameIndex + " (rect=" + i + ": yuv=" + testY +
+                        "," + testU + "," + testV + " vs. expected " + expY + "," + expU +
+                        "," + expV + ")");
+                frameFailed = true;
+            }
+        }
+
+        return !frameFailed;
+    }
+
+    /**
+     * Generates a frame of data using GL commands.
+     */
+    private void generateSurfaceFrame(int frameIndex) {
+        frameIndex %= 8;
+
+        int startX, startY;
+        if (frameIndex < 4) {
+            // (0,0) is bottom-left in GL
+            startX = frameIndex * (mWidth / 4);
+            startY = mHeight / 2;
+        } else {
+            startX = (7 - frameIndex) * (mWidth / 4);
+            startY = 0;
+        }
+
+        GLES20.glDisable(GLES20.GL_SCISSOR_TEST);
+        GLES20.glClearColor(TEST_R0 / 255.0f, TEST_G0 / 255.0f, TEST_B0 / 255.0f, 1.0f);
+        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+        GLES20.glEnable(GLES20.GL_SCISSOR_TEST);
+        GLES20.glScissor(startX, startY, mWidth / 4, mHeight / 2);
+        GLES20.glClearColor(TEST_R1 / 255.0f, TEST_G1 / 255.0f, TEST_B1 / 255.0f, 1.0f);
+        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+    }
+
+    /**
+     * Checks the frame for correctness.  Similar to {@link checkFrame}, but uses GL to
+     * read pixels from the current surface.
+     *
+     * @return true if the frame looks good
+     */
+    private boolean checkSurfaceFrame(int frameIndex) {
+        ByteBuffer pixelBuf = ByteBuffer.allocateDirect(4); // TODO - reuse this
+        boolean frameFailed = false;
+
+        for (int i = 0; i < 8; i++) {
+            // Note the coordinates are inverted on the Y-axis in GL.
+            int x, y;
+            if (i < 4) {
+                x = i * (mWidth / 4) + (mWidth / 8);
+                y = (mHeight * 3) / 4;
+            } else {
+                x = (7 - i) * (mWidth / 4) + (mWidth / 8);
+                y = mHeight / 4;
+            }
+
+            GLES20.glReadPixels(x, y, 1, 1, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, pixelBuf);
+            int r = pixelBuf.get(0) & 0xff;
+            int g = pixelBuf.get(1) & 0xff;
+            int b = pixelBuf.get(2) & 0xff;
+            //Log.d(TAG, "GOT(" + frameIndex + "/" + i + "): r=" + r + " g=" + g + " b=" + b);
+
+            int expR, expG, expB;
+            if (i == frameIndex % 8) {
+                // colored rect
+                expR = TEST_R1;
+                expG = TEST_G1;
+                expB = TEST_B1;
+            } else {
+                // zero background color
+                expR = TEST_R0;
+                expG = TEST_G0;
+                expB = TEST_B0;
+            }
+            if (!isColorClose(r, expR) ||
+                    !isColorClose(g, expG) ||
+                    !isColorClose(b, expB)) {
+                Log.w(TAG, "Bad frame " + frameIndex + " (rect=" + i + ": rgb=" + r +
+                        "," + g + "," + b + " vs. expected " + expR + "," + expG +
+                        "," + expB + ")");
+                frameFailed = true;
+            }
+        }
+
+        return !frameFailed;
+    }
+
+    /**
+     * Returns true if the actual color value is close to the expected color value.
+     */
+    static boolean isColorClose(int actual, int expected) {
+        final int MAX_DELTA = 8;
+        if (expected < MAX_DELTA) {
+            return actual < (expected + MAX_DELTA);
+        } else if (expected > (255 - MAX_DELTA)) {
+            return actual > (expected - MAX_DELTA);
+        } else {
+            return actual > (expected - MAX_DELTA) && actual < (expected + MAX_DELTA);
+        }
+    }
+
+    /**
+     * Generates the presentation time for frame N.
+     */
+    private static long computePresentationTime(int frameIndex) {
+        return 132 + frameIndex * 1000000 / FRAME_RATE;
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/InputSurface.java b/tests/tests/media/src/android/media/cts/InputSurface.java
new file mode 100644
index 0000000..ff6ece1
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/InputSurface.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2013 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.media.cts;
+
+import android.opengl.EGL14;
+import android.opengl.EGLConfig;
+import android.opengl.EGLContext;
+import android.opengl.EGLDisplay;
+import android.opengl.EGLSurface;
+import android.opengl.GLES10;
+import android.opengl.GLES11Ext;
+import android.opengl.GLES20;
+import android.opengl.Matrix;
+import android.util.Log;
+import android.view.Surface;
+
+import java.nio.ByteBuffer;
+
+
+/**
+ * Holds state associated with a Surface used for MediaCodec encoder input.
+ * <p>
+ * The constructor takes a Surface obtained from MediaCodec.createInputSurface(), and uses that
+ * to create an EGL window surface.  Calls to eglSwapBuffers() cause a frame of data to be sent
+ * to the video encoder.
+ */
+class InputSurface {
+    private static final String TAG = "InputSurface";
+    private static final boolean VERBOSE = false;
+
+    private static final int EGL_RECORDABLE_ANDROID = 0x3142;
+    private static final int EGL_OPENGL_ES2_BIT = 4;
+
+    private EGLDisplay mEGLDisplay;
+    private EGLContext mEGLContext;
+    private EGLSurface mEGLSurface;
+
+    private Surface mSurface;
+
+    /**
+     * Creates an InputSurface from a Surface.
+     */
+    public InputSurface(Surface surface) {
+        if (surface == null) {
+            throw new NullPointerException();
+        }
+        mSurface = surface;
+
+        eglSetup();
+    }
+
+    /**
+     * Prepares EGL.  We want a GLES 2.0 context and a surface that supports recording.
+     */
+    private void eglSetup() {
+        mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
+        if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
+            throw new RuntimeException("unable to get EGL14 display");
+        }
+        int[] version = new int[2];
+        if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) {
+            mEGLDisplay = null;
+            throw new RuntimeException("unable to initialize EGL14");
+        }
+
+        // Configure EGL for pbuffer and OpenGL ES 2.0.  We want enough RGB bits
+        // to be able to tell if the frame is reasonable.
+        int[] attribList = {
+                EGL14.EGL_RED_SIZE, 8,
+                EGL14.EGL_GREEN_SIZE, 8,
+                EGL14.EGL_BLUE_SIZE, 8,
+                EGL14.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+                EGL_RECORDABLE_ANDROID, 1,
+                EGL14.EGL_NONE
+        };
+        EGLConfig[] configs = new EGLConfig[1];
+        int[] numConfigs = new int[1];
+        if (!EGL14.eglChooseConfig(mEGLDisplay, attribList, 0, configs, 0, configs.length,
+                numConfigs, 0)) {
+            throw new RuntimeException("unable to find RGB888+recordable ES2 EGL config");
+        }
+
+        // Configure context for OpenGL ES 2.0.
+        int[] attrib_list = {
+                EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
+                EGL14.EGL_NONE
+        };
+        mEGLContext = EGL14.eglCreateContext(mEGLDisplay, configs[0], EGL14.EGL_NO_CONTEXT,
+                attrib_list, 0);
+        checkEglError("eglCreateContext");
+        if (mEGLContext == null) {
+            throw new RuntimeException("null context");
+        }
+
+        // Create a window surface, and attach it to the Surface we received.
+        int[] surfaceAttribs = {
+                EGL14.EGL_NONE
+        };
+        mEGLSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, configs[0], mSurface,
+                surfaceAttribs, 0);
+        checkEglError("eglCreateWindowSurface");
+        if (mEGLSurface == null) {
+            throw new RuntimeException("surface was null");
+        }
+    }
+
+    /**
+     * Discard all resources held by this class, notably the EGL context.  Also releases the
+     * Surface that was passed to our constructor.
+     */
+    public void release() {
+        if (EGL14.eglGetCurrentContext() == mEGLContext) {
+            // Clear the current context and surface to ensure they are discarded immediately.
+            EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
+                    EGL14.EGL_NO_CONTEXT);
+        }
+        EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface);
+        EGL14.eglDestroyContext(mEGLDisplay, mEGLContext);
+        //EGL14.eglTerminate(mEGLDisplay);
+
+        mSurface.release();
+
+        // null everything out so future attempts to use this object will cause an NPE
+        mEGLDisplay = null;
+        mEGLContext = null;
+        mEGLSurface = null;
+
+        mSurface = null;
+    }
+
+    /**
+     * Makes our EGL context and surface current.
+     */
+    public void makeCurrent() {
+        if (!EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) {
+            throw new RuntimeException("eglMakeCurrent failed");
+        }
+    }
+
+    /**
+     * Calls eglSwapBuffers.  Use this to "publish" the current frame.
+     */
+    public boolean swapBuffers() {
+        return EGL14.eglSwapBuffers(mEGLDisplay, mEGLSurface);
+    }
+
+    /**
+     * Returns the Surface that the MediaCodec receives buffers from.
+     */
+    public Surface getSurface() {
+        return mSurface;
+    }
+
+    /**
+     * Sends the presentation time stamp to EGL.
+     */
+    public void setPresentationTime(long when) {
+        EGL14.eglPresentationTimeANDROID(mEGLDisplay, mEGLSurface, when);
+    }
+
+    /**
+     * Checks for EGL errors.
+     */
+    private void checkEglError(String msg) {
+        boolean failed = false;
+        int error;
+        while ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) {
+            Log.e(TAG, msg + ": EGL error: 0x" + Integer.toHexString(error));
+            failed = true;
+        }
+        if (failed) {
+            throw new RuntimeException("EGL error encountered (see log)");
+        }
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/IvfReader.java b/tests/tests/media/src/android/media/cts/IvfReader.java
new file mode 100644
index 0000000..508ae25
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/IvfReader.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2013 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.media.cts;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+/**
+ * A simple reader for an IVF file.
+ *
+ * IVF format is a simple container format for VP8 encoded frames.
+ * This reader is capable of getting frame count, width and height
+ * from the header, and access individual frames randomly by
+ * frame number.
+ */
+
+public class IvfReader {
+    private static final byte HEADER_END = 32;
+    private static final byte FOURCC_HEAD = 8;
+    private static final byte WIDTH_HEAD = 12;
+    private static final byte HEIGHT_HEAD = 14;
+    private static final byte FRAMECOUNT_HEAD = 24;
+    private static final byte FRAME_HEADER_SIZE = 12;
+
+    private RandomAccessFile mIvfFile;
+    private boolean mHeaderValid;
+    private int mWidth;
+    private int mHeight;
+    private int mFrameCount;
+    private int[] mFrameHeads;  // Head of frame header
+    private int[] mFrameSizes;  // Frame size excluding header
+
+
+    /**
+     * Initializes the IVF file reader.
+     *
+     * Only minimal verification is done to check if this
+     * is indeed a valid IVF file. (fourcc, signature)
+     *
+     * All frame headers are read in advance.
+     *
+     * @param filename   name of the IVF file
+     */
+    public IvfReader(String filename) throws IOException{
+        mIvfFile = new RandomAccessFile(filename, "r");
+
+        mHeaderValid = verifyHeader();
+        readHeaderData();
+        readFrameMetadata();
+    }
+
+    /**
+     * Tells if file header seems to be valid.
+     *
+     * Only minimal verification is done to check if this
+     * is indeed a valid IVF file. (fourcc, signature)
+     */
+    public boolean isHeaderValid(){
+        return mHeaderValid;
+    }
+
+    /**
+     * Returns frame width according to header information.
+     */
+    public int getWidth(){
+        return mWidth;
+    }
+
+    /**
+     * Returns frame height according to header information.
+     */
+    public int getHeight(){
+        return mHeight;
+    }
+
+    /**
+     * Returns frame count according to header information.
+     */
+    public int getFrameCount(){
+        return mFrameCount;
+    }
+
+    /**
+     * Returns frame data by index.
+     *
+     * @param frameIndex index of the frame to read, greater-equal
+     * than 0 and less than frameCount.
+     */
+    public byte[] readFrame(int frameIndex) throws IOException {
+        if (frameIndex > mFrameCount | frameIndex < 0){
+            return null;
+        }
+        int frameSize = mFrameSizes[frameIndex];
+        int frameHead = mFrameHeads[frameIndex];
+
+        byte[] frame = new byte[frameSize];
+        mIvfFile.seek(frameHead + FRAME_HEADER_SIZE);
+        mIvfFile.read(frame);
+
+        return frame;
+    }
+
+    /**
+     * Closes IVF file.
+     */
+    public void close() throws IOException{
+        mIvfFile.close();
+    }
+
+    private boolean verifyHeader() throws IOException{
+        mIvfFile.seek(0);
+
+        if (mIvfFile.length() < HEADER_END){
+            return false;
+        }
+
+        // DKIF signature
+        boolean signatureMatch = ((mIvfFile.readByte() == (byte)'D') &&
+                (mIvfFile.readByte() == (byte)'K') &&
+                (mIvfFile.readByte() == (byte)'I') &&
+                (mIvfFile.readByte() == (byte)'F'));
+
+        // Fourcc
+        mIvfFile.seek(FOURCC_HEAD);
+        boolean fourccMatch = ((mIvfFile.readByte() == (byte)'V') &&
+                (mIvfFile.readByte() == (byte)'P') &&
+                (mIvfFile.readByte() == (byte)'8') &&
+                (mIvfFile.readByte() == (byte)'0'));
+
+        return signatureMatch && fourccMatch;
+    }
+
+    private void readHeaderData() throws IOException{
+        // width
+        mIvfFile.seek(WIDTH_HEAD);
+        mWidth = (int) changeEndianness(mIvfFile.readShort());
+
+        // height
+        mIvfFile.seek(HEIGHT_HEAD);
+        mHeight = (int) changeEndianness(mIvfFile.readShort());
+
+        // frame count
+        mIvfFile.seek(FRAMECOUNT_HEAD);
+        mFrameCount = changeEndianness(mIvfFile.readInt());
+
+        // allocate frame metadata
+        mFrameHeads = new int[mFrameCount];
+        mFrameSizes = new int[mFrameCount];
+    }
+
+    private void readFrameMetadata() throws IOException{
+        int frameHead = HEADER_END;
+        for(int i = 0; i < mFrameCount; i++){
+            mIvfFile.seek(frameHead);
+            int frameSize = changeEndianness(mIvfFile.readInt());
+            mFrameHeads[i] = frameHead;
+            mFrameSizes[i] = frameSize;
+            // next frame
+            frameHead += FRAME_HEADER_SIZE + frameSize;
+        }
+    }
+
+    private static short changeEndianness(short value){
+        // Rationale for down-cast;
+        // Java Language specification 15.19:
+        //  "The type of the shift expression is the promoted type of the left-hand operand."
+        // Java Language specification 5.6:
+        //  "...if the operand is of compile-time type byte, short, or char,
+        //  unary numeric promotion promotes it to a value of type int by a widening conversion."
+        return (short) (((value << 8) & 0XFF00) | ((value >> 8) & 0X00FF));
+    }
+
+    private static int changeEndianness(int value){
+        return (((value << 24) & 0XFF000000) |
+                ((value << 8)  & 0X00FF0000) |
+                ((value >> 8)  & 0X0000FF00) |
+                ((value >> 24) & 0X000000FF));
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/IvfWriter.java b/tests/tests/media/src/android/media/cts/IvfWriter.java
new file mode 100644
index 0000000..ccc0ac5
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/IvfWriter.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2013 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.media.cts;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+/**
+ * Writes an IVF file.
+ *
+ * IVF format is a simple container format for VP8 encoded frames.
+ */
+
+public class IvfWriter {
+    private static final byte HEADER_END = 32;
+    private RandomAccessFile mOutputFile;
+    private int mWidth;
+    private int mHeight;
+    private int mScale;
+    private int mRate;
+    private int mFrameCount;
+
+    /**
+     * Initializes the IVF file writer.
+     *
+     * Timebase fraction is in format scale/rate, e.g. 1/1000
+     * Timestamp values supplied while writing frames should be in accordance
+     * with this timebase value.
+     *
+     * @param filename   name of the IVF file
+     * @param width      frame width
+     * @param height     frame height
+     * @param scale      timebase scale (or numerator of the timebase fraction)
+     * @param rate       timebase rate (or denominator of the timebase fraction)
+     */
+    public IvfWriter(String filename,
+                     int width, int height,
+                     int scale, int rate) throws IOException {
+        mOutputFile = new RandomAccessFile(filename, "rw");
+        mWidth = width;
+        mHeight = height;
+        mScale = scale;
+        mRate = rate;
+        mFrameCount = 0;
+        mOutputFile.seek(HEADER_END);  // Skip the header for now, as framecount is unknown
+    }
+
+    /**
+     * Initializes the IVF file writer with a microsecond timebase.
+     *
+     *
+     * Microsecond timebase is default for OMX thus stagefright.
+     *
+     * @param filename   name of the IVF file
+     * @param width      frame width
+     * @param height     frame height
+     */
+    public IvfWriter(String filename, int width, int height) throws IOException {
+        this(filename, width, height, 1, 1000000);
+    }
+
+    /**
+     * Finalizes the IVF header and closes the file.
+     */
+    public void close() throws IOException{
+        // Write header now
+        mOutputFile.seek(0);
+        mOutputFile.write(makeIvfHeader(mFrameCount, mWidth, mHeight, mScale, mRate));
+        mOutputFile.close();
+    }
+
+    /**
+     * Writes a single encoded VP8 frame with its frame header.
+     *
+     * @param frame     actual contents of the encoded frame data
+     * @param width     timestamp of the frame (in accordance to specified timebase)
+     */
+    public void writeFrame(byte[] frame, long timeStamp) throws IOException {
+        mOutputFile.write(makeIvfFrameHeader(frame.length, timeStamp));
+        mOutputFile.write(frame);
+        mFrameCount++;
+    }
+
+    /**
+     * Makes a 32 byte file header for IVF format.
+     *
+     * Timebase fraction is in format scale/rate, e.g. 1/1000
+     *
+     * @param frameCount total number of frames file contains
+     * @param width      frame width
+     * @param height     frame height
+     * @param scale      timebase scale (or numerator of the timebase fraction)
+     * @param rate       timebase rate (or denominator of the timebase fraction)
+     */
+    private static byte[] makeIvfHeader(int frameCount, int width, int height, int scale, int rate){
+        byte[] ivfHeader = new byte[32];
+        ivfHeader[0] = 'D';
+        ivfHeader[1] = 'K';
+        ivfHeader[2] = 'I';
+        ivfHeader[3] = 'F';
+        lay16Bits(ivfHeader, 4, 0);  // version
+        lay16Bits(ivfHeader, 6, 32);  // header size
+        ivfHeader[8] = 'V';  // fourcc
+        ivfHeader[9] = 'P';
+        ivfHeader[10] = '8';
+        ivfHeader[11] = '0';
+        lay16Bits(ivfHeader, 12, width);
+        lay16Bits(ivfHeader, 14, height);
+        lay32Bits(ivfHeader, 16, rate);  // scale/rate
+        lay32Bits(ivfHeader, 20, scale);
+        lay32Bits(ivfHeader, 24, frameCount);
+        lay32Bits(ivfHeader, 28, 0);  // unused
+        return ivfHeader;
+    }
+
+    /**
+     * Makes a 12 byte header for an encoded frame.
+     *
+     * @param size      frame size
+     * @param timestamp presentation timestamp of the frame
+     */
+    private static byte[] makeIvfFrameHeader(int size, long timestamp){
+        byte[] frameHeader = new byte[12];
+        lay32Bits(frameHeader, 0, size);
+        lay64bits(frameHeader, 4, timestamp);
+        return frameHeader;
+    }
+
+
+    /**
+     * Lays least significant 16 bits of an int into 2 items of a byte array.
+     *
+     * Note that ordering is little-endian.
+     *
+     * @param array     the array to be modified
+     * @param index     index of the array to start laying down
+     * @param value     the integer to use least significant 16 bits
+     */
+    private static void lay16Bits(byte[] array, int index, int value){
+        array[index] = (byte) (value);
+        array[index + 1] = (byte) (value >> 8);
+    }
+
+    /**
+     * Lays an int into 4 items of a byte array.
+     *
+     * Note that ordering is little-endian.
+     *
+     * @param array     the array to be modified
+     * @param index     index of the array to start laying down
+     * @param value     the integer to use
+     */
+    private static void lay32Bits(byte[] array, int index, int value){
+        for (int i = 0; i < 4; i++){
+            array[index + i] = (byte) (value >> (i * 8));
+        }
+    }
+
+    /**
+     * Lays a long int into 8 items of a byte array.
+     *
+     * Note that ordering is little-endian.
+     *
+     * @param array     the array to be modified
+     * @param index     index of the array to start laying down
+     * @param value     the integer to use
+     */
+    private static void lay64bits(byte[] array, int index, long value){
+        for (int i = 0; i < 8; i++){
+            array[index + i] = (byte) (value >> (i * 8));
+        }
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecListTest.java b/tests/tests/media/src/android/media/cts/MediaCodecListTest.java
old mode 100755
new mode 100644
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecTest.java b/tests/tests/media/src/android/media/cts/MediaCodecTest.java
new file mode 100644
index 0000000..afa34e8
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/MediaCodecTest.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2013 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.media.cts;
+
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecList;
+import android.media.MediaFormat;
+import android.opengl.GLES20;
+import android.test.AndroidTestCase;
+import android.util.Log;
+import android.view.Surface;
+
+
+/**
+ * General MediaCodec tests.
+ *
+ * In particular, check various API edge cases.
+ */
+public class MediaCodecTest extends AndroidTestCase {
+    private static final String TAG = "MediaCodecTest";
+    private static final boolean VERBOSE = false;           // lots of logging
+
+    // parameters for the encoder
+    private static final String MIME_TYPE = "video/avc";    // H.264 Advanced Video Coding
+    private static final int BIT_RATE = 2000000;            // 2Mbps
+    private static final int FRAME_RATE = 15;               // 15fps
+    private static final int IFRAME_INTERVAL = 10;          // 10 seconds between I-frames
+    private static final int WIDTH = 1280;
+    private static final int HEIGHT = 720;
+
+    /**
+     * Tests:
+     * <br> calling createInputSurface() before configure() throws exception
+     * <br> calling createInputSurface() after start() throws exception
+     * <br> calling createInputSurface() with a non-Surface color format throws exception
+     */
+    public void testCreateInputSurfaceErrors() {
+        MediaFormat format = createMediaFormat();
+        MediaCodec encoder = null;
+        Surface surface = null;
+
+        // Replace color format with something that isn't COLOR_FormatSurface.
+        MediaCodecInfo codecInfo = selectCodec(MIME_TYPE);
+        int colorFormat = findNonSurfaceColorFormat(codecInfo, MIME_TYPE);
+        format.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);
+
+        try {
+            encoder = MediaCodec.createByCodecName(codecInfo.getName());;
+            try {
+                surface = encoder.createInputSurface();
+                fail("createInputSurface should not work pre-configure");
+            } catch (IllegalStateException ise) {
+                // good
+            }
+            encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+
+            try {
+                surface = encoder.createInputSurface();
+                fail("createInputSurface should require COLOR_FormatSurface");
+            } catch (IllegalStateException ise) {
+                // good
+            }
+
+            encoder.start();
+
+            try {
+                surface = encoder.createInputSurface();
+                fail("createInputSurface should not work post-start");
+            } catch (IllegalStateException ise) {
+                // good
+            }
+        } finally {
+            if (encoder != null) {
+                encoder.stop();
+                encoder.release();
+            }
+        }
+        assertNull(surface);
+    }
+
+
+    /**
+     * Tests:
+     * <br> signaling end-of-stream before any data is sent works
+     * <br> signaling EOS twice throws exception
+     * <br> submitting a frame after EOS throws exception [TODO]
+     */
+    public void testSignalSurfaceEOS() {
+        MediaFormat format = createMediaFormat();
+        MediaCodec encoder = null;
+        InputSurface inputSurface = null;
+
+        try {
+            encoder = MediaCodec.createEncoderByType(MIME_TYPE);
+            encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+            inputSurface = new InputSurface(encoder.createInputSurface());
+            inputSurface.makeCurrent();
+            encoder.start();
+
+            // send an immediate EOS
+            encoder.signalEndOfInputStream();
+
+            try {
+                encoder.signalEndOfInputStream();
+                fail("should not be able to signal EOS twice");
+            } catch (IllegalStateException ise) {
+                // good
+            }
+
+            // submit a frame post-EOS
+            GLES20.glClearColor(0.0f, 0.5f, 0.0f, 1.0f);
+            GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+            try {
+                inputSurface.swapBuffers();
+                if (false) {    // TODO
+                    fail("should not be able to submit frame after EOS");
+                }
+            } catch (Exception ex) {
+                // good
+            }
+        } finally {
+            if (encoder != null) {
+                encoder.stop();
+                encoder.release();
+            }
+            if (inputSurface != null) {
+                inputSurface.release();
+            }
+        }
+    }
+
+    /**
+     * Tests:
+     * <br> dequeueInputBuffer() fails when encoder configured with an input Surface
+     */
+    public void testDequeueSurface() {
+        MediaFormat format = createMediaFormat();
+        MediaCodec encoder = null;
+        Surface surface = null;
+
+        try {
+            encoder = MediaCodec.createEncoderByType(MIME_TYPE);
+            encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+            surface = encoder.createInputSurface();
+            encoder.start();
+
+            try {
+                encoder.dequeueInputBuffer(-1);
+                fail("dequeueInputBuffer should fail on encoder with input surface");
+            } catch (IllegalStateException ise) {
+                // good
+            }
+
+        } finally {
+            if (encoder != null) {
+                encoder.stop();
+                encoder.release();
+            }
+            if (surface != null) {
+                surface.release();
+            }
+        }
+    }
+
+    /**
+     * Tests:
+     * <br> configure() encoder with Surface, re-configure() without Surface works
+     * <br> sending EOS with signalEndOfInputStream on non-Surface encoder fails
+     */
+    public void testReconfigureWithoutSurface() {
+        MediaFormat format = createMediaFormat();
+        MediaCodec encoder = null;
+        Surface surface = null;
+
+        try {
+            encoder = MediaCodec.createEncoderByType(MIME_TYPE);
+            encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+            surface = encoder.createInputSurface();
+            encoder.start();
+
+            encoder.getOutputBuffers();
+
+            // re-configure, this time without an input surface
+            if (VERBOSE) Log.d(TAG, "reconfiguring");
+            encoder.stop();
+            encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+            encoder.start();
+            if (VERBOSE) Log.d(TAG, "reconfigured");
+
+            encoder.getOutputBuffers();
+            encoder.dequeueInputBuffer(-1);
+
+            try {
+                encoder.signalEndOfInputStream();
+                fail("signalEndOfInputStream only works on surface input");
+            } catch (IllegalStateException ise) {
+                // good
+            }
+        } finally {
+            if (encoder != null) {
+                encoder.stop();
+                encoder.release();
+            }
+            if (surface != null) {
+                surface.release();
+            }
+        }
+    }
+
+    /**
+     * Creates a MediaFormat with the basic set of values.
+     */
+    private static MediaFormat createMediaFormat() {
+        MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, WIDTH, HEIGHT);
+        format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+                MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
+        format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
+        format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
+        format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
+        return format;
+    }
+
+    /**
+     * Returns the first codec capable of encoding the specified MIME type, or null if no
+     * match was found.
+     */
+    private static MediaCodecInfo selectCodec(String mimeType) {
+        int numCodecs = MediaCodecList.getCodecCount();
+        for (int i = 0; i < numCodecs; i++) {
+            MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
+
+            if (!codecInfo.isEncoder()) {
+                continue;
+            }
+
+            String[] types = codecInfo.getSupportedTypes();
+            for (int j = 0; j < types.length; j++) {
+                if (types[j].equalsIgnoreCase(mimeType)) {
+                    return codecInfo;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns a color format that is supported by the codec and isn't COLOR_FormatSurface.  Throws
+     * an exception if none found.
+     */
+    private static int findNonSurfaceColorFormat(MediaCodecInfo codecInfo, String mimeType) {
+        MediaCodecInfo.CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(mimeType);
+        for (int i = 0; i < capabilities.colorFormats.length; i++) {
+            int colorFormat = capabilities.colorFormats[i];
+            if (colorFormat != MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface) {
+                return colorFormat;
+            }
+        }
+        fail("couldn't find a good color format for " + codecInfo.getName() + " / " + MIME_TYPE);
+        return 0;   // not reached
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/MediaPlayerTest.java b/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
index dda78f8..c6d2985 100644
--- a/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
@@ -750,7 +750,7 @@
             return;
         }
         for (int i = 0; i < trackInfos.length; ++i) {
-            if (trackInfos[i] == null) continue;
+            assertTrue(trackInfos[i] != null);
             if (trackInfos[i].getTrackType() ==
                  MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT) {
                 mTimedTextTrackIndex.add(i);
@@ -907,6 +907,35 @@
         mMediaPlayer.stop();
     }
 
+    public void testGetTrackInfo() throws Exception {
+        loadResource(R.raw.testvideo_with_2_subtitles);
+        loadSubtitleSource(R.raw.test_subtitle1_srt);
+        loadSubtitleSource(R.raw.test_subtitle2_srt);
+        mMediaPlayer.prepare();
+        mMediaPlayer.start();
+
+        readTimedTextTracks();
+        selectSubtitleTrack(2);
+
+        int count = 0;
+        MediaPlayer.TrackInfo[] trackInfos = mMediaPlayer.getTrackInfo();
+        assertTrue(trackInfos != null && trackInfos.length != 0);
+        for (int i = 0; i < trackInfos.length; ++i) {
+            assertTrue(trackInfos[i] != null);
+            if (trackInfos[i].getTrackType() == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT) {
+                String trackLanguage = trackInfos[i].getLanguage();
+                assertTrue(trackLanguage != null);
+                trackLanguage.trim();
+                Log.d(LOG_TAG, "track info lang: " + trackLanguage);
+                assertTrue("Should not see empty track language with our test data.",
+                           trackLanguage.length() > 0);
+                count++;
+            }
+        }
+        // There are 4 subtitle tracks in total in our test data.
+        assertEquals(4, count);
+    }
+
     public void testCallback() throws Throwable {
         final int mp4Duration = 8484;
 
diff --git a/tests/tests/media/src/android/media/cts/OutputSurface.java b/tests/tests/media/src/android/media/cts/OutputSurface.java
new file mode 100644
index 0000000..8494796
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/OutputSurface.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2013 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.media.cts;
+
+import android.graphics.SurfaceTexture;
+import android.opengl.EGL14;
+import android.opengl.GLES20;
+import android.opengl.GLES11Ext;
+import android.opengl.GLSurfaceView;
+import android.opengl.Matrix;
+import android.util.Log;
+import android.view.Surface;
+
+import java.nio.ByteBuffer;
+
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLContext;
+import javax.microedition.khronos.egl.EGLDisplay;
+import javax.microedition.khronos.egl.EGLSurface;
+import javax.microedition.khronos.opengles.GL;
+import javax.microedition.khronos.opengles.GL10;
+
+
+
+/**
+ * Holds state associated with a Surface used for MediaCodec decoder output.
+ * <p>
+ * The (width,height) constructor for this class will prepare GL, create a SurfaceTexture,
+ * and then create a Surface for that SurfaceTexture.  The Surface can be passed to
+ * MediaCodec.configure() to receive decoder output.  When a frame arrives, we latch the
+ * texture with updateTexImage, then render the texture with GL to a pbuffer.
+ * <p>
+ * The no-arg constructor skips the GL preparation step and doesn't allocate a pbuffer.
+ * Instead, it just creates the Surface and SurfaceTexture, and when a frame arrives
+ * we just draw it on whatever surface is current.
+ * <p>
+ * By default, the Surface will be using a BufferQueue in asynchronous mode, so we
+ * can potentially drop frames.
+ */
+class OutputSurface implements SurfaceTexture.OnFrameAvailableListener {
+    private static final String TAG = "OutputSurface";
+    private static final boolean VERBOSE = false;
+
+    private static final int EGL_OPENGL_ES2_BIT = 4;
+
+    private EGL10 mEGL;
+    private EGLDisplay mEGLDisplay;
+    private EGLContext mEGLContext;
+    private EGLSurface mEGLSurface;
+
+    private SurfaceTexture mSurfaceTexture;
+    private Surface mSurface;
+
+    private Object mFrameSyncObject = new Object();     // guards mFrameAvailable
+    private boolean mFrameAvailable;
+
+    private TextureRender mTextureRender;
+
+    /**
+     * Creates an OutputSurface backed by a pbuffer with the specifed dimensions.  The new
+     * EGL context and surface will be made current.  Creates a Surface that can be passed
+     * to MediaCodec.configure().
+     */
+    public OutputSurface(int width, int height) {
+        if (width <= 0 || height <= 0) {
+            throw new IllegalArgumentException();
+        }
+
+        eglSetup(width, height);
+        makeCurrent();
+
+        setup();
+    }
+
+    /**
+     * Creates an OutputSurface using the current EGL context.  Creates a Surface that can be
+     * passed to MediaCodec.configure().
+     */
+    public OutputSurface() {
+        setup();
+    }
+
+    /**
+     * Creates instances of TextureRender and SurfaceTexture, and a Surface associated
+     * with the SurfaceTexture.
+     */
+    private void setup() {
+        mTextureRender = new TextureRender();
+        mTextureRender.surfaceCreated();
+
+        // Even if we don't access the SurfaceTexture after the constructor returns, we
+        // still need to keep a reference to it.  The Surface doesn't retain a reference
+        // at the Java level, so if we don't either then the object can get GCed, which
+        // causes the native finalizer to run.
+        if (VERBOSE) Log.d(TAG, "textureID=" + mTextureRender.getTextureId());
+        mSurfaceTexture = new SurfaceTexture(mTextureRender.getTextureId());
+
+        // This doesn't work if OutputSurface is created on the thread that CTS started for
+        // these test cases.
+        //
+        // The CTS-created thread has a Looper, and the SurfaceTexture constructor will
+        // create a Handler that uses it.  The "frame available" message is delivered
+        // there, but since we're not a Looper-based thread we'll never see it.  For
+        // this to do anything useful, OutputSurface must be created on a thread without
+        // a Looper, so that SurfaceTexture uses the main application Looper instead.
+        //
+        // Java language note: passing "this" out of a constructor is generally unwise,
+        // but we should be able to get away with it here.
+        mSurfaceTexture.setOnFrameAvailableListener(this);
+
+        mSurface = new Surface(mSurfaceTexture);
+    }
+
+    /**
+     * Prepares EGL.  We want a GLES 2.0 context and a surface that supports pbuffer.
+     */
+    private void eglSetup(int width, int height) {
+        mEGL = (EGL10)EGLContext.getEGL();
+        mEGLDisplay = mEGL.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
+        if (!mEGL.eglInitialize(mEGLDisplay, null)) {
+            throw new RuntimeException("unable to initialize EGL10");
+        }
+
+        // Configure EGL for pbuffer and OpenGL ES 2.0.  We want enough RGB bits
+        // to be able to tell if the frame is reasonable.
+        int[] attribList = {
+                EGL10.EGL_RED_SIZE, 8,
+                EGL10.EGL_GREEN_SIZE, 8,
+                EGL10.EGL_BLUE_SIZE, 8,
+                EGL10.EGL_SURFACE_TYPE, EGL10.EGL_PBUFFER_BIT,
+                EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+                EGL10.EGL_NONE
+        };
+        EGLConfig[] configs = new EGLConfig[1];
+        int[] numConfigs = new int[1];
+        if (!mEGL.eglChooseConfig(mEGLDisplay, attribList, configs, 1, numConfigs)) {
+            throw new RuntimeException("unable to find RGB888+pbuffer EGL config");
+        }
+
+        // Configure context for OpenGL ES 2.0.
+        int[] attrib_list = {
+                EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
+                EGL10.EGL_NONE
+        };
+        mEGLContext = mEGL.eglCreateContext(mEGLDisplay, configs[0], EGL10.EGL_NO_CONTEXT,
+                attrib_list);
+        checkEglError("eglCreateContext");
+        if (mEGLContext == null) {
+            throw new RuntimeException("null context");
+        }
+
+        // Create a pbuffer surface.  By using this for output, we can use glReadPixels
+        // to test values in the output.
+        int[] surfaceAttribs = {
+                EGL10.EGL_WIDTH, width,
+                EGL10.EGL_HEIGHT, height,
+                EGL10.EGL_NONE
+        };
+        mEGLSurface = mEGL.eglCreatePbufferSurface(mEGLDisplay, configs[0], surfaceAttribs);
+        checkEglError("eglCreatePbufferSurface");
+        if (mEGLSurface == null) {
+            throw new RuntimeException("surface was null");
+        }
+    }
+
+    /**
+     * Discard all resources held by this class, notably the EGL context.
+     */
+    public void release() {
+        if (mEGL != null) {
+            if (mEGL.eglGetCurrentContext() == mEGLContext) {
+                // Clear the current context and surface to ensure they are discarded immediately.
+                mEGL.eglMakeCurrent(mEGLDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE,
+                        EGL10.EGL_NO_CONTEXT);
+            }
+            mEGL.eglDestroySurface(mEGLDisplay, mEGLSurface);
+            mEGL.eglDestroyContext(mEGLDisplay, mEGLContext);
+            //mEGL.eglTerminate(mEGLDisplay);
+        }
+
+        mSurface.release();
+
+        // this causes a bunch of warnings that appear harmless but might confuse someone:
+        //  W BufferQueue: [unnamed-3997-2] cancelBuffer: BufferQueue has been abandoned!
+        //mSurfaceTexture.release();
+
+        // null everything out so future attempts to use this object will cause an NPE
+        mEGLDisplay = null;
+        mEGLContext = null;
+        mEGLSurface = null;
+        mEGL = null;
+
+        mTextureRender = null;
+        mSurface = null;
+        mSurfaceTexture = null;
+    }
+
+    /**
+     * Makes our EGL context and surface current.
+     */
+    public void makeCurrent() {
+        if (mEGL == null) {
+            throw new RuntimeException("not configured for makeCurrent");
+        }
+        checkEglError("before makeCurrent");
+        if (!mEGL.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) {
+            throw new RuntimeException("eglMakeCurrent failed");
+        }
+    }
+
+    /**
+     * Returns the Surface that we draw onto.
+     */
+    public Surface getSurface() {
+        return mSurface;
+    }
+
+    /**
+     * Replaces the fragment shader.
+     */
+    public void changeFragmentShader(String fragmentShader) {
+        mTextureRender.changeFragmentShader(fragmentShader);
+    }
+
+    /**
+     * Latches the next buffer into the texture.  Must be called from the thread that created
+     * the OutputSurface object, after the onFrameAvailable callback has signaled that new
+     * data is available.
+     */
+    public void awaitNewImage() {
+        final int TIMEOUT_MS = 500;
+
+        synchronized (mFrameSyncObject) {
+            while (!mFrameAvailable) {
+                try {
+                    // Wait for onFrameAvailable() to signal us.  Use a timeout to avoid
+                    // stalling the test if it doesn't arrive.
+                    mFrameSyncObject.wait(TIMEOUT_MS);
+                    if (!mFrameAvailable) {
+                        // TODO: if "spurious wakeup", continue while loop
+                        throw new RuntimeException("Surface frame wait timed out");
+                    }
+                } catch (InterruptedException ie) {
+                    // shouldn't happen
+                    throw new RuntimeException(ie);
+                }
+            }
+            mFrameAvailable = false;
+        }
+
+        // Latch the data.
+        mTextureRender.checkGlError("before updateTexImage");
+        mSurfaceTexture.updateTexImage();
+    }
+
+    /**
+     * Draws the data from SurfaceTexture onto the current EGL surface.
+     */
+    public void drawImage() {
+        mTextureRender.drawFrame(mSurfaceTexture);
+    }
+
+    @Override
+    public void onFrameAvailable(SurfaceTexture st) {
+        if (VERBOSE) Log.d(TAG, "new frame available");
+        synchronized (mFrameSyncObject) {
+            if (mFrameAvailable) {
+                throw new RuntimeException("mFrameAvailable already set, frame could be dropped");
+            }
+            mFrameAvailable = true;
+            mFrameSyncObject.notifyAll();
+        }
+    }
+
+    /**
+     * Checks for EGL errors.
+     */
+    private void checkEglError(String msg) {
+        boolean failed = false;
+        int error;
+        while ((error = mEGL.eglGetError()) != EGL10.EGL_SUCCESS) {
+            Log.e(TAG, msg + ": EGL error: 0x" + Integer.toHexString(error));
+            failed = true;
+        }
+        if (failed) {
+            throw new RuntimeException("EGL error encountered (see log)");
+        }
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/TextureRender.java b/tests/tests/media/src/android/media/cts/TextureRender.java
new file mode 100644
index 0000000..1ed568a
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/TextureRender.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2013 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.media.cts;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+import android.graphics.SurfaceTexture;
+import android.opengl.GLES11Ext;
+import android.opengl.GLES20;
+import android.opengl.GLSurfaceView;
+import android.opengl.Matrix;
+import android.util.Log;
+
+
+/**
+ * Code for rendering a texture onto a surface using OpenGL ES 2.0.
+ */
+class TextureRender {
+    private static final String TAG = "TextureRender";
+
+    private static final int FLOAT_SIZE_BYTES = 4;
+    private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES;
+    private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
+    private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
+    private final float[] mTriangleVerticesData = {
+        // X, Y, Z, U, V
+        -1.0f, -1.0f, 0, 0.f, 0.f,
+        1.0f, -1.0f, 0, 1.f, 0.f,
+        -1.0f,  1.0f, 0, 0.f, 1.f,
+        1.0f,  1.0f, 0, 1.f, 1.f,
+    };
+
+    private FloatBuffer mTriangleVertices;
+
+    private static final String VERTEX_SHADER =
+            "uniform mat4 uMVPMatrix;\n" +
+            "uniform mat4 uSTMatrix;\n" +
+            "attribute vec4 aPosition;\n" +
+            "attribute vec4 aTextureCoord;\n" +
+            "varying vec2 vTextureCoord;\n" +
+            "void main() {\n" +
+            "  gl_Position = uMVPMatrix * aPosition;\n" +
+            "  vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" +
+            "}\n";
+
+    private static final String FRAGMENT_SHADER =
+            "#extension GL_OES_EGL_image_external : require\n" +
+            "precision mediump float;\n" +      // highp here doesn't seem to matter
+            "varying vec2 vTextureCoord;\n" +
+            "uniform samplerExternalOES sTexture;\n" +
+            "void main() {\n" +
+            "  gl_FragColor = texture2D(sTexture, vTextureCoord);\n" +
+            "}\n";
+
+    private float[] mMVPMatrix = new float[16];
+    private float[] mSTMatrix = new float[16];
+
+    private int mProgram;
+    private int mTextureID = -12345;
+    private int muMVPMatrixHandle;
+    private int muSTMatrixHandle;
+    private int maPositionHandle;
+    private int maTextureHandle;
+
+    public TextureRender() {
+        mTriangleVertices = ByteBuffer.allocateDirect(
+            mTriangleVerticesData.length * FLOAT_SIZE_BYTES)
+                .order(ByteOrder.nativeOrder()).asFloatBuffer();
+        mTriangleVertices.put(mTriangleVerticesData).position(0);
+
+        Matrix.setIdentityM(mSTMatrix, 0);
+    }
+
+    public int getTextureId() {
+        return mTextureID;
+    }
+
+    public void drawFrame(SurfaceTexture st) {
+        checkGlError("onDrawFrame start");
+        st.getTransformMatrix(mSTMatrix);
+
+        GLES20.glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
+        GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
+
+        GLES20.glUseProgram(mProgram);
+        checkGlError("glUseProgram");
+
+        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID);
+
+        mTriangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
+        GLES20.glVertexAttribPointer(maPositionHandle, 3, GLES20.GL_FLOAT, false,
+            TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
+        checkGlError("glVertexAttribPointer maPosition");
+        GLES20.glEnableVertexAttribArray(maPositionHandle);
+        checkGlError("glEnableVertexAttribArray maPositionHandle");
+
+        mTriangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
+        GLES20.glVertexAttribPointer(maTextureHandle, 3, GLES20.GL_FLOAT, false,
+            TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
+        checkGlError("glVertexAttribPointer maTextureHandle");
+        GLES20.glEnableVertexAttribArray(maTextureHandle);
+        checkGlError("glEnableVertexAttribArray maTextureHandle");
+
+        Matrix.setIdentityM(mMVPMatrix, 0);
+        GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mMVPMatrix, 0);
+        GLES20.glUniformMatrix4fv(muSTMatrixHandle, 1, false, mSTMatrix, 0);
+
+        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
+        checkGlError("glDrawArrays");
+        GLES20.glFinish();
+    }
+
+    /**
+     * Initializes GL state.  Call this after the EGL surface has been created and made current.
+     */
+    public void surfaceCreated() {
+        mProgram = createProgram(VERTEX_SHADER, FRAGMENT_SHADER);
+        if (mProgram == 0) {
+            throw new RuntimeException("failed creating program");
+        }
+        maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
+        checkGlError("glGetAttribLocation aPosition");
+        if (maPositionHandle == -1) {
+            throw new RuntimeException("Could not get attrib location for aPosition");
+        }
+        maTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTextureCoord");
+        checkGlError("glGetAttribLocation aTextureCoord");
+        if (maTextureHandle == -1) {
+            throw new RuntimeException("Could not get attrib location for aTextureCoord");
+        }
+
+        muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
+        checkGlError("glGetUniformLocation uMVPMatrix");
+        if (muMVPMatrixHandle == -1) {
+            throw new RuntimeException("Could not get attrib location for uMVPMatrix");
+        }
+
+        muSTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uSTMatrix");
+        checkGlError("glGetUniformLocation uSTMatrix");
+        if (muSTMatrixHandle == -1) {
+            throw new RuntimeException("Could not get attrib location for uSTMatrix");
+        }
+
+
+        int[] textures = new int[1];
+        GLES20.glGenTextures(1, textures, 0);
+
+        mTextureID = textures[0];
+        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID);
+        checkGlError("glBindTexture mTextureID");
+
+        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER,
+                GLES20.GL_NEAREST);
+        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER,
+                GLES20.GL_LINEAR);
+        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S,
+                GLES20.GL_CLAMP_TO_EDGE);
+        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T,
+                GLES20.GL_CLAMP_TO_EDGE);
+        checkGlError("glTexParameter");
+    }
+
+    /**
+     * Replaces the fragment shader.
+     */
+    public void changeFragmentShader(String fragmentShader) {
+        GLES20.glDeleteProgram(mProgram);
+        mProgram = createProgram(VERTEX_SHADER, fragmentShader);
+        if (mProgram == 0) {
+            throw new RuntimeException("failed creating program");
+        }
+    }
+
+    private int loadShader(int shaderType, String source) {
+        int shader = GLES20.glCreateShader(shaderType);
+        checkGlError("glCreateShader type=" + shaderType);
+        GLES20.glShaderSource(shader, source);
+        GLES20.glCompileShader(shader);
+        int[] compiled = new int[1];
+        GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
+        if (compiled[0] == 0) {
+            Log.e(TAG, "Could not compile shader " + shaderType + ":");
+            Log.e(TAG, " " + GLES20.glGetShaderInfoLog(shader));
+            GLES20.glDeleteShader(shader);
+            shader = 0;
+        }
+        return shader;
+    }
+
+    private int createProgram(String vertexSource, String fragmentSource) {
+        int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
+        if (vertexShader == 0) {
+            return 0;
+        }
+        int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
+        if (pixelShader == 0) {
+            return 0;
+        }
+
+        int program = GLES20.glCreateProgram();
+        checkGlError("glCreateProgram");
+        if (program == 0) {
+            Log.e(TAG, "Could not create program");
+        }
+        GLES20.glAttachShader(program, vertexShader);
+        checkGlError("glAttachShader");
+        GLES20.glAttachShader(program, pixelShader);
+        checkGlError("glAttachShader");
+        GLES20.glLinkProgram(program);
+        int[] linkStatus = new int[1];
+        GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
+        if (linkStatus[0] != GLES20.GL_TRUE) {
+            Log.e(TAG, "Could not link program: ");
+            Log.e(TAG, GLES20.glGetProgramInfoLog(program));
+            GLES20.glDeleteProgram(program);
+            program = 0;
+        }
+        return program;
+    }
+
+    public void checkGlError(String op) {
+        int error;
+        while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
+            Log.e(TAG, op + ": glError " + error);
+            throw new RuntimeException(op + ": glError " + error);
+        }
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/VideoSurfaceView.java b/tests/tests/media/src/android/media/cts/VideoSurfaceView.java
index 943a56b..229c6f6 100644
--- a/tests/tests/media/src/android/media/cts/VideoSurfaceView.java
+++ b/tests/tests/media/src/android/media/cts/VideoSurfaceView.java
@@ -19,9 +19,6 @@
 import com.android.cts.media.R;
 
 import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.FloatBuffer;
 
 import javax.microedition.khronos.egl.EGLConfig;
 import javax.microedition.khronos.opengles.GL10;
@@ -29,7 +26,6 @@
 import android.content.Context;
 import android.graphics.SurfaceTexture;
 import android.media.MediaPlayer;
-import android.opengl.GLES20;
 import android.opengl.GLSurfaceView;
 import android.opengl.Matrix;
 import android.util.Log;
@@ -73,68 +69,22 @@
         }
     }
 
+    /**
+     * A GLSurfaceView implementation that wraps TextureRender.  Used to render frames from a
+     * video decoder to a View.
+     */
     private static class VideoRender
-        implements GLSurfaceView.Renderer, SurfaceTexture.OnFrameAvailableListener {
+            implements GLSurfaceView.Renderer, SurfaceTexture.OnFrameAvailableListener {
         private static String TAG = "VideoRender";
 
-        private static final int FLOAT_SIZE_BYTES = 4;
-        private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES;
-        private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
-        private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
-        private final float[] mTriangleVerticesData = {
-            // X, Y, Z, U, V
-            -1.0f, -1.0f, 0, 0.f, 0.f,
-            1.0f, -1.0f, 0, 1.f, 0.f,
-            -1.0f,  1.0f, 0, 0.f, 1.f,
-            1.0f,  1.0f, 0, 1.f, 1.f,
-        };
-
-        private FloatBuffer mTriangleVertices;
-
-        private final String mVertexShader =
-                "uniform mat4 uMVPMatrix;\n" +
-                "uniform mat4 uSTMatrix;\n" +
-                "attribute vec4 aPosition;\n" +
-                "attribute vec4 aTextureCoord;\n" +
-                "varying vec2 vTextureCoord;\n" +
-                "void main() {\n" +
-                "  gl_Position = uMVPMatrix * aPosition;\n" +
-                "  vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" +
-                "}\n";
-
-        private final String mFragmentShader =
-                "#extension GL_OES_EGL_image_external : require\n" +
-                "precision mediump float;\n" +
-                "varying vec2 vTextureCoord;\n" +
-                "uniform samplerExternalOES sTexture;\n" +
-                "void main() {\n" +
-                "  gl_FragColor = texture2D(sTexture, vTextureCoord);\n" +
-                "}\n";
-
-        private float[] mMVPMatrix = new float[16];
-        private float[] mSTMatrix = new float[16];
-
-        private int mProgram;
-        private int mTextureID;
-        private int muMVPMatrixHandle;
-        private int muSTMatrixHandle;
-        private int maPositionHandle;
-        private int maTextureHandle;
-
-        private SurfaceTexture mSurface;
+        private TextureRender mTextureRender;
+        private SurfaceTexture mSurfaceTexture;
         private boolean updateSurface = false;
 
-        private static int GL_TEXTURE_EXTERNAL_OES = 0x8D65;
-
         private MediaPlayer mMediaPlayer;
 
         public VideoRender(Context context) {
-            mTriangleVertices = ByteBuffer.allocateDirect(
-                mTriangleVerticesData.length * FLOAT_SIZE_BYTES)
-                    .order(ByteOrder.nativeOrder()).asFloatBuffer();
-            mTriangleVertices.put(mTriangleVerticesData).position(0);
-
-            Matrix.setIdentityM(mSTMatrix, 0);
+            mTextureRender = new TextureRender();
         }
 
         public void setMediaPlayer(MediaPlayer player) {
@@ -144,97 +94,28 @@
         public void onDrawFrame(GL10 glUnused) {
             synchronized(this) {
                 if (updateSurface) {
-                    mSurface.updateTexImage();
-                    mSurface.getTransformMatrix(mSTMatrix);
+                    mSurfaceTexture.updateTexImage();
                     updateSurface = false;
                 }
             }
 
-            GLES20.glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
-            GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
-
-            GLES20.glUseProgram(mProgram);
-            checkGlError("glUseProgram");
-
-            GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
-            GLES20.glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTextureID);
-
-            mTriangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
-            GLES20.glVertexAttribPointer(maPositionHandle, 3, GLES20.GL_FLOAT, false,
-                TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
-            checkGlError("glVertexAttribPointer maPosition");
-            GLES20.glEnableVertexAttribArray(maPositionHandle);
-            checkGlError("glEnableVertexAttribArray maPositionHandle");
-
-            mTriangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
-            GLES20.glVertexAttribPointer(maTextureHandle, 3, GLES20.GL_FLOAT, false,
-                TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
-            checkGlError("glVertexAttribPointer maTextureHandle");
-            GLES20.glEnableVertexAttribArray(maTextureHandle);
-            checkGlError("glEnableVertexAttribArray maTextureHandle");
-
-            Matrix.setIdentityM(mMVPMatrix, 0);
-            GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mMVPMatrix, 0);
-            GLES20.glUniformMatrix4fv(muSTMatrixHandle, 1, false, mSTMatrix, 0);
-
-            GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
-            checkGlError("glDrawArrays");
-            GLES20.glFinish();
-
+            mTextureRender.drawFrame(mSurfaceTexture);
         }
 
         public void onSurfaceChanged(GL10 glUnused, int width, int height) {
         }
 
         public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {
-            mProgram = createProgram(mVertexShader, mFragmentShader);
-            if (mProgram == 0) {
-                return;
-            }
-            maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
-            checkGlError("glGetAttribLocation aPosition");
-            if (maPositionHandle == -1) {
-                throw new RuntimeException("Could not get attrib location for aPosition");
-            }
-            maTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTextureCoord");
-            checkGlError("glGetAttribLocation aTextureCoord");
-            if (maTextureHandle == -1) {
-                throw new RuntimeException("Could not get attrib location for aTextureCoord");
-            }
-
-            muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
-            checkGlError("glGetUniformLocation uMVPMatrix");
-            if (muMVPMatrixHandle == -1) {
-                throw new RuntimeException("Could not get attrib location for uMVPMatrix");
-            }
-
-            muSTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uSTMatrix");
-            checkGlError("glGetUniformLocation uSTMatrix");
-            if (muSTMatrixHandle == -1) {
-                throw new RuntimeException("Could not get attrib location for uSTMatrix");
-            }
-
-
-            int[] textures = new int[1];
-            GLES20.glGenTextures(1, textures, 0);
-
-            mTextureID = textures[0];
-            GLES20.glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTextureID);
-            checkGlError("glBindTexture mTextureID");
-
-            GLES20.glTexParameterf(GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER,
-                                   GLES20.GL_NEAREST);
-            GLES20.glTexParameterf(GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER,
-                                   GLES20.GL_LINEAR);
+            mTextureRender.surfaceCreated();
 
             /*
              * Create the SurfaceTexture that will feed this textureID,
              * and pass it to the MediaPlayer
              */
-            mSurface = new SurfaceTexture(mTextureID);
-            mSurface.setOnFrameAvailableListener(this);
+            mSurfaceTexture = new SurfaceTexture(mTextureRender.getTextureId());
+            mSurfaceTexture.setOnFrameAvailableListener(this);
 
-            Surface surface = new Surface(mSurface);
+            Surface surface = new Surface(mSurfaceTexture);
             mMediaPlayer.setSurface(surface);
             surface.release();
 
@@ -252,61 +133,6 @@
         synchronized public void onFrameAvailable(SurfaceTexture surface) {
             updateSurface = true;
         }
-
-        private int loadShader(int shaderType, String source) {
-            int shader = GLES20.glCreateShader(shaderType);
-            if (shader != 0) {
-                GLES20.glShaderSource(shader, source);
-                GLES20.glCompileShader(shader);
-                int[] compiled = new int[1];
-                GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
-                if (compiled[0] == 0) {
-                    Log.e(TAG, "Could not compile shader " + shaderType + ":");
-                    Log.e(TAG, GLES20.glGetShaderInfoLog(shader));
-                    GLES20.glDeleteShader(shader);
-                    shader = 0;
-                }
-            }
-            return shader;
-        }
-
-        private int createProgram(String vertexSource, String fragmentSource) {
-            int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
-            if (vertexShader == 0) {
-                return 0;
-            }
-            int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
-            if (pixelShader == 0) {
-                return 0;
-            }
-
-            int program = GLES20.glCreateProgram();
-            if (program != 0) {
-                GLES20.glAttachShader(program, vertexShader);
-                checkGlError("glAttachShader");
-                GLES20.glAttachShader(program, pixelShader);
-                checkGlError("glAttachShader");
-                GLES20.glLinkProgram(program);
-                int[] linkStatus = new int[1];
-                GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
-                if (linkStatus[0] != GLES20.GL_TRUE) {
-                    Log.e(TAG, "Could not link program: ");
-                    Log.e(TAG, GLES20.glGetProgramInfoLog(program));
-                    GLES20.glDeleteProgram(program);
-                    program = 0;
-                }
-            }
-            return program;
-        }
-
-        private void checkGlError(String op) {
-            int error;
-            while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
-                Log.e(TAG, op + ": glError " + error);
-                throw new RuntimeException(op + ": glError " + error);
-            }
-        }
-
     }  // End of class VideoRender.
 
 }  // End of class VideoSurfaceView.
diff --git a/tests/tests/media/src/android/media/cts/Vp8EncoderTest.java b/tests/tests/media/src/android/media/cts/Vp8EncoderTest.java
new file mode 100644
index 0000000..308fb98
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/Vp8EncoderTest.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2013 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.media.cts;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaFormat;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import com.android.cts.media.R;
+
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+
+/**
+ * Basic verification test for vp8 encoder.
+ *
+ * A raw yv12 stream is encoded and written to an IVF
+ * file, which is later decoded by vp8 decoder to verify
+ * frames are at least decodable.
+ */
+public class Vp8EncoderTest extends AndroidTestCase {
+
+    private static final String TAG = "VP8EncoderTest";
+    private static final String VP8_MIME = "video/x-vnd.on2.vp8";
+    private static final String VPX_DECODER_NAME = "OMX.google.vpx.decoder";
+    private static final String VPX_ENCODER_NAME = "OMX.google.vpx.encoder";
+    private static final String BASIC_IVF = "video_176x144_vp8_basic.ivf";
+    private static final long DEFAULT_TIMEOUT_US = 5000;
+
+    private Resources mResources;
+    private MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo();
+    private ByteBuffer[] mInputBuffers;
+    private ByteBuffer[] mOutputBuffers;
+
+    @Override
+    public void setContext(Context context) {
+        super.setContext(context);
+        mResources = mContext.getResources();
+    }
+
+    /**
+     * A basic test for VP8 encoder.
+     *
+     * Encodes a raw stream with default configuration options,
+     * and then decodes it to verify the bitstream.
+     */
+    public void testBasic() throws Exception {
+        encode(BASIC_IVF,
+               R.raw.video_176x144_yv12,
+               176,  // width
+               144,  // height
+               30);  // framerate
+        decode(BASIC_IVF);
+    }
+
+
+    /**
+     * A basic check if an encoded stream is decodable.
+     *
+     * The most basic confirmation we can get about a frame
+     * being properly encoded is trying to decode it.
+     * (Especially in realtime mode encode output is non-
+     * deterministic, therefore a more thorough check like
+     * md5 sum comparison wouldn't work.)
+     *
+     * Indeed, MediaCodec will raise an IllegalStateException
+     * whenever vp8 decoder fails to decode a frame, and
+     * this test uses that fact to verify the bitstream.
+     *
+     * @param filename  The name of the IVF file containing encoded bitsream.
+     */
+    private void decode(String filename) throws Exception {
+        IvfReader ivf = null;
+        try {
+            ivf = new IvfReader(filename);
+            int frameWidth = ivf.getWidth();
+            int frameHeight = ivf.getHeight();
+            int frameCount = ivf.getFrameCount();
+
+            assertTrue(frameWidth > 0);
+            assertTrue(frameHeight > 0);
+            assertTrue(frameCount > 0);
+
+            MediaFormat format = MediaFormat.createVideoFormat(VP8_MIME,
+                                                               ivf.getWidth(),
+                                                               ivf.getHeight());
+
+            Log.d(TAG, "Creating decoder");
+            MediaCodec decoder = MediaCodec.createByCodecName(VPX_DECODER_NAME);
+            decoder.configure(format,
+                              null,  // surface
+                              null,  // crypto
+                              0);  // flags
+            decoder.start();
+
+            mInputBuffers = decoder.getInputBuffers();
+            mOutputBuffers = decoder.getOutputBuffers();
+
+            // decode loop
+            int frameIndex = 0;
+            boolean sawOutputEOS = false;
+            boolean sawInputEOS = false;
+
+            while (!sawOutputEOS) {
+                if (!sawInputEOS) {
+                    int inputBufIndex = decoder.dequeueInputBuffer(DEFAULT_TIMEOUT_US);
+                    if (inputBufIndex >= 0) {
+                        byte[] frame = ivf.readFrame(frameIndex);
+
+                        if (frameIndex == frameCount - 1) {
+                            sawInputEOS = true;
+                        }
+
+                        mInputBuffers[inputBufIndex].clear();
+                        mInputBuffers[inputBufIndex].put(frame);
+                        mInputBuffers[inputBufIndex].rewind();
+
+                        Log.d(TAG, "Decoding frame at index " + frameIndex);
+                        try {
+                            decoder.queueInputBuffer(
+                                    inputBufIndex,
+                                    0,  // offset
+                                    frame.length,
+                                    frameIndex,
+                                    sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
+                        } catch (IllegalStateException ise) {
+                            //That is all what is passed from MediaCodec in case of
+                            //decode failure.
+                            fail("Failed to decode frame at index " + frameIndex);
+                        }
+                        frameIndex++;
+                    }
+                }
+
+                int result = decoder.dequeueOutputBuffer(mBufferInfo, DEFAULT_TIMEOUT_US);
+                if (result >= 0) {
+                    int outputBufIndex = result;
+                    if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                        sawOutputEOS = true;
+                    }
+                    decoder.releaseOutputBuffer(outputBufIndex, false);
+                } else if (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                    mOutputBuffers = decoder.getOutputBuffers();
+                }
+            }
+            decoder.stop();
+            decoder.release();
+        } finally {
+            if (ivf != null) {
+                ivf.close();
+            }
+        }
+    }
+
+    /**
+     * A basic vp8 encode loop.
+     *
+     * MediaCodec will raise an IllegalStateException
+     * whenever vp8 encoder fails to encode a frame.
+     *
+     * In addition to that written IVF file can be tested
+     * to be decodable in order to verify the bitstream produced.
+     *
+     * Color format of input file should be YUV420, and frameWidth,
+     * frameHeight should be supplied correctly as raw input file doesn't
+     * include any header data.
+     *
+     * @param outputFilename  The name of the IVF file to write encoded bitsream
+     * @param rawInputFd      File descriptor for the raw input file (YUV420)
+     * @param frameWidth      Frame width of input file
+     * @param frameHeight     Frame height of input file
+     * @param frameRate       Frame rate of input file in frames per second
+     */
+    private void encode(String outputFilename, int rawInputFd,
+                       int frameWidth, int frameHeight, int frameRate) throws Exception {
+        int frameSize = frameWidth * frameHeight * 3 / 2;
+
+
+        // Create a media format signifying desired output
+        MediaFormat format = MediaFormat.createVideoFormat(VP8_MIME, frameWidth, frameHeight);
+        format.setInteger(MediaFormat.KEY_BIT_RATE, 100000);
+        format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+                          CodecCapabilities.COLOR_FormatYUV420Planar);
+        format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
+
+        Log.d(TAG, "Creating encoder");
+        MediaCodec encoder;
+        encoder = MediaCodec.createByCodecName(VPX_ENCODER_NAME);
+        encoder.configure(format,
+                          null,  // surface
+                          null,  // crypto
+                          MediaCodec.CONFIGURE_FLAG_ENCODE);
+        encoder.start();
+
+        mInputBuffers = encoder.getInputBuffers();
+        mOutputBuffers = encoder.getOutputBuffers();
+
+        InputStream rawStream = null;
+        IvfWriter ivf = null;
+
+        try {
+            rawStream = mResources.openRawResource(rawInputFd);
+            ivf = new IvfWriter(outputFilename, frameWidth, frameHeight);
+            // encode loop
+            long presentationTimeUs = 0;
+            int frameIndex = 0;
+            boolean sawInputEOS = false;
+            boolean sawOutputEOS = false;
+
+            while (!sawOutputEOS) {
+                if (!sawInputEOS) {
+                    int inputBufIndex = encoder.dequeueInputBuffer(DEFAULT_TIMEOUT_US);
+                    if (inputBufIndex >= 0) {
+                        byte[] frame = new byte[frameSize];
+                        int bytesRead = rawStream.read(frame);
+
+                        if (bytesRead == -1) {
+                            sawInputEOS = true;
+                            bytesRead = 0;
+                        }
+
+                        mInputBuffers[inputBufIndex].clear();
+                        mInputBuffers[inputBufIndex].put(frame);
+                        mInputBuffers[inputBufIndex].rewind();
+
+                        presentationTimeUs = (frameIndex * 1000000) / frameRate;
+                        Log.d(TAG, "Encoding frame at index " + frameIndex);
+                        encoder.queueInputBuffer(
+                                inputBufIndex,
+                                0,  // offset
+                                bytesRead,  // size
+                                presentationTimeUs,
+                                sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
+
+                        frameIndex++;
+                    }
+                }
+
+                int result = encoder.dequeueOutputBuffer(mBufferInfo, DEFAULT_TIMEOUT_US);
+                if (result >= 0) {
+                    int outputBufIndex = result;
+                    byte[] buffer = new byte[mBufferInfo.size];
+                    mOutputBuffers[outputBufIndex].rewind();
+                    mOutputBuffers[outputBufIndex].get(buffer, 0, mBufferInfo.size);
+
+                    if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                        sawOutputEOS = true;
+                    } else {
+                        ivf.writeFrame(buffer, mBufferInfo.presentationTimeUs);
+                    }
+                    encoder.releaseOutputBuffer(outputBufIndex,
+                                                false);  // render
+                } else if (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                    mOutputBuffers = encoder.getOutputBuffers();
+                }
+            }
+
+            encoder.stop();
+            encoder.release();
+        } finally {
+            if (ivf != null) {
+                ivf.close();
+            }
+
+            if (rawStream != null) {
+                rawStream.close();
+            }
+        }
+    }
+}
diff --git a/tests/tests/nativeopengl/Android.mk b/tests/tests/nativeopengl/Android.mk
new file mode 100644
index 0000000..dd19548
--- /dev/null
+++ b/tests/tests/nativeopengl/Android.mk
@@ -0,0 +1,41 @@
+# Copyright 2012 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsNativeOpenGLTestCases
+
+# Don't include this package in any target.
+LOCAL_MODULE_TAGS := optional
+
+# When built, explicitly put it in the data partition.
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+# All tests should include android.test.runner.
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner ctswrappedgtest
+
+LOCAL_JNI_SHARED_LIBRARIES := libnativeopengltests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_GTEST_PACKAGE)
+
+# Include the associated library's makefile.
+include $(LOCAL_PATH)/libnativeopengltests/Android.mk
diff --git a/tests/tests/nativeopengl/AndroidManifest.xml b/tests/tests/nativeopengl/AndroidManifest.xml
new file mode 100644
index 0000000..52157b4
--- /dev/null
+++ b/tests/tests/nativeopengl/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * 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.
+ */
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.opengl.cts">
+
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <activity android:name="com.android.opengl.cts.GLTestActivity"/>
+    </application>
+
+    <!-- This is a self-instrumenting test package. -->
+    <instrumentation android:name="GLTestInstrumentation"
+                     android:targetPackage="com.android.opengl.cts"
+                     android:label="Native OpenGL tests">
+    </instrumentation>
+</manifest>
diff --git a/tests/tests/nativeopengl/libnativeopengltests/Android.mk b/tests/tests/nativeopengl/libnativeopengltests/Android.mk
new file mode 100644
index 0000000..b6ca1cb
--- /dev/null
+++ b/tests/tests/nativeopengl/libnativeopengltests/Android.mk
@@ -0,0 +1,48 @@
+# Copyright 2012 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 is the shared library included by the JNI test app.
+#
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libnativeopengltests
+
+# Don't include this package in any configuration by default.
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_C_INCLUDES := $(JNI_H_INCLUDE) \
+    bionic \
+    bionic/libstdc++/include \
+    external/gtest/include \
+    external/stlport/stlport
+
+LOCAL_SRC_FILES := \
+        register.cpp \
+        GLTestHelper.cpp \
+        android_test_wrappedgtest_WrappedGTestActivity.cpp \
+        com_android_opengl_cts_GLTestActivity.cpp \
+        tests/GLTest_test.cpp
+
+LOCAL_SHARED_LIBRARIES := libEGL \
+                          libGLESv2 \
+                          libstlport \
+                          libandroid
+
+LOCAL_STATIC_LIBRARIES := libgtest
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/tests/tests/nativeopengl/libnativeopengltests/GLTestHelper.cpp b/tests/tests/nativeopengl/libnativeopengltests/GLTestHelper.cpp
new file mode 100644
index 0000000..ed527a6
--- /dev/null
+++ b/tests/tests/nativeopengl/libnativeopengltests/GLTestHelper.cpp
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2012 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.
+ */
+
+#include "GLTestHelper.h"
+
+using namespace android;
+
+ANativeWindow* GLTestHelper::mWindow;
+
+ANativeWindow* GLTestHelper::getWindow() {
+    return mWindow;
+}
+
+void GLTestHelper::setWindow(ANativeWindow* value) {
+    mWindow = value;
+}
diff --git a/tests/tests/nativeopengl/libnativeopengltests/GLTestHelper.h b/tests/tests/nativeopengl/libnativeopengltests/GLTestHelper.h
new file mode 100644
index 0000000..c0775f0
--- /dev/null
+++ b/tests/tests/nativeopengl/libnativeopengltests/GLTestHelper.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#ifndef ANDROID_GL_TEST_HELPER_H
+#define ANDROID_GL_TEST_HELPER_H
+
+#include <stdint.h>
+#include <sys/types.h>
+#include <android/native_window.h>
+#include <utils/RefBase.h>
+
+using namespace android;
+
+class GLTestHelper
+{
+private:
+    static ANativeWindow* mWindow;
+public:
+    static ANativeWindow* getWindow();
+    static void setWindow(ANativeWindow* value);
+};
+
+
+
+#endif // ANDROID_GL_TEST_HELPER_H
diff --git a/tests/tests/nativeopengl/libnativeopengltests/android_test_wrappedgtest_WrappedGTestActivity.cpp b/tests/tests/nativeopengl/libnativeopengltests/android_test_wrappedgtest_WrappedGTestActivity.cpp
new file mode 100644
index 0000000..5e1c30e
--- /dev/null
+++ b/tests/tests/nativeopengl/libnativeopengltests/android_test_wrappedgtest_WrappedGTestActivity.cpp
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2012 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.
+ */
+#include <android/log.h>
+#include <gtest/gtest.h>
+#include <jni.h>
+
+using namespace testing;
+
+class GTestListener : public EmptyTestEventListener {
+public:
+    GTestListener(JNIEnv *env, jobject activity)
+        : mActivity(activity), mEnv(env) {
+
+        jclass clazz = env->FindClass(
+              "android/test/wrappedgtest/WrappedGTestActivity");
+        mSendStatusID = env->GetMethodID(clazz, "sendStatus",
+              "(Ljava/lang/String;)V");
+        mMessageBuffer = new char[2048];
+    }
+
+    ~GTestListener() {
+        delete[] mMessageBuffer;
+    }
+
+private:
+    jobject   mActivity;
+    JNIEnv *  mEnv;
+    jmethodID mSendStatusID;
+    char *    mMessageBuffer;
+
+    virtual void OnTestIterationStart(const UnitTest& unit_test,
+            int iteration) {
+        snprintf(mMessageBuffer, sizeof(char) * 2048,
+                "[==========] Running %i tests from %i test cases.",
+                unit_test.test_to_run_count(),
+                unit_test.test_case_to_run_count());
+
+        mEnv->CallVoidMethod(mActivity, mSendStatusID,
+                mEnv->NewStringUTF(mMessageBuffer));
+    }
+
+    virtual void OnTestStart(const TestInfo& test_info) {
+        snprintf(mMessageBuffer, sizeof(char) * 2048, "[ RUN      ] %s.%s",
+                test_info.test_case_name(), test_info.name());
+
+        mEnv->CallVoidMethod(mActivity, mSendStatusID,
+                mEnv->NewStringUTF(mMessageBuffer));
+    }
+
+    virtual void OnTestPartResult(const TestPartResult& result) {
+        if (result.type() == TestPartResult::kSuccess) {
+            return;
+        }
+
+        snprintf(mMessageBuffer, sizeof(char) * 2048, "%s:%i: Failure\n%s",
+                result.file_name(), result.line_number(), result.message());
+
+        mEnv->CallVoidMethod(mActivity, mSendStatusID,
+                mEnv->NewStringUTF(mMessageBuffer));
+    }
+
+    virtual void OnTestEnd(const TestInfo& test_info) {
+        const char * result = test_info.result()->Passed() ?
+                "[       OK ] " : "[  FAILED  ] ";
+
+        snprintf(mMessageBuffer, sizeof(char) * 2048, "%s%s.%s (%lli ms)",
+                result, test_info.test_case_name(), test_info.name(),
+                test_info.result()->elapsed_time());
+
+        mEnv->CallVoidMethod(mActivity, mSendStatusID,
+                mEnv->NewStringUTF(mMessageBuffer));
+    }
+
+    virtual void OnTestIterationEnd(const UnitTest& unit_test, int iteration) {
+        snprintf(mMessageBuffer, sizeof(char) * 2048,
+                "[==========] %i tests from %i test cases ran. (%lli ms total)",
+                unit_test.test_to_run_count(),
+                unit_test.test_case_to_run_count(), unit_test.elapsed_time());
+
+        mEnv->CallVoidMethod(mActivity, mSendStatusID,
+                mEnv->NewStringUTF(mMessageBuffer));
+    }
+};
+
+static jboolean WrappedGTestActivity_runTests(JNIEnv *env, jobject obj,
+        jobject activity) {
+    // init gtest with no args
+    int argc = 0;
+    InitGoogleTest(&argc, (char**)NULL);
+
+    // delete the default listener
+    TestEventListeners& listeners = UnitTest::GetInstance()->listeners();
+    delete listeners.Release(listeners.default_result_printer());
+
+    // add custom listener
+    GTestListener * listener = new GTestListener(env, activity);
+    listeners.Append(listener);
+
+    // run tests
+    int result = RUN_ALL_TESTS();
+
+    delete listener;
+    return result;
+};
+
+static JNINativeMethod methods[] = {
+    // name, signature, function
+    { "runTests", "(Landroid/test/wrappedgtest/WrappedGTestActivity;)I", (void*)WrappedGTestActivity_runTests },
+};
+
+int register_WrappedGTestActivity(JNIEnv *env) {
+    return env->RegisterNatives(
+            env->FindClass("android/test/wrappedgtest/WrappedGTestActivity"),
+            methods, sizeof(methods) / sizeof(JNINativeMethod));
+};
diff --git a/tests/tests/nativeopengl/libnativeopengltests/com_android_opengl_cts_GLTestActivity.cpp b/tests/tests/nativeopengl/libnativeopengltests/com_android_opengl_cts_GLTestActivity.cpp
new file mode 100644
index 0000000..8544f2a
--- /dev/null
+++ b/tests/tests/nativeopengl/libnativeopengltests/com_android_opengl_cts_GLTestActivity.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2012 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.
+ */
+
+#include <gtest/gtest.h>
+#include <jni.h>
+
+#include "GLTestHelper.h"
+#include <android/native_window_jni.h>
+
+using namespace android;
+
+static void GLTestActivity_setSurface(JNIEnv *env, jobject obj,
+        jobject surface) {
+    ANativeWindow* window = ANativeWindow_fromSurface(env, surface);
+    GLTestHelper::setWindow(window);
+};
+
+static JNINativeMethod methods[] = {
+    // name, signature, function
+    { "setSurface", "(Landroid/view/Surface;)V", (void*)GLTestActivity_setSurface },
+};
+
+int register_GLTestActivity(JNIEnv *env) {
+    return env->RegisterNatives(
+            env->FindClass("com/android/opengl/cts/GLTestActivity"),
+            methods, sizeof(methods) / sizeof(JNINativeMethod));
+};
diff --git a/tests/tests/nativeopengl/libnativeopengltests/register.cpp b/tests/tests/nativeopengl/libnativeopengltests/register.cpp
new file mode 100644
index 0000000..97a8bd4
--- /dev/null
+++ b/tests/tests/nativeopengl/libnativeopengltests/register.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2012 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.
+ */
+
+#include <jni.h>
+#include <stdlib.h>
+
+/*
+ * This function is called automatically by the system when this
+ * library is loaded. We use it to register all our native functions,
+ * which is the recommended practice for Android.
+ */
+jint JNI_OnLoad(JavaVM *vm, void *reserved) {
+    JNIEnv *env = NULL;
+
+    if (vm->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) {
+        return JNI_ERR;
+    }
+
+    extern int register_WrappedGTestActivity(JNIEnv *);
+    if (register_WrappedGTestActivity(env)) {
+        return JNI_ERR;
+    }
+
+    extern int register_GLTestActivity(JNIEnv *);
+    if (register_GLTestActivity(env)) {
+        return JNI_ERR;
+    }
+
+    return JNI_VERSION_1_4;
+}
diff --git a/tests/tests/nativeopengl/libnativeopengltests/tests/GLTest_test.cpp b/tests/tests/nativeopengl/libnativeopengltests/tests/GLTest_test.cpp
new file mode 100644
index 0000000..37bfcb5
--- /dev/null
+++ b/tests/tests/nativeopengl/libnativeopengltests/tests/GLTest_test.cpp
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2012 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.
+ */
+
+
+#include <android/native_window.h>
+
+#include <gtest/gtest.h>
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+
+#include "GLTestHelper.h"
+
+
+namespace android {
+
+class GLTest : public ::testing::Test {
+
+protected:
+
+    GLTest():
+            mEglDisplay(EGL_NO_DISPLAY),
+            mEglSurface(EGL_NO_SURFACE),
+            mEglContext(EGL_NO_CONTEXT) {
+    }
+
+
+   virtual void SetUp() {
+        mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+        ASSERT_EQ(EGL_SUCCESS, eglGetError());
+        ASSERT_NE(EGL_NO_DISPLAY, mEglDisplay);
+
+        EGLint majorVersion;
+        EGLint minorVersion;
+        EXPECT_TRUE(eglInitialize(mEglDisplay, &majorVersion, &minorVersion));
+        ASSERT_EQ(EGL_SUCCESS, eglGetError());
+
+        EGLint numConfigs = 0;
+        EXPECT_TRUE(eglChooseConfig(mEglDisplay, getConfigAttribs(), &mGlConfig,
+                1, &numConfigs));
+        ASSERT_EQ(EGL_SUCCESS, eglGetError());
+
+        char* displaySecsEnv = getenv("GLTEST_DISPLAY_SECS");
+        if (displaySecsEnv != NULL) {
+            mDisplaySecs = atoi(displaySecsEnv);
+            if (mDisplaySecs < 0) {
+                mDisplaySecs = 0;
+            }
+        } else {
+            mDisplaySecs = 0;
+        }
+
+        if (mDisplaySecs > 0) {
+            mEglSurface = eglCreateWindowSurface(mEglDisplay, mGlConfig,
+                    GLTestHelper::getWindow(), NULL);
+        } else {
+            EGLint pbufferAttribs[] = {
+                EGL_WIDTH, getSurfaceWidth(),
+                EGL_HEIGHT, getSurfaceHeight(),
+                EGL_NONE };
+
+            mEglSurface = eglCreatePbufferSurface(mEglDisplay, mGlConfig,
+                    pbufferAttribs);
+        }
+        ASSERT_EQ(EGL_SUCCESS, eglGetError());
+        ASSERT_NE(EGL_NO_SURFACE, mEglSurface);
+
+        mEglContext = eglCreateContext(mEglDisplay, mGlConfig, EGL_NO_CONTEXT,
+                getContextAttribs());
+        ASSERT_EQ(EGL_SUCCESS, eglGetError());
+        ASSERT_NE(EGL_NO_CONTEXT, mEglContext);
+
+        EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
+                mEglContext));
+        ASSERT_EQ(EGL_SUCCESS, eglGetError());
+
+        EGLint w, h;
+        EXPECT_TRUE(eglQuerySurface(mEglDisplay, mEglSurface, EGL_WIDTH, &w));
+        ASSERT_EQ(EGL_SUCCESS, eglGetError());
+        EXPECT_TRUE(eglQuerySurface(mEglDisplay, mEglSurface, EGL_HEIGHT, &h));
+        ASSERT_EQ(EGL_SUCCESS, eglGetError());
+
+        glViewport(0, 0, w, h);
+        ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+    }
+
+    virtual void TearDown() {
+        // Display the result
+        if (mDisplaySecs > 0 && mEglSurface != EGL_NO_SURFACE) {
+            eglSwapBuffers(mEglDisplay, mEglSurface);
+            sleep(mDisplaySecs);
+        }
+
+        if (mEglContext != EGL_NO_CONTEXT) {
+            eglDestroyContext(mEglDisplay, mEglContext);
+        }
+        if (mEglSurface != EGL_NO_SURFACE) {
+            eglDestroySurface(mEglDisplay, mEglSurface);
+        }
+        if (mEglDisplay != EGL_NO_DISPLAY) {
+            eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE,
+                    EGL_NO_CONTEXT);
+            eglTerminate(mEglDisplay);
+        }
+        ASSERT_EQ(EGL_SUCCESS, eglGetError());
+    }
+
+    virtual EGLint const* getConfigAttribs() {
+        static EGLint sDefaultConfigAttribs[] = {
+            EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
+            EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+            EGL_RED_SIZE, 8,
+            EGL_GREEN_SIZE, 8,
+            EGL_BLUE_SIZE, 8,
+            EGL_ALPHA_SIZE, 8,
+            EGL_DEPTH_SIZE, 16,
+            EGL_STENCIL_SIZE, 8,
+            EGL_NONE };
+
+        return sDefaultConfigAttribs;
+    }
+
+    virtual EGLint const* getContextAttribs() {
+        static EGLint sDefaultContextAttribs[] = {
+            EGL_CONTEXT_CLIENT_VERSION, 2,
+            EGL_NONE };
+
+        return sDefaultContextAttribs;
+    }
+
+    virtual EGLint getSurfaceWidth() {
+        return 512;
+    }
+
+    virtual EGLint getSurfaceHeight() {
+        return 512;
+    }
+
+    bool checkPixel(GLubyte * actual, GLubyte * expected, int tolerance) {
+        for (int i = 0; i < 4; i++) {
+            if (abs(actual[i] - expected[i]) > tolerance) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    ::testing::AssertionResult AssertPixel(const char* a_expr,
+            const char* e_expr, const char* t_expr, GLubyte * actual,
+            GLubyte * expected, int tolerance) {
+
+        if (checkPixel(actual, expected, tolerance)) {
+            return ::testing::AssertionSuccess();
+        }
+
+        return ::testing::AssertionFailure()
+            << "Pixel comparison failed with tolerance " << tolerance << "\n"
+            << "Actual: r=" << (int)actual[0] << " g=" << (int)actual[1]
+            << " b=" << (int)actual[2] << " a=" << (int)actual[3] << "\n"
+            << "Expected: r=" << (int)expected[0] << " g=" << (int)expected[1]
+            << " b=" << (int)expected[2] << " a=" << (int)expected[3] << "\n";
+    }
+
+    int mDisplaySecs;
+
+    EGLDisplay mEglDisplay;
+    EGLSurface mEglSurface;
+    EGLContext mEglContext;
+    EGLConfig  mGlConfig;
+};
+
+TEST_F(GLTest, ClearColorTest) {
+    glClearColor(0.2, 0.2, 0.2, 0.2);
+    glClear(GL_COLOR_BUFFER_BIT);
+    GLubyte expected[4] = { 51, 51, 51, 51 };
+    GLubyte pixel[4];
+    glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel);
+    ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+    ASSERT_PRED_FORMAT3(AssertPixel, pixel, expected, 2);
+}
+
+} // namespace android
diff --git a/tests/tests/nativeopengl/src/com/android/opengl/cts/GLTestActivity.java b/tests/tests/nativeopengl/src/com/android/opengl/cts/GLTestActivity.java
new file mode 100644
index 0000000..1633a93
--- /dev/null
+++ b/tests/tests/nativeopengl/src/com/android/opengl/cts/GLTestActivity.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2012 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.opengl.cts;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.os.Bundle;
+import android.test.wrappedgtest.WrappedGTestActivity;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.Surface;
+
+public class GLTestActivity extends WrappedGTestActivity {
+
+    private SurfaceView mSurfaceView;
+    private SurfaceHolder.Callback mHolderCallback = new SurfaceHolder.Callback() {
+
+        @Override
+        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+             setSurface(holder.getSurface());
+        }
+
+        @Override
+        public void surfaceCreated(SurfaceHolder holder) {
+             setSurface(holder.getSurface());
+        }
+
+        @Override
+        public void surfaceDestroyed(SurfaceHolder holder) {
+        }
+    };
+
+    public void onCreate(Bundle data) {
+        super.onCreate(data);
+        mSurfaceView = new SurfaceView(this);
+        mSurfaceView.getHolder().addCallback(mHolderCallback);
+        setContentView(mSurfaceView);
+        System.loadLibrary("nativeopengltests");
+    }
+
+    private static native void setSurface(Surface surface);
+}
diff --git a/tests/tests/nativeopengl/src/com/android/opengl/cts/GLTestInstrumentation.java b/tests/tests/nativeopengl/src/com/android/opengl/cts/GLTestInstrumentation.java
new file mode 100644
index 0000000..913f0eb
--- /dev/null
+++ b/tests/tests/nativeopengl/src/com/android/opengl/cts/GLTestInstrumentation.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2012 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.opengl.cts;
+
+import android.test.wrappedgtest.WrappedGTestInstrumentation;
+
+/**
+ * adb shell am instrument -w com.android.opengl.cts/.GLTestInstrumentation
+ */
+public class GLTestInstrumentation extends WrappedGTestInstrumentation {
+    public GLTestInstrumentation() {
+        mActivityClass = GLTestActivity.class;
+    }
+}
diff --git a/tests/tests/net/Android.mk b/tests/tests/net/Android.mk
index b327392..a6543b3 100644
--- a/tests/tests/net/Android.mk
+++ b/tests/tests/net/Android.mk
@@ -21,7 +21,9 @@
 # and when built explicitly put it in the data partition
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner voip-common
+
+LOCAL_JNI_SHARED_LIBRARIES := libnativedns_jni
 
 # include CtsTestServer as a temporary hack to free net.cts from cts.stub.
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
@@ -34,3 +36,5 @@
 #LOCAL_SDK_VERSION := current
 
 include $(BUILD_CTS_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/suite/pts/hostTests/ptshostutil/Android.mk b/tests/tests/net/jni/Android.mk
similarity index 64%
copy from suite/pts/hostTests/ptshostutil/Android.mk
copy to tests/tests/net/jni/Android.mk
index 14e786a..75982de 100644
--- a/suite/pts/hostTests/ptshostutil/Android.mk
+++ b/tests/tests/net/jni/Android.mk
@@ -1,4 +1,4 @@
-# Copyright (C) 2012 The Android Open Source Project
+# Copyright (C) 2013 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.
@@ -12,18 +12,19 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-LOCAL_PATH := $(call my-dir)
+LOCAL_PATH:= $(call my-dir)
 
 include $(CLEAR_VARS)
 
-LOCAL_SRC_FILES := \
-    $(call all-java-files-under, src)
+LOCAL_MODULE := libnativedns_jni
 
-LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed-prebuilt ddmlib-prebuilt junit
-
+# Don't include this package in any configuration by default.
 LOCAL_MODULE_TAGS := optional
 
-LOCAL_MODULE := ptshostutil
+LOCAL_SRC_FILES := NativeDnsJni.c
 
-include $(BUILD_HOST_JAVA_LIBRARY)
+LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
 
+LOCAL_SHARED_LIBRARIES := libnativehelper liblog
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/tests/tests/net/jni/NativeDnsJni.c b/tests/tests/net/jni/NativeDnsJni.c
new file mode 100644
index 0000000..de9bb67
--- /dev/null
+++ b/tests/tests/net/jni/NativeDnsJni.c
@@ -0,0 +1,166 @@
+/*
+ * 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.
+ */
+
+#include <arpa/inet.h>
+#include <jni.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <utils/Log.h>
+
+JNIEXPORT jboolean Java_android_net_cts_DnsTest_testNativeDns(JNIEnv* env, jclass class)
+{
+    const char *node = "www.google.com";
+    char *service = NULL;
+    struct addrinfo *answer;
+
+    int res = getaddrinfo(node, service, NULL, &answer);
+    ALOGD("getaddrinfo(www.google.com) gave res=%d (%s)", res, gai_strerror(res));
+    if (res != 0) return JNI_FALSE;
+
+    // check for v4 & v6
+    {
+        int foundv4 = 0;
+        int foundv6 = 0;
+        struct addrinfo *current = answer;
+        while (current != NULL) {
+            char buf[256];
+            if (current->ai_addr->sa_family == AF_INET) {
+                inet_ntop(current->ai_family, &((struct sockaddr_in *)current->ai_addr)->sin_addr,
+                        buf, sizeof(buf));
+                foundv4 = 1;
+                ALOGD("  %s", buf);
+            } else if (current->ai_addr->sa_family == AF_INET6) {
+                inet_ntop(current->ai_family, &((struct sockaddr_in6 *)current->ai_addr)->sin6_addr,
+                        buf, sizeof(buf));
+                foundv6 = 1;
+                ALOGD("  %s", buf);
+            }
+            current = current->ai_next;
+        }
+
+        freeaddrinfo(answer);
+        answer = NULL;
+        if (foundv4 != 1 || foundv6 != 1) {
+            ALOGD("getaddrinfo(www.google.com) didn't find both v4 and v6");
+            return JNI_FALSE;
+        }
+    }
+
+    node = "ipv6.google.com";
+    res = getaddrinfo(node, service, NULL, &answer);
+    ALOGD("getaddrinfo(ipv6.google.com) gave res=%d", res);
+    if (res != 0) return JNI_FALSE;
+
+    {
+        int foundv4 = 0;
+        int foundv6 = 0;
+        struct addrinfo *current = answer;
+        while (current != NULL) {
+            char buf[256];
+            if (current->ai_addr->sa_family == AF_INET) {
+                inet_ntop(current->ai_family, &((struct sockaddr_in *)current->ai_addr)->sin_addr,
+                        buf, sizeof(buf));
+                ALOGD("  %s", buf);
+                foundv4 = 1;
+            } else if (current->ai_addr->sa_family == AF_INET6) {
+                inet_ntop(current->ai_family, &((struct sockaddr_in6 *)current->ai_addr)->sin6_addr,
+                        buf, sizeof(buf));
+                ALOGD("  %s", buf);
+                foundv6 = 1;
+            }
+            current = current->ai_next;
+        }
+
+        freeaddrinfo(answer);
+        answer = NULL;
+        if (foundv4 == 1 || foundv6 != 1) {
+            ALOGD("getaddrinfo(ipv6.google.com) didn't find only v6");
+            return JNI_FALSE;
+        }
+    }
+
+    // getnameinfo
+    struct sockaddr_in sa4;
+    sa4.sin_family = AF_INET;
+    sa4.sin_port = 0;
+    inet_pton(AF_INET, "173.252.110.27", &(sa4.sin_addr));
+
+    struct sockaddr_in6 sa6;
+    sa6.sin6_family = AF_INET6;
+    sa6.sin6_port = 0;
+    sa6.sin6_flowinfo = 0;
+    sa6.sin6_scope_id = 0;
+    inet_pton(AF_INET6, "2001:4860:4001:802::1008", &(sa6.sin6_addr));
+
+    char buf[NI_MAXHOST];
+    int flags = NI_NAMEREQD;
+
+    res = getnameinfo((const struct sockaddr*)&sa4, sizeof(sa4), buf, sizeof(buf), NULL, 0, flags);
+    if (res != 0) {
+        ALOGD("getnameinfo(173.252.110.27 (facebook) ) gave error %d (%s)", res, gai_strerror(res));
+        return JNI_FALSE;
+    }
+    if (strstr(buf, "facebook.com") == NULL) {
+        ALOGD("getnameinfo(173.252.110.27 (facebook) ) didn't return facebook.com: %s", buf);
+        return JNI_FALSE;
+    }
+
+    memset(buf, sizeof(buf), 0);
+    res = getnameinfo((const struct sockaddr*)&sa6, sizeof(sa6), buf, sizeof(buf),
+            NULL, 0, flags);
+    if (res != 0) {
+        ALOGD("getnameinfo(2a03:2880:2110:df01:face:b00c::8 (facebook) ) gave error %d (%s)",
+                res, gai_strerror(res));
+        return JNI_FALSE;
+    }
+    if (strstr(buf, "1e100.net") == NULL) {
+        ALOGD("getnameinfo(2a03:2880:2110:df01:face:b00c::8) didn't return facebook.com: %s", buf);
+        return JNI_FALSE;
+    }
+
+    // gethostbyname
+    struct hostent *my_hostent = gethostbyname("www.mit.edu");
+    if (my_hostent == NULL) {
+        ALOGD("gethostbyname(www.mit.edu) gave null response");
+        return JNI_FALSE;
+    }
+    if ((my_hostent->h_addr_list == NULL) || (*my_hostent->h_addr_list == NULL)) {
+        ALOGD("gethostbyname(www.mit.edu) gave 0 addresses");
+        return JNI_FALSE;
+    }
+    {
+        char **current = my_hostent->h_addr_list;
+        while (*current != NULL) {
+            char buf[256];
+            inet_ntop(my_hostent->h_addrtype, *current, buf, sizeof(buf));
+            ALOGD("gethostbyname(www.mit.edu) gave %s", buf);
+            current++;
+        }
+    }
+
+    // gethostbyaddr
+    char addr6[16];
+    inet_pton(AF_INET6, "2001:4b10:bbc::2", addr6);
+    my_hostent = gethostbyaddr(addr6, sizeof(addr6), AF_INET6);
+    if (my_hostent == NULL) {
+        ALOGD("gethostbyaddr(2001:4b10:bbc::2 (bbc) ) gave null response");
+        return JNI_FALSE;
+    }
+    ALOGD("gethostbyaddr(2001:4b10:bbc::2 (bbc) ) gave %s for name",
+    my_hostent->h_name ? my_hostent->h_name : "null");
+    if (my_hostent->h_name == NULL) return JNI_FALSE;
+    return JNI_TRUE;
+}
diff --git a/tests/tests/net/src/android/net/cts/DnsTest.java b/tests/tests/net/src/android/net/cts/DnsTest.java
new file mode 100644
index 0000000..cdd95aa
--- /dev/null
+++ b/tests/tests/net/src/android/net/cts/DnsTest.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2013 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.net.cts;
+
+import android.os.SystemClock;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+
+public class DnsTest extends AndroidTestCase {
+
+    static {
+        System.loadLibrary("nativedns_jni");
+    }
+
+    private static final boolean DBG = false;
+    private static final String TAG = "DnsTest";
+
+    /**
+     * @return true on success
+     */
+    private static native boolean testNativeDns();
+
+    /**
+     * Verify:
+     * DNS works - forwards and backwards, giving ipv4 and ipv6
+     * Test that DNS work on v4 and v6 networks
+     * Test Native dns calls (4)
+     * Todo:
+     * Cache is flushed when we change networks
+     * have per-network caches
+     * No cache when there's no network
+     * Perf - measure size of first and second tier caches and their effect
+     * Assert requires network permission
+     */
+    public void testDnsWorks() {
+        InetAddress addrs[] = {};
+        try {
+            addrs = InetAddress.getAllByName("www.google.com");
+        } catch (UnknownHostException e) {}
+        assertTrue(addrs.length != 0);
+        boolean foundV4 = false, foundV6 = false;
+        for (InetAddress addr : addrs) {
+            if (addr instanceof Inet4Address) foundV4 = true;
+            else if (addr instanceof Inet6Address) foundV6 = true;
+            if (DBG) Log.e(TAG, "www.google.com gave " + addr.toString());
+        }
+        assertTrue(foundV4);
+        assertTrue(foundV6);
+        try {
+            addrs = InetAddress.getAllByName("ipv6.google.com");
+        } catch (UnknownHostException e) {}
+        assertTrue(addrs.length != 0);
+        foundV4 = false;
+        foundV6 = false;
+        for (InetAddress addr : addrs) {
+            if (addr instanceof Inet4Address) foundV4 = true;
+            else if (addr instanceof Inet6Address) foundV6 = true;
+            if (DBG) Log.e(TAG, "ipv6.google.com gave " + addr.toString());
+        }
+        assertTrue(foundV4 == false);
+        assertTrue(foundV6 == true);
+        assertTrue(testNativeDns());
+    }
+
+    private static final String[] URLS = { "www.google.com", "ipv6.google.com", "www.yahoo.com",
+            "facebook.com", "youtube.com", "blogspot.com", "baidu.com", "wikipedia.org",
+// live.com fails rev lookup.
+            "twitter.com", "qq.com", "msn.com", "yahoo.co.jp", "linkedin.com",
+            "taobao.com", "google.co.in", "sina.com.cn", "amazon.com", "wordpress.com",
+            "google.co.uk", "ebay.com", "yandex.ru", "163.com", "google.co.jp", "google.fr",
+            "microsoft.com", "paypal.com", "google.com.br", "flickr.com",
+            "mail.ru", "craigslist.org", "fc2.com", "google.it",
+// "apple.com", fails rev lookup
+            "google.es",
+            "imdb.com", "google.ru", "soho.com", "bbc.co.uk", "vkontakte.ru", "ask.com",
+            "tumblr.com", "weibo.com", "go.com", "xvideos.com", "livejasmin.com", "cnn.com",
+            "youku.com", "blogspot.com", "soso.com", "google.ca", "aol.com", "tudou.com",
+            "xhamster.com", "megaupload.com", "ifeng.com", "zedo.com", "mediafire.com", "ameblo.jp",
+            "pornhub.com", "google.co.id", "godaddy.com", "adobe.com", "rakuten.co.jp", "about.com",
+            "espn.go.com", "4shared.com", "alibaba.com","ebay.de", "yieldmanager.com",
+            "wordpress.org", "livejournal.com", "google.com.tr", "google.com.mx", "renren.com",
+           "livedoor.com", "google.com.au", "youporn.com", "uol.com.br", "cnet.com", "conduit.com",
+            "google.pl", "myspace.com", "nytimes.com", "ebay.co.uk", "chinaz.com", "hao123.com",
+            "thepiratebay.org", "doubleclick.com", "alipay.com", "netflix.com", "cnzz.com",
+            "huffingtonpost.com", "twitpic.com", "weather.com", "babylon.com", "amazon.de",
+            "dailymotion.com", "orkut.com", "orkut.com.br", "google.com.sa", "odnoklassniki.ru",
+            "amazon.co.jp", "google.nl", "goo.ne.jp", "stumbleupon.com", "tube8.com", "tmall.com",
+            "imgur.com", "globo.com", "secureserver.net", "fileserve.com", "tianya.cn", "badoo.com",
+            "ehow.com", "photobucket.com", "imageshack.us", "xnxx.com", "deviantart.com",
+            "filestube.com", "addthis.com", "douban.com", "vimeo.com", "sogou.com",
+            "stackoverflow.com", "reddit.com", "dailymail.co.uk", "redtube.com", "megavideo.com",
+            "taringa.net", "pengyou.com", "amazon.co.uk", "fbcdn.net", "aweber.com", "spiegel.de",
+            "rapidshare.com", "mixi.jp", "360buy.com", "google.cn", "digg.com", "answers.com",
+            "bit.ly", "indiatimes.com", "skype.com", "yfrog.com", "optmd.com", "google.com.eg",
+            "google.com.pk", "58.com", "hotfile.com", "google.co.th",
+            "bankofamerica.com", "sourceforge.net", "maktoob.com", "warriorforum.com", "rediff.com",
+            "google.co.za", "56.com", "torrentz.eu", "clicksor.com", "avg.com",
+            "download.com", "ku6.com", "statcounter.com", "foxnews.com", "google.com.ar",
+            "nicovideo.jp", "reference.com", "liveinternet.ru", "ucoz.ru", "xinhuanet.com",
+            "xtendmedia.com", "naver.com", "youjizz.com", "domaintools.com", "sparkstudios.com",
+            "rambler.ru", "scribd.com", "kaixin001.com", "mashable.com", "adultfirendfinder.com",
+            "files.wordpress.com", "guardian.co.uk", "bild.de", "yelp.com", "wikimedia.org",
+            "chase.com", "onet.pl", "ameba.jp", "pconline.com.cn", "free.fr", "etsy.com",
+            "typepad.com", "youdao.com", "megaclick.com", "digitalpoint.com", "blogfa.com",
+            "salesforce.com", "adf.ly", "ganji.com", "wikia.com", "archive.org", "terra.com.br",
+            "w3schools.com", "ezinearticles.com", "wjs.com", "google.com.my", "clickbank.com",
+            "squidoo.com", "hulu.com", "repubblica.it", "google.be", "allegro.pl", "comcast.net",
+            "narod.ru", "zol.com.cn", "orange.fr", "soufun.com", "hatena.ne.jp", "google.gr",
+            "in.com", "techcrunch.com", "orkut.co.in", "xunlei.com",
+            "reuters.com", "google.com.vn", "hostgator.com", "kaskus.us", "espncricinfo.com",
+            "hootsuite.com", "qiyi.com", "gmx.net", "xing.com", "php.net", "soku.com", "web.de",
+            "libero.it", "groupon.com", "51.la", "slideshare.net", "booking.com", "seesaa.net",
+            "126.com", "telegraph.co.uk", "wretch.cc", "twimg.com", "rutracker.org", "angege.com",
+            "nba.com", "dell.com", "leboncoin.fr", "people.com", "google.com.tw", "walmart.com",
+            "daum.net", "2ch.net", "constantcontact.com", "nifty.com", "mywebsearch.com",
+            "tripadvisor.com", "google.se", "paipai.com", "google.com.ua", "ning.com", "hp.com",
+            "google.at", "joomla.org", "icio.us", "hudong.com", "csdn.net", "getfirebug.com",
+            "ups.com", "cj.com", "google.ch", "camzap.com", "wordreference.com", "tagged.com",
+            "wp.pl", "mozilla.com", "google.ru", "usps.com", "china.com", "themeforest.net",
+            "search-results.com", "tribalfusion.com", "thefreedictionary.com", "isohunt.com",
+            "linkwithin.com", "cam4.com", "plentyoffish.com", "wellsfargo.com", "metacafe.com",
+            "depositfiles.com", "freelancer.com", "opendns.com", "homeway.com", "engadget.com",
+            "10086.cn", "360.cn", "marca.com", "dropbox.com", "ign.com", "match.com", "google.pt",
+            "facemoods.com", "hardsextube.com", "google.com.ph", "lockerz.com", "istockphoto.com",
+            "partypoker.com", "netlog.com", "outbrain.com", "elpais.com", "fiverr.com",
+            "biglobe.ne.jp", "corriere.it", "love21cn.com", "yesky.com", "spankwire.com",
+            "ig.com.br", "imagevenue.com", "hubpages.com", "google.co.ve"};
+
+// TODO - this works, but is slow and cts doesn't do anything with the result.
+// Maybe require a min performance, a min cache size (detectable) and/or move
+// to perf testing
+    private static final int LOOKUP_COUNT_GOAL = URLS.length;
+    public void skiptestDnsPerf() {
+        ArrayList<String> results = new ArrayList<String>();
+        int failures = 0;
+        try {
+            for (int numberOfUrls = URLS.length; numberOfUrls > 0; numberOfUrls--) {
+                failures = 0;
+                int iterationLimit = LOOKUP_COUNT_GOAL / numberOfUrls;
+                long startTime = SystemClock.elapsedRealtimeNanos();
+                for (int iteration = 0; iteration < iterationLimit; iteration++) {
+                    for (int urlIndex = 0; urlIndex < numberOfUrls; urlIndex++) {
+                        try {
+                            InetAddress addr = InetAddress.getByName(URLS[urlIndex]);
+                        } catch (UnknownHostException e) {
+                            Log.e(TAG, "failed first lookup of " + URLS[urlIndex]);
+                            failures++;
+                            try {
+                                InetAddress addr = InetAddress.getByName(URLS[urlIndex]);
+                            } catch (UnknownHostException ee) {
+                                failures++;
+                                Log.e(TAG, "failed SECOND lookup of " + URLS[urlIndex]);
+                            }
+                        }
+                    }
+                }
+                long endTime = SystemClock.elapsedRealtimeNanos();
+                float nsPer = ((float)(endTime-startTime) / iterationLimit) / numberOfUrls/ 1000;
+                String thisResult = new String("getByName for " + numberOfUrls + " took " +
+                        (endTime - startTime)/1000 + "(" + nsPer + ") with " +
+                        failures + " failures\n");
+                Log.d(TAG, thisResult);
+                results.add(thisResult);
+            }
+            // build up a list of addresses
+            ArrayList<byte[]> addressList = new ArrayList<byte[]>();
+            for (String url : URLS) {
+                try {
+                    InetAddress addr = InetAddress.getByName(url);
+                    addressList.add(addr.getAddress());
+                } catch (UnknownHostException e) {
+                    Log.e(TAG, "Exception making reverseDNS list: " + e.toString());
+                }
+            }
+            for (int numberOfAddrs = addressList.size(); numberOfAddrs > 0; numberOfAddrs--) {
+                int iterationLimit = LOOKUP_COUNT_GOAL / numberOfAddrs;
+                failures = 0;
+                long startTime = SystemClock.elapsedRealtimeNanos();
+                for (int iteration = 0; iteration < iterationLimit; iteration++) {
+                    for (int addrIndex = 0; addrIndex < numberOfAddrs; addrIndex++) {
+                        try {
+                            InetAddress addr = InetAddress.getByAddress(addressList.get(addrIndex));
+                            String hostname = addr.getHostName();
+                        } catch (UnknownHostException e) {
+                            failures++;
+                            Log.e(TAG, "Failure doing reverse DNS lookup: " + e.toString());
+                            try {
+                                InetAddress addr =
+                                        InetAddress.getByAddress(addressList.get(addrIndex));
+                                String hostname = addr.getHostName();
+
+                            } catch (UnknownHostException ee) {
+                                failures++;
+                                Log.e(TAG, "Failure doing SECOND reverse DNS lookup: " +
+                                        ee.toString());
+                            }
+                        }
+                    }
+                }
+                long endTime = SystemClock.elapsedRealtimeNanos();
+                float nsPer = ((endTime-startTime) / iterationLimit) / numberOfAddrs / 1000;
+                String thisResult = new String("getHostName for " + numberOfAddrs + " took " +
+                        (endTime - startTime)/1000 + "(" + nsPer + ") with " +
+                        failures + " failures\n");
+                Log.d(TAG, thisResult);
+                results.add(thisResult);
+            }
+            for (String result : results) Log.d(TAG, result);
+
+            InetAddress exit = InetAddress.getByName("exitrightnow.com");
+            Log.e(TAG, " exit address= "+exit.toString());
+
+        } catch (Exception e) {
+            Log.e(TAG, "bad URL in testDnsPerf: " + e.toString());
+        }
+    }
+}
diff --git a/tests/tests/net/src/android/net/cts/TrafficStatsTest.java b/tests/tests/net/src/android/net/cts/TrafficStatsTest.java
index a5bbd98..89933bf 100644
--- a/tests/tests/net/src/android/net/cts/TrafficStatsTest.java
+++ b/tests/tests/net/src/android/net/cts/TrafficStatsTest.java
@@ -42,19 +42,29 @@
                    TrafficStats.getMobileRxBytes() >= 0);
     }
 
+    long tcpPacketToIpBytes(long packetCount, long bytes) {
+        // ip header + tcp header + data.
+        // Tcp header is mostly 32. Syn has different tcp options -> 40. Don't care.
+        return packetCount * (20 + 32 + bytes);
+    }
+
     public void testTrafficStatsForLocalhost() throws IOException {
-        long mobileTxPacketsBefore = TrafficStats.getTotalTxPackets();
-        long mobileRxPacketsBefore = TrafficStats.getTotalRxPackets();
-        long mobileTxBytesBefore = TrafficStats.getTotalTxBytes();
-        long mobileRxBytesBefore = TrafficStats.getTotalRxBytes();
+        long mobileTxPacketsBefore = TrafficStats.getMobileTxPackets();
+        long mobileRxPacketsBefore = TrafficStats.getMobileRxPackets();
+        long mobileTxBytesBefore = TrafficStats.getMobileTxBytes();
+        long mobileRxBytesBefore = TrafficStats.getMobileRxBytes();
         long totalTxPacketsBefore = TrafficStats.getTotalTxPackets();
         long totalRxPacketsBefore = TrafficStats.getTotalRxPackets();
         long totalTxBytesBefore = TrafficStats.getTotalTxBytes();
         long totalRxBytesBefore = TrafficStats.getTotalRxBytes();
         long uidTxBytesBefore = TrafficStats.getUidTxBytes(Process.myUid());
         long uidRxBytesBefore = TrafficStats.getUidRxBytes(Process.myUid());
+        long uidTxPacketsBefore = TrafficStats.getUidTxPackets(Process.myUid());
+        long uidRxPacketsBefore = TrafficStats.getUidRxPackets(Process.myUid());
 
         // Transfer 1MB of data across an explicitly localhost socket.
+        final int byteCount = 1024;
+        final int packetCount = 1024;
 
         final ServerSocket server = new ServerSocket(0);
         new Thread("TrafficStatsTest.testTrafficStatsForLocalhost") {
@@ -62,9 +72,15 @@
             public void run() {
                 try {
                     Socket socket = new Socket("localhost", server.getLocalPort());
+                    // Make sure that each write()+flush() turns into a packet:
+                    // disable Nagle.
+                    socket.setTcpNoDelay(true);
                     OutputStream out = socket.getOutputStream();
-                    byte[] buf = new byte[1024];
-                    for (int i = 0; i < 1024; i++) out.write(buf);
+                    byte[] buf = new byte[byteCount];
+                    for (int i = 0; i < packetCount; i++) {
+                        out.write(buf);
+                        out.flush();
+                    }
                     out.close();
                     socket.close();
                 } catch (IOException e) {
@@ -75,9 +91,9 @@
         try {
             Socket socket = server.accept();
             InputStream in = socket.getInputStream();
-            byte[] buf = new byte[1024];
+            byte[] buf = new byte[byteCount];
             int read = 0;
-            while (read < 1048576) {
+            while (read < byteCount * packetCount) {
                 int n = in.read(buf);
                 assertTrue("Unexpected EOF", n > 0);
                 read += n;
@@ -92,50 +108,76 @@
         } catch (InterruptedException e) {
         }
 
-        long mobileTxPacketsAfter = TrafficStats.getTotalTxPackets();
-        long mobileRxPacketsAfter = TrafficStats.getTotalRxPackets();
-        long mobileTxBytesAfter = TrafficStats.getTotalTxBytes();
-        long mobileRxBytesAfter = TrafficStats.getTotalRxBytes();
+        long mobileTxPacketsAfter = TrafficStats.getMobileTxPackets();
+        long mobileRxPacketsAfter = TrafficStats.getMobileRxPackets();
+        long mobileTxBytesAfter = TrafficStats.getMobileTxBytes();
+        long mobileRxBytesAfter = TrafficStats.getMobileRxBytes();
         long totalTxPacketsAfter = TrafficStats.getTotalTxPackets();
         long totalRxPacketsAfter = TrafficStats.getTotalRxPackets();
         long totalTxBytesAfter = TrafficStats.getTotalTxBytes();
         long totalRxBytesAfter = TrafficStats.getTotalRxBytes();
         long uidTxBytesAfter = TrafficStats.getUidTxBytes(Process.myUid());
         long uidRxBytesAfter = TrafficStats.getUidRxBytes(Process.myUid());
-
-        // Localhost traffic should *not* count against mobile or total stats.
-        // There might be some other traffic, but nowhere near 1MB.
-
-        assertTrue("mtxp: " + mobileTxPacketsBefore + " -> " + mobileTxPacketsAfter,
-               mobileTxPacketsAfter >= mobileTxPacketsBefore &&
-               mobileTxPacketsAfter <= mobileTxPacketsBefore + 500);
-        assertTrue("mrxp: " + mobileRxPacketsBefore + " -> " + mobileRxPacketsAfter,
-               mobileRxPacketsAfter >= mobileRxPacketsBefore &&
-               mobileRxPacketsAfter <= mobileRxPacketsBefore + 500);
-        assertTrue("mtxb: " + mobileTxBytesBefore + " -> " + mobileTxBytesAfter,
-               mobileTxBytesAfter >= mobileTxBytesBefore &&
-               mobileTxBytesAfter <= mobileTxBytesBefore + 200000);
-        assertTrue("mrxb: " + mobileRxBytesBefore + " -> " + mobileRxBytesAfter,
-               mobileRxBytesAfter >= mobileRxBytesBefore &&
-               mobileRxBytesAfter <= mobileRxBytesBefore + 200000);
-
-        assertTrue("ttxp: " + totalTxPacketsBefore + " -> " + totalTxPacketsAfter,
-               totalTxPacketsAfter >= totalTxPacketsBefore &&
-               totalTxPacketsAfter <= totalTxPacketsBefore + 500);
-        assertTrue("trxp: " + totalRxPacketsBefore + " -> " + totalRxPacketsAfter,
-               totalRxPacketsAfter >= totalRxPacketsBefore &&
-               totalRxPacketsAfter <= totalRxPacketsBefore + 500);
-        assertTrue("ttxb: " + totalTxBytesBefore + " -> " + totalTxBytesAfter,
-               totalTxBytesAfter >= totalTxBytesBefore &&
-               totalTxBytesAfter <= totalTxBytesBefore + 200000);
-        assertTrue("trxb: " + totalRxBytesBefore + " -> " + totalRxBytesAfter,
-               totalRxBytesAfter >= totalRxBytesBefore &&
-               totalRxBytesAfter <= totalRxBytesBefore + 200000);
+        long uidTxPacketsAfter = TrafficStats.getUidTxPackets(Process.myUid());
+        long uidRxPacketsAfter = TrafficStats.getUidRxPackets(Process.myUid());
+        long uidTxDeltaBytes = uidTxBytesAfter - uidTxBytesBefore;
+        long uidTxDeltaPackets = uidTxPacketsAfter - uidTxPacketsBefore;
+        long uidRxDeltaBytes = uidRxBytesAfter - uidRxBytesBefore;
+        long uidRxDeltaPackets = uidRxPacketsAfter - uidRxPacketsBefore;
 
         // Localhost traffic *does* count against per-UID stats.
-        assertTrue("uidtxb: " + uidTxBytesBefore + " -> " + uidTxBytesAfter,
-               uidTxBytesAfter >= uidTxBytesBefore + 1048576);
-        assertTrue("uidrxb: " + uidRxBytesBefore + " -> " + uidRxBytesAfter,
-               uidRxBytesAfter >= uidRxBytesBefore + 1048576);
+        /*
+         * Calculations:
+         *  - bytes
+         *   bytes is approx: packets * data + packets * acks;
+         *   but sometimes there are less acks than packets, so we set a lower
+         *   limit of 1 ack.
+         *  - setup/teardown
+         *   + 7 approx.: syn, syn-ack, ack, fin-ack, ack, fin-ack, ack;
+         *   but sometimes the last find-acks just vanish, so we set a lower limit of +5.
+         */
+        assertTrue("uidtxp: " + uidTxPacketsBefore + " -> " + uidTxPacketsAfter + " delta=" + uidTxDeltaPackets,
+            uidTxDeltaPackets >= packetCount + 5 &&
+            uidTxDeltaPackets <= packetCount + packetCount + 7);
+        assertTrue("uidrxp: " + uidRxPacketsBefore + " -> " + uidRxPacketsAfter + " delta=" + uidRxDeltaPackets,
+            uidRxDeltaPackets >= packetCount + 5 &&
+            uidRxDeltaPackets <= packetCount + packetCount + 7);
+        assertTrue("uidtxb: " + uidTxBytesBefore + " -> " + uidTxBytesAfter + " delta=" + uidTxDeltaBytes,
+            uidTxDeltaBytes >= tcpPacketToIpBytes(packetCount, byteCount) + tcpPacketToIpBytes(5, 0) &&
+            uidTxDeltaBytes <= tcpPacketToIpBytes(packetCount, byteCount) + tcpPacketToIpBytes(packetCount + 7, 0));
+        assertTrue("uidrxb: " + uidRxBytesBefore + " -> " + uidRxBytesAfter + " delta=" + uidRxDeltaBytes,
+            uidRxDeltaBytes >= tcpPacketToIpBytes(packetCount, byteCount) + tcpPacketToIpBytes(5, 0) &&
+            uidRxDeltaBytes <= tcpPacketToIpBytes(packetCount, byteCount) + tcpPacketToIpBytes(packetCount + 7, 0));
+
+        // Localhost traffic *does* count against total stats.
+        // Fudge by 132 packets of 1500 bytes not related to the test.
+        assertTrue("ttxp: " + totalTxPacketsBefore + " -> " + totalTxPacketsAfter,
+            totalTxPacketsAfter >= totalTxPacketsBefore + uidTxDeltaPackets &&
+            totalTxPacketsAfter <= totalTxPacketsBefore + uidTxDeltaPackets + 132);
+        assertTrue("trxp: " + totalRxPacketsBefore + " -> " + totalRxPacketsAfter,
+            totalRxPacketsAfter >= totalRxPacketsBefore + uidRxDeltaPackets &&
+            totalRxPacketsAfter <= totalRxPacketsBefore + uidRxDeltaPackets + 132);
+        assertTrue("ttxb: " + totalTxBytesBefore + " -> " + totalTxBytesAfter,
+            totalTxBytesAfter >= totalTxBytesBefore + uidTxDeltaBytes &&
+            totalTxBytesAfter <= totalTxBytesBefore + uidTxDeltaBytes + 132 * 1500);
+        assertTrue("trxb: " + totalRxBytesBefore + " -> " + totalRxBytesAfter,
+            totalRxBytesAfter >= totalRxBytesBefore + uidRxDeltaBytes &&
+            totalRxBytesAfter <= totalRxBytesBefore + uidRxDeltaBytes + 132 * 1500);
+
+        // Localhost traffic should *not* count against mobile stats,
+        // There might be some other traffic, but nowhere near 1MB.
+        assertTrue("mtxp: " + mobileTxPacketsBefore + " -> " + mobileTxPacketsAfter,
+            mobileTxPacketsAfter >= mobileTxPacketsBefore &&
+            mobileTxPacketsAfter <= mobileTxPacketsBefore + 500);
+        assertTrue("mrxp: " + mobileRxPacketsBefore + " -> " + mobileRxPacketsAfter,
+            mobileRxPacketsAfter >= mobileRxPacketsBefore &&
+            mobileRxPacketsAfter <= mobileRxPacketsBefore + 500);
+        assertTrue("mtxb: " + mobileTxBytesBefore + " -> " + mobileTxBytesAfter,
+            mobileTxBytesAfter >= mobileTxBytesBefore &&
+            mobileTxBytesAfter <= mobileTxBytesBefore + 200000);
+        assertTrue("mrxb: " + mobileRxBytesBefore + " -> " + mobileRxBytesAfter,
+            mobileRxBytesAfter >= mobileRxBytesBefore &&
+            mobileRxBytesAfter <= mobileRxBytesBefore + 200000);
+
     }
 }
diff --git a/tests/tests/openglperf/Android.mk b/tests/tests/openglperf/Android.mk
index 2d1767e..e10d1c7 100644
--- a/tests/tests/openglperf/Android.mk
+++ b/tests/tests/openglperf/Android.mk
@@ -23,7 +23,9 @@
 
 LOCAL_JAVA_LIBRARIES := android.test.runner
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner ctsutil
+
+LOCAL_JNI_SHARED_LIBRARIES := libctsopenglperf_jni
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
@@ -35,4 +37,4 @@
 
 include $(BUILD_CTS_PACKAGE)
 
-#include $(call all-makefiles-under,$(LOCAL_PATH))
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/openglperf/AndroidManifest.xml b/tests/tests/openglperf/AndroidManifest.xml
index 540f190..84bfea4 100644
--- a/tests/tests/openglperf/AndroidManifest.xml
+++ b/tests/tests/openglperf/AndroidManifest.xml
@@ -32,12 +32,9 @@
         android:targetPackage="com.android.cts.openglperf"
         android:name="android.test.InstrumentationCtsTestRunner" />
 
-    <application
-        android:label="@string/app_name" >
+    <application>
         <uses-library android:name="android.test.runner" />
-        <activity
-            android:name="android.openglperf.cts.GlPlanetsActivity"
-            android:label="@string/app_name" >
+        <activity android:name="android.openglperf.cts.GlPlanetsActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
diff --git a/suite/pts/hostTests/ptshostutil/Android.mk b/tests/tests/openglperf/jni/Android.mk
similarity index 67%
copy from suite/pts/hostTests/ptshostutil/Android.mk
copy to tests/tests/openglperf/jni/Android.mk
index 14e786a..77db1df 100644
--- a/suite/pts/hostTests/ptshostutil/Android.mk
+++ b/tests/tests/openglperf/jni/Android.mk
@@ -11,19 +11,21 @@
 # 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.
-
+#
 LOCAL_PATH := $(call my-dir)
 
 include $(CLEAR_VARS)
 
-LOCAL_SRC_FILES := \
-    $(call all-java-files-under, src)
-
-LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed-prebuilt ddmlib-prebuilt junit
+LOCAL_MODULE    := libctsopenglperf_jni
 
 LOCAL_MODULE_TAGS := optional
 
-LOCAL_MODULE := ptshostutil
+LOCAL_SRC_FILES := OpenGlPerfNativeJni.cpp
 
-include $(BUILD_HOST_JAVA_LIBRARY)
+LOCAL_C_INCLUDES := $(JNI_H_INCLUDE) $(call include-path-for, system-core) frameworks/native/opengl/include
 
+LOCAL_SHARED_LIBRARIES := libnativehelper libcutils libGLES_trace libEGL
+
+LOCAL_SDK_VERSION := 14
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/tests/tests/openglperf/jni/OpenGlPerfNativeJni.cpp b/tests/tests/openglperf/jni/OpenGlPerfNativeJni.cpp
new file mode 100644
index 0000000..63311eb
--- /dev/null
+++ b/tests/tests/openglperf/jni/OpenGlPerfNativeJni.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#include <EGL/egl.h>
+#define EGL_EGLEXT_PROTOTYPES // for egl*Sync*
+#include <EGL/eglext.h>
+#include <cutils/log.h>
+#include <jni.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+
+typedef EGLSyncKHR EGLAPIENTRY (*TypeEglCreateSyncKHR)(EGLDisplay dpy, \
+    EGLenum type, const EGLint *attrib_list);
+typedef EGLBoolean EGLAPIENTRY (*TypeEglDestroySyncKHR)(EGLDisplay dpy, \
+    EGLSyncKHR sync);
+typedef EGLint EGLAPIENTRY (*TypeEglClientWaitSyncKHR)(EGLDisplay dpy, \
+    EGLSyncKHR sync, EGLint flags, EGLTimeKHR timeout);
+
+static TypeEglCreateSyncKHR mEglCreateSyncKHR = NULL;
+static TypeEglClientWaitSyncKHR mEglClientWaitSyncKHR = NULL;
+static TypeEglDestroySyncKHR mEglDestroySyncKHR = NULL;
+static bool mInitialized = false;
+static bool mEglKhrFenceSyncSupported = false;
+
+bool IsEglKHRFenceSyncSupported(EGLDisplay& display)
+{
+    if (!mInitialized) {
+        const char* eglExtensions = eglQueryString(display, EGL_EXTENSIONS);
+        if (eglExtensions && strstr(eglExtensions, "EGL_KHR_fence_sync")) {
+            mEglCreateSyncKHR = (TypeEglCreateSyncKHR) eglGetProcAddress("eglCreateSyncKHR");
+            mEglClientWaitSyncKHR =
+                    (TypeEglClientWaitSyncKHR) eglGetProcAddress("eglClientWaitSyncKHR");
+            mEglDestroySyncKHR = (TypeEglDestroySyncKHR) eglGetProcAddress("eglDestroySyncKHR");
+            if (mEglCreateSyncKHR != NULL && mEglClientWaitSyncKHR != NULL
+                    && mEglDestroySyncKHR != NULL) {
+                mEglKhrFenceSyncSupported = true;
+            }
+        }
+        mInitialized = true;
+    }
+    return mEglKhrFenceSyncSupported;
+}
+
+extern "C" JNIEXPORT \
+jboolean JNICALL Java_android_openglperf_cts_OpenGlPerfNative_waitForEglCompletion(JNIEnv* env,
+        jclass clazz, jlong waitTimeInNs)
+{
+    EGLDisplay dpy = eglGetCurrentDisplay();
+    if (!IsEglKHRFenceSyncSupported(dpy)) {
+        return JNI_FALSE;
+    }
+    EGLSyncKHR sync = mEglCreateSyncKHR(dpy, EGL_SYNC_FENCE_KHR, NULL);
+    if (sync == EGL_NO_SYNC_KHR) {
+        return JNI_FALSE;
+    }
+    jboolean res = JNI_TRUE;
+    EGLint result = mEglClientWaitSyncKHR(dpy, sync, 0, waitTimeInNs);
+    if (result == EGL_FALSE) {
+        ALOGE("FrameCompletion: error waiting for fence: %#x", eglGetError());
+        res = JNI_FALSE;
+    } else if (result == EGL_TIMEOUT_EXPIRED_KHR) {
+        ALOGE("FrameCompletion: timeout waiting for fence");
+        res = JNI_FALSE;
+    }
+    mEglDestroySyncKHR(dpy, sync);
+    return res;
+}
diff --git a/tests/tests/openglperf/src/android/openglperf/cts/GLSurfaceViewCustom.java b/tests/tests/openglperf/src/android/openglperf/cts/GLSurfaceViewCustom.java
new file mode 100644
index 0000000..e1a2292
--- /dev/null
+++ b/tests/tests/openglperf/src/android/openglperf/cts/GLSurfaceViewCustom.java
@@ -0,0 +1,1943 @@
+/*
+ * Copyright (C) 2008 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.openglperf.cts;
+
+// This is a copy of f/b/opengl/java/android/opengl/GlSurfaceView.
+// Most of the code is used as it is, but there was a need to add a hook
+// after eglSwapBuffers is called. Also classes not available outside framework is commented out
+// or replaced.
+
+
+import java.io.Writer;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGL11;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLContext;
+import javax.microedition.khronos.egl.EGLDisplay;
+import javax.microedition.khronos.egl.EGLSurface;
+import javax.microedition.khronos.opengles.GL;
+import javax.microedition.khronos.opengles.GL10;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+
+/**
+ * An implementation of SurfaceView that uses the dedicated surface for
+ * displaying OpenGL rendering.
+ * <p>
+ * A GLSurfaceView provides the following features:
+ * <p>
+ * <ul>
+ * <li>Manages a surface, which is a special piece of memory that can be
+ * composited into the Android view system.
+ * <li>Manages an EGL display, which enables OpenGL to render into a surface.
+ * <li>Accepts a user-provided Renderer object that does the actual rendering.
+ * <li>Renders on a dedicated thread to decouple rendering performance from the
+ * UI thread.
+ * <li>Supports both on-demand and continuous rendering.
+ * <li>Optionally wraps, traces, and/or error-checks the renderer's OpenGL calls.
+ * </ul>
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about how to use OpenGL, read the
+ * <a href="{@docRoot}guide/topics/graphics/opengl.html">OpenGL</a> developer guide.</p>
+ * </div>
+ *
+ * <h3>Using GLSurfaceView</h3>
+ * <p>
+ * Typically you use GLSurfaceView by subclassing it and overriding one or more of the
+ * View system input event methods. If your application does not need to override event
+ * methods then GLSurfaceView can be used as-is. For the most part
+ * GLSurfaceView behavior is customized by calling "set" methods rather than by subclassing.
+ * For example, unlike a regular View, drawing is delegated to a separate Renderer object which
+ * is registered with the GLSurfaceView
+ * using the {@link #setRenderer(Renderer)} call.
+ * <p>
+ * <h3>Initializing GLSurfaceView</h3>
+ * All you have to do to initialize a GLSurfaceView is call {@link #setRenderer(Renderer)}.
+ * However, if desired, you can modify the default behavior of GLSurfaceView by calling one or
+ * more of these methods before calling setRenderer:
+ * <ul>
+ * <li>{@link #setDebugFlags(int)}
+ * <li>{@link #setEGLConfigChooser(boolean)}
+ * <li>{@link #setEGLConfigChooser(EGLConfigChooser)}
+ * <li>{@link #setEGLConfigChooser(int, int, int, int, int, int)}
+ * <li>{@link #setGLWrapper(GLWrapper)}
+ * </ul>
+ * <p>
+ * <h4>Specifying the android.view.Surface</h4>
+ * By default GLSurfaceView will create a PixelFormat.RGB_888 format surface. If a translucent
+ * surface is required, call getHolder().setFormat(PixelFormat.TRANSLUCENT).
+ * The exact format of a TRANSLUCENT surface is device dependent, but it will be
+ * a 32-bit-per-pixel surface with 8 bits per component.
+ * <p>
+ * <h4>Choosing an EGL Configuration</h4>
+ * A given Android device may support multiple EGLConfig rendering configurations.
+ * The available configurations may differ in how may channels of data are present, as
+ * well as how many bits are allocated to each channel. Therefore, the first thing
+ * GLSurfaceView has to do when starting to render is choose what EGLConfig to use.
+ * <p>
+ * By default GLSurfaceView chooses a EGLConfig that has an RGB_888 pixel format,
+ * with at least a 16-bit depth buffer and no stencil.
+ * <p>
+ * If you would prefer a different EGLConfig
+ * you can override the default behavior by calling one of the
+ * setEGLConfigChooser methods.
+ * <p>
+ * <h4>Debug Behavior</h4>
+ * You can optionally modify the behavior of GLSurfaceView by calling
+ * one or more of the debugging methods {@link #setDebugFlags(int)},
+ * and {@link #setGLWrapper}. These methods may be called before and/or after setRenderer, but
+ * typically they are called before setRenderer so that they take effect immediately.
+ * <p>
+ * <h4>Setting a Renderer</h4>
+ * Finally, you must call {@link #setRenderer} to register a {@link Renderer}.
+ * The renderer is
+ * responsible for doing the actual OpenGL rendering.
+ * <p>
+ * <h3>Rendering Mode</h3>
+ * Once the renderer is set, you can control whether the renderer draws
+ * continuously or on-demand by calling
+ * {@link #setRenderMode}. The default is continuous rendering.
+ * <p>
+ * <h3>Activity Life-cycle</h3>
+ * A GLSurfaceView must be notified when the activity is paused and resumed. GLSurfaceView clients
+ * are required to call {@link #onPause()} when the activity pauses and
+ * {@link #onResume()} when the activity resumes. These calls allow GLSurfaceView to
+ * pause and resume the rendering thread, and also allow GLSurfaceView to release and recreate
+ * the OpenGL display.
+ * <p>
+ * <h3>Handling events</h3>
+ * <p>
+ * To handle an event you will typically subclass GLSurfaceView and override the
+ * appropriate method, just as you would with any other View. However, when handling
+ * the event, you may need to communicate with the Renderer object
+ * that's running in the rendering thread. You can do this using any
+ * standard Java cross-thread communication mechanism. In addition,
+ * one relatively easy way to communicate with your renderer is
+ * to call
+ * {@link #queueEvent(Runnable)}. For example:
+ * <pre class="prettyprint">
+ * class MyGLSurfaceView extends GLSurfaceView {
+ *
+ *     private MyRenderer mMyRenderer;
+ *
+ *     public void start() {
+ *         mMyRenderer = ...;
+ *         setRenderer(mMyRenderer);
+ *     }
+ *
+ *     public boolean onKeyDown(int keyCode, KeyEvent event) {
+ *         if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
+ *             queueEvent(new Runnable() {
+ *                 // This method will be called on the rendering
+ *                 // thread:
+ *                 public void run() {
+ *                     mMyRenderer.handleDpadCenter();
+ *                 }});
+ *             return true;
+ *         }
+ *         return super.onKeyDown(keyCode, event);
+ *     }
+ * }
+ * </pre>
+ *
+ */
+public class GLSurfaceViewCustom extends SurfaceView implements SurfaceHolder.Callback {
+    private final static String TAG = "GLSurfaceView";
+    private final static boolean LOG_ATTACH_DETACH = false;
+    private final static boolean LOG_THREADS = false;
+    private final static boolean LOG_PAUSE_RESUME = false;
+    private final static boolean LOG_SURFACE = false;
+    private final static boolean LOG_RENDERER = false;
+    private final static boolean LOG_RENDERER_DRAW_FRAME = false;
+    private final static boolean LOG_EGL = false;
+    /**
+     * The renderer only renders
+     * when the surface is created, or when {@link #requestRender} is called.
+     *
+     * @see #getRenderMode()
+     * @see #setRenderMode(int)
+     * @see #requestRender()
+     */
+    public final static int RENDERMODE_WHEN_DIRTY = 0;
+    /**
+     * The renderer is called
+     * continuously to re-render the scene.
+     *
+     * @see #getRenderMode()
+     * @see #setRenderMode(int)
+     */
+    public final static int RENDERMODE_CONTINUOUSLY = 1;
+
+    /**
+     * Check glError() after every GL call and throw an exception if glError indicates
+     * that an error has occurred. This can be used to help track down which OpenGL ES call
+     * is causing an error.
+     *
+     * @see #getDebugFlags
+     * @see #setDebugFlags
+     */
+    public final static int DEBUG_CHECK_GL_ERROR = 1;
+
+    /**
+     * Log GL calls to the system log at "verbose" level with tag "GLSurfaceView".
+     *
+     * @see #getDebugFlags
+     * @see #setDebugFlags
+     */
+    public final static int DEBUG_LOG_GL_CALLS = 2;
+
+    /**
+     * Standard View constructor. In order to render something, you
+     * must call {@link #setRenderer} to register a renderer.
+     */
+    public GLSurfaceViewCustom(Context context) {
+        super(context);
+        init();
+    }
+
+    /**
+     * Standard View constructor. In order to render something, you
+     * must call {@link #setRenderer} to register a renderer.
+     */
+    public GLSurfaceViewCustom(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            if (mGLThread != null) {
+                // GLThread may still be running if this view was never
+                // attached to a window.
+                mGLThread.requestExitAndWait();
+            }
+        } finally {
+            super.finalize();
+        }
+    }
+
+    private void init() {
+        // Install a SurfaceHolder.Callback so we get notified when the
+        // underlying surface is created and destroyed
+        SurfaceHolder holder = getHolder();
+        holder.addCallback(this);
+        // setFormat is done by SurfaceView in SDK 2.3 and newer. Uncomment
+        // this statement if back-porting to 2.2 or older:
+        // holder.setFormat(PixelFormat.RGB_565);
+        //
+        // setType is not needed for SDK 2.0 or newer. Uncomment this
+        // statement if back-porting this code to older SDKs.
+        // holder.setType(SurfaceHolder.SURFACE_TYPE_GPU);
+    }
+
+    /**
+     * Set the glWrapper. If the glWrapper is not null, its
+     * {@link GLWrapper#wrap(GL)} method is called
+     * whenever a surface is created. A GLWrapper can be used to wrap
+     * the GL object that's passed to the renderer. Wrapping a GL
+     * object enables examining and modifying the behavior of the
+     * GL calls made by the renderer.
+     * <p>
+     * Wrapping is typically used for debugging purposes.
+     * <p>
+     * The default value is null.
+     * @param glWrapper the new GLWrapper
+     */
+    public void setGLWrapper(GLWrapper glWrapper) {
+        mGLWrapper = glWrapper;
+    }
+
+    /**
+     * Set the debug flags to a new value. The value is
+     * constructed by OR-together zero or more
+     * of the DEBUG_CHECK_* constants. The debug flags take effect
+     * whenever a surface is created. The default value is zero.
+     * @param debugFlags the new debug flags
+     * @see #DEBUG_CHECK_GL_ERROR
+     * @see #DEBUG_LOG_GL_CALLS
+     */
+    public void setDebugFlags(int debugFlags) {
+        mDebugFlags = debugFlags;
+    }
+
+    /**
+     * Get the current value of the debug flags.
+     * @return the current value of the debug flags.
+     */
+    public int getDebugFlags() {
+        return mDebugFlags;
+    }
+
+    /**
+     * Control whether the EGL context is preserved when the GLSurfaceView is paused and
+     * resumed.
+     * <p>
+     * If set to true, then the EGL context may be preserved when the GLSurfaceView is paused.
+     * Whether the EGL context is actually preserved or not depends upon whether the
+     * Android device that the program is running on can support an arbitrary number of EGL
+     * contexts or not. Devices that can only support a limited number of EGL contexts must
+     * release the  EGL context in order to allow multiple applications to share the GPU.
+     * <p>
+     * If set to false, the EGL context will be released when the GLSurfaceView is paused,
+     * and recreated when the GLSurfaceView is resumed.
+     * <p>
+     *
+     * The default is false.
+     *
+     * @param preserveOnPause preserve the EGL context when paused
+     */
+    public void setPreserveEGLContextOnPause(boolean preserveOnPause) {
+        mPreserveEGLContextOnPause = preserveOnPause;
+    }
+
+    /**
+     * @return true if the EGL context will be preserved when paused
+     */
+    public boolean getPreserveEGLContextOnPause() {
+        return mPreserveEGLContextOnPause;
+    }
+
+    /**
+     * Set the renderer associated with this view. Also starts the thread that
+     * will call the renderer, which in turn causes the rendering to start.
+     * <p>This method should be called once and only once in the life-cycle of
+     * a GLSurfaceView.
+     * <p>The following GLSurfaceView methods can only be called <em>before</em>
+     * setRenderer is called:
+     * <ul>
+     * <li>{@link #setEGLConfigChooser(boolean)}
+     * <li>{@link #setEGLConfigChooser(EGLConfigChooser)}
+     * <li>{@link #setEGLConfigChooser(int, int, int, int, int, int)}
+     * </ul>
+     * <p>
+     * The following GLSurfaceView methods can only be called <em>after</em>
+     * setRenderer is called:
+     * <ul>
+     * <li>{@link #getRenderMode()}
+     * <li>{@link #onPause()}
+     * <li>{@link #onResume()}
+     * <li>{@link #queueEvent(Runnable)}
+     * <li>{@link #requestRender()}
+     * <li>{@link #setRenderMode(int)}
+     * </ul>
+     *
+     * @param renderer the renderer to use to perform OpenGL drawing.
+     */
+    public void setRenderer(Renderer renderer) {
+        checkRenderThreadState();
+        if (mEGLConfigChooser == null) {
+            mEGLConfigChooser = new SimpleEGLConfigChooser(true);
+        }
+        if (mEGLContextFactory == null) {
+            mEGLContextFactory = new DefaultContextFactory();
+        }
+        if (mEGLWindowSurfaceFactory == null) {
+            mEGLWindowSurfaceFactory = new DefaultWindowSurfaceFactory();
+        }
+        mRenderer = renderer;
+        mGLThread = new GLThread(mThisWeakRef);
+        mGLThread.start();
+    }
+
+    /**
+     * Install a custom EGLContextFactory.
+     * <p>If this method is
+     * called, it must be called before {@link #setRenderer(Renderer)}
+     * is called.
+     * <p>
+     * If this method is not called, then by default
+     * a context will be created with no shared context and
+     * with a null attribute list.
+     */
+    public void setEGLContextFactory(EGLContextFactory factory) {
+        checkRenderThreadState();
+        mEGLContextFactory = factory;
+    }
+
+    /**
+     * Install a custom EGLWindowSurfaceFactory.
+     * <p>If this method is
+     * called, it must be called before {@link #setRenderer(Renderer)}
+     * is called.
+     * <p>
+     * If this method is not called, then by default
+     * a window surface will be created with a null attribute list.
+     */
+    public void setEGLWindowSurfaceFactory(EGLWindowSurfaceFactory factory) {
+        checkRenderThreadState();
+        mEGLWindowSurfaceFactory = factory;
+    }
+
+    /**
+     * Install a custom EGLConfigChooser.
+     * <p>If this method is
+     * called, it must be called before {@link #setRenderer(Renderer)}
+     * is called.
+     * <p>
+     * If no setEGLConfigChooser method is called, then by default the
+     * view will choose an EGLConfig that is compatible with the current
+     * android.view.Surface, with a depth buffer depth of
+     * at least 16 bits.
+     * @param configChooser
+     */
+    public void setEGLConfigChooser(EGLConfigChooser configChooser) {
+        checkRenderThreadState();
+        mEGLConfigChooser = configChooser;
+    }
+
+    /**
+     * Install a config chooser which will choose a config
+     * as close to 16-bit RGB as possible, with or without an optional depth
+     * buffer as close to 16-bits as possible.
+     * <p>If this method is
+     * called, it must be called before {@link #setRenderer(Renderer)}
+     * is called.
+     * <p>
+     * If no setEGLConfigChooser method is called, then by default the
+     * view will choose an RGB_888 surface with a depth buffer depth of
+     * at least 16 bits.
+     *
+     * @param needDepth
+     */
+    public void setEGLConfigChooser(boolean needDepth) {
+        setEGLConfigChooser(new SimpleEGLConfigChooser(needDepth));
+    }
+
+    /**
+     * Install a config chooser which will choose a config
+     * with at least the specified depthSize and stencilSize,
+     * and exactly the specified redSize, greenSize, blueSize and alphaSize.
+     * <p>If this method is
+     * called, it must be called before {@link #setRenderer(Renderer)}
+     * is called.
+     * <p>
+     * If no setEGLConfigChooser method is called, then by default the
+     * view will choose an RGB_888 surface with a depth buffer depth of
+     * at least 16 bits.
+     *
+     */
+    public void setEGLConfigChooser(int redSize, int greenSize, int blueSize,
+            int alphaSize, int depthSize, int stencilSize) {
+        setEGLConfigChooser(new ComponentSizeChooser(redSize, greenSize,
+                blueSize, alphaSize, depthSize, stencilSize));
+    }
+
+    /**
+     * Inform the default EGLContextFactory and default EGLConfigChooser
+     * which EGLContext client version to pick.
+     * <p>Use this method to create an OpenGL ES 2.0-compatible context.
+     * Example:
+     * <pre class="prettyprint">
+     *     public MyView(Context context) {
+     *         super(context);
+     *         setEGLContextClientVersion(2); // Pick an OpenGL ES 2.0 context.
+     *         setRenderer(new MyRenderer());
+     *     }
+     * </pre>
+     * <p>Note: Activities which require OpenGL ES 2.0 should indicate this by
+     * setting @lt;uses-feature android:glEsVersion="0x00020000" /> in the activity's
+     * AndroidManifest.xml file.
+     * <p>If this method is called, it must be called before {@link #setRenderer(Renderer)}
+     * is called.
+     * <p>This method only affects the behavior of the default EGLContexFactory and the
+     * default EGLConfigChooser. If
+     * {@link #setEGLContextFactory(EGLContextFactory)} has been called, then the supplied
+     * EGLContextFactory is responsible for creating an OpenGL ES 2.0-compatible context.
+     * If
+     * {@link #setEGLConfigChooser(EGLConfigChooser)} has been called, then the supplied
+     * EGLConfigChooser is responsible for choosing an OpenGL ES 2.0-compatible config.
+     * @param version The EGLContext client version to choose. Use 2 for OpenGL ES 2.0
+     */
+    public void setEGLContextClientVersion(int version) {
+        checkRenderThreadState();
+        mEGLContextClientVersion = version;
+    }
+
+    /**
+     * Set the rendering mode. When renderMode is
+     * RENDERMODE_CONTINUOUSLY, the renderer is called
+     * repeatedly to re-render the scene. When renderMode
+     * is RENDERMODE_WHEN_DIRTY, the renderer only rendered when the surface
+     * is created, or when {@link #requestRender} is called. Defaults to RENDERMODE_CONTINUOUSLY.
+     * <p>
+     * Using RENDERMODE_WHEN_DIRTY can improve battery life and overall system performance
+     * by allowing the GPU and CPU to idle when the view does not need to be updated.
+     * <p>
+     * This method can only be called after {@link #setRenderer(Renderer)}
+     *
+     * @param renderMode one of the RENDERMODE_X constants
+     * @see #RENDERMODE_CONTINUOUSLY
+     * @see #RENDERMODE_WHEN_DIRTY
+     */
+    public void setRenderMode(int renderMode) {
+        mGLThread.setRenderMode(renderMode);
+    }
+
+    /**
+     * Get the current rendering mode. May be called
+     * from any thread. Must not be called before a renderer has been set.
+     * @return the current rendering mode.
+     * @see #RENDERMODE_CONTINUOUSLY
+     * @see #RENDERMODE_WHEN_DIRTY
+     */
+    public int getRenderMode() {
+        return mGLThread.getRenderMode();
+    }
+
+    /**
+     * Request that the renderer render a frame.
+     * This method is typically used when the render mode has been set to
+     * {@link #RENDERMODE_WHEN_DIRTY}, so that frames are only rendered on demand.
+     * May be called
+     * from any thread. Must not be called before a renderer has been set.
+     */
+    public void requestRender() {
+        mGLThread.requestRender();
+    }
+
+    /**
+     * This method is part of the SurfaceHolder.Callback interface, and is
+     * not normally called or subclassed by clients of GLSurfaceView.
+     */
+    public void surfaceCreated(SurfaceHolder holder) {
+        mGLThread.surfaceCreated();
+    }
+
+    /**
+     * This method is part of the SurfaceHolder.Callback interface, and is
+     * not normally called or subclassed by clients of GLSurfaceView.
+     */
+    public void surfaceDestroyed(SurfaceHolder holder) {
+        // Surface will be destroyed when we return
+        mGLThread.surfaceDestroyed();
+    }
+
+    /**
+     * This method is part of the SurfaceHolder.Callback interface, and is
+     * not normally called or subclassed by clients of GLSurfaceView.
+     */
+    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
+        mGLThread.onWindowResize(w, h);
+    }
+
+    /**
+     * Inform the view that the activity is paused. The owner of this view must
+     * call this method when the activity is paused. Calling this method will
+     * pause the rendering thread.
+     * Must not be called before a renderer has been set.
+     */
+    public void onPause() {
+        mGLThread.onPause();
+    }
+
+    /**
+     * Inform the view that the activity is resumed. The owner of this view must
+     * call this method when the activity is resumed. Calling this method will
+     * recreate the OpenGL display and resume the rendering
+     * thread.
+     * Must not be called before a renderer has been set.
+     */
+    public void onResume() {
+        mGLThread.onResume();
+    }
+
+    /**
+     * Queue a runnable to be run on the GL rendering thread. This can be used
+     * to communicate with the Renderer on the rendering thread.
+     * Must not be called before a renderer has been set.
+     * @param r the runnable to be run on the GL rendering thread.
+     */
+    public void queueEvent(Runnable r) {
+        mGLThread.queueEvent(r);
+    }
+
+    /**
+     * This method is used as part of the View class and is not normally
+     * called or subclassed by clients of GLSurfaceView.
+     */
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        if (LOG_ATTACH_DETACH) {
+            Log.d(TAG, "onAttachedToWindow reattach =" + mDetached);
+        }
+        if (mDetached && (mRenderer != null)) {
+            int renderMode = RENDERMODE_CONTINUOUSLY;
+            if (mGLThread != null) {
+                renderMode = mGLThread.getRenderMode();
+            }
+            mGLThread = new GLThread(mThisWeakRef);
+            if (renderMode != RENDERMODE_CONTINUOUSLY) {
+                mGLThread.setRenderMode(renderMode);
+            }
+            mGLThread.start();
+        }
+        mDetached = false;
+    }
+
+    /**
+     * This method is used as part of the View class and is not normally
+     * called or subclassed by clients of GLSurfaceView.
+     * Must not be called before a renderer has been set.
+     */
+    @Override
+    protected void onDetachedFromWindow() {
+        if (LOG_ATTACH_DETACH) {
+            Log.d(TAG, "onDetachedFromWindow");
+        }
+        if (mGLThread != null) {
+            mGLThread.requestExitAndWait();
+        }
+        mDetached = true;
+        super.onDetachedFromWindow();
+    }
+
+    // ----------------------------------------------------------------------
+
+    /**
+     * An interface used to wrap a GL interface.
+     * <p>Typically
+     * used for implementing debugging and tracing on top of the default
+     * GL interface. You would typically use this by creating your own class
+     * that implemented all the GL methods by delegating to another GL instance.
+     * Then you could add your own behavior before or after calling the
+     * delegate. All the GLWrapper would do was instantiate and return the
+     * wrapper GL instance:
+     * <pre class="prettyprint">
+     * class MyGLWrapper implements GLWrapper {
+     *     GL wrap(GL gl) {
+     *         return new MyGLImplementation(gl);
+     *     }
+     *     static class MyGLImplementation implements GL,GL10,GL11,... {
+     *         ...
+     *     }
+     * }
+     * </pre>
+     * @see #setGLWrapper(GLWrapper)
+     */
+    public interface GLWrapper {
+        /**
+         * Wraps a gl interface in another gl interface.
+         * @param gl a GL interface that is to be wrapped.
+         * @return either the input argument or another GL object that wraps the input argument.
+         */
+        GL wrap(GL gl);
+    }
+
+    /**
+     * A generic renderer interface.
+     * <p>
+     * The renderer is responsible for making OpenGL calls to render a frame.
+     * <p>
+     * GLSurfaceView clients typically create their own classes that implement
+     * this interface, and then call {@link GLSurfaceView#setRenderer} to
+     * register the renderer with the GLSurfaceView.
+     * <p>
+     *
+     * <div class="special reference">
+     * <h3>Developer Guides</h3>
+     * <p>For more information about how to use OpenGL, read the
+     * <a href="{@docRoot}guide/topics/graphics/opengl.html">OpenGL</a> developer guide.</p>
+     * </div>
+     *
+     * <h3>Threading</h3>
+     * The renderer will be called on a separate thread, so that rendering
+     * performance is decoupled from the UI thread. Clients typically need to
+     * communicate with the renderer from the UI thread, because that's where
+     * input events are received. Clients can communicate using any of the
+     * standard Java techniques for cross-thread communication, or they can
+     * use the {@link GLSurfaceView#queueEvent(Runnable)} convenience method.
+     * <p>
+     * <h3>EGL Context Lost</h3>
+     * There are situations where the EGL rendering context will be lost. This
+     * typically happens when device wakes up after going to sleep. When
+     * the EGL context is lost, all OpenGL resources (such as textures) that are
+     * associated with that context will be automatically deleted. In order to
+     * keep rendering correctly, a renderer must recreate any lost resources
+     * that it still needs. The {@link #onSurfaceCreated(GL10, EGLConfig)} method
+     * is a convenient place to do this.
+     *
+     *
+     * @see #setRenderer(Renderer)
+     */
+    public interface Renderer {
+        /**
+         * Called when the surface is created or recreated.
+         * <p>
+         * Called when the rendering thread
+         * starts and whenever the EGL context is lost. The EGL context will typically
+         * be lost when the Android device awakes after going to sleep.
+         * <p>
+         * Since this method is called at the beginning of rendering, as well as
+         * every time the EGL context is lost, this method is a convenient place to put
+         * code to create resources that need to be created when the rendering
+         * starts, and that need to be recreated when the EGL context is lost.
+         * Textures are an example of a resource that you might want to create
+         * here.
+         * <p>
+         * Note that when the EGL context is lost, all OpenGL resources associated
+         * with that context will be automatically deleted. You do not need to call
+         * the corresponding "glDelete" methods such as glDeleteTextures to
+         * manually delete these lost resources.
+         * <p>
+         * @param gl the GL interface. Use <code>instanceof</code> to
+         * test if the interface supports GL11 or higher interfaces.
+         * @param config the EGLConfig of the created surface. Can be used
+         * to create matching pbuffers.
+         */
+        void onSurfaceCreated(GL10 gl, EGLConfig config);
+
+        /**
+         * Called when the surface changed size.
+         * <p>
+         * Called after the surface is created and whenever
+         * the OpenGL ES surface size changes.
+         * <p>
+         * Typically you will set your viewport here. If your camera
+         * is fixed then you could also set your projection matrix here:
+         * <pre class="prettyprint">
+         * void onSurfaceChanged(GL10 gl, int width, int height) {
+         *     gl.glViewport(0, 0, width, height);
+         *     // for a fixed camera, set the projection too
+         *     float ratio = (float) width / height;
+         *     gl.glMatrixMode(GL10.GL_PROJECTION);
+         *     gl.glLoadIdentity();
+         *     gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);
+         * }
+         * </pre>
+         * @param gl the GL interface. Use <code>instanceof</code> to
+         * test if the interface supports GL11 or higher interfaces.
+         * @param width
+         * @param height
+         */
+        void onSurfaceChanged(GL10 gl, int width, int height);
+
+        /**
+         * Called to draw the current frame.
+         * <p>
+         * This method is responsible for drawing the current frame.
+         * <p>
+         * The implementation of this method typically looks like this:
+         * <pre class="prettyprint">
+         * void onDrawFrame(GL10 gl) {
+         *     gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
+         *     //... other gl calls to render the scene ...
+         * }
+         * </pre>
+         * @param gl the GL interface. Use <code>instanceof</code> to
+         * test if the interface supports GL11 or higher interfaces.
+         */
+        void onDrawFrame(GL10 gl);
+
+        /**
+         * called after eglSwapBuffers() is called.
+         * can be used for custom synchronization / measurement.
+         */
+        void onEglSwapBuffers();
+    }
+
+    /**
+     * An interface for customizing the eglCreateContext and eglDestroyContext calls.
+     * <p>
+     * This interface must be implemented by clients wishing to call
+     * {@link GLSurfaceView#setEGLContextFactory(EGLContextFactory)}
+     */
+    public interface EGLContextFactory {
+        EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig);
+        void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context);
+    }
+
+    private class DefaultContextFactory implements EGLContextFactory {
+        private int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
+
+        public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig config) {
+            int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, mEGLContextClientVersion,
+                    EGL10.EGL_NONE };
+
+            return egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT,
+                    mEGLContextClientVersion != 0 ? attrib_list : null);
+        }
+
+        public void destroyContext(EGL10 egl, EGLDisplay display,
+                EGLContext context) {
+            if (!egl.eglDestroyContext(display, context)) {
+                Log.e("DefaultContextFactory", "display:" + display + " context: " + context);
+                if (LOG_THREADS) {
+                    Log.i("DefaultContextFactory", "tid=" + Thread.currentThread().getId());
+                }
+                EglHelper.throwEglException("eglDestroyContex", egl.eglGetError());
+            }
+        }
+    }
+
+    /**
+     * An interface for customizing the eglCreateWindowSurface and eglDestroySurface calls.
+     * <p>
+     * This interface must be implemented by clients wishing to call
+     * {@link GLSurfaceView#setEGLWindowSurfaceFactory(EGLWindowSurfaceFactory)}
+     */
+    public interface EGLWindowSurfaceFactory {
+        /**
+         *  @return null if the surface cannot be constructed.
+         */
+        EGLSurface createWindowSurface(EGL10 egl, EGLDisplay display, EGLConfig config,
+                Object nativeWindow);
+        void destroySurface(EGL10 egl, EGLDisplay display, EGLSurface surface);
+    }
+
+    private static class DefaultWindowSurfaceFactory implements EGLWindowSurfaceFactory {
+
+        public EGLSurface createWindowSurface(EGL10 egl, EGLDisplay display,
+                EGLConfig config, Object nativeWindow) {
+            EGLSurface result = null;
+            try {
+                result = egl.eglCreateWindowSurface(display, config, nativeWindow, null);
+            } catch (IllegalArgumentException e) {
+                // This exception indicates that the surface flinger surface
+                // is not valid. This can happen if the surface flinger surface has
+                // been torn down, but the application has not yet been
+                // notified via SurfaceHolder.Callback.surfaceDestroyed.
+                // In theory the application should be notified first,
+                // but in practice sometimes it is not. See b/4588890
+                Log.e(TAG, "eglCreateWindowSurface", e);
+            }
+            return result;
+        }
+
+        public void destroySurface(EGL10 egl, EGLDisplay display,
+                EGLSurface surface) {
+            egl.eglDestroySurface(display, surface);
+        }
+    }
+
+    /**
+     * An interface for choosing an EGLConfig configuration from a list of
+     * potential configurations.
+     * <p>
+     * This interface must be implemented by clients wishing to call
+     * {@link GLSurfaceView#setEGLConfigChooser(EGLConfigChooser)}
+     */
+    public interface EGLConfigChooser {
+        /**
+         * Choose a configuration from the list. Implementors typically
+         * implement this method by calling
+         * {@link EGL10#eglChooseConfig} and iterating through the results. Please consult the
+         * EGL specification available from The Khronos Group to learn how to call eglChooseConfig.
+         * @param egl the EGL10 for the current display.
+         * @param display the current display.
+         * @return the chosen configuration.
+         */
+        EGLConfig chooseConfig(EGL10 egl, EGLDisplay display);
+    }
+
+    private abstract class BaseConfigChooser
+            implements EGLConfigChooser {
+        public BaseConfigChooser(int[] configSpec) {
+            mConfigSpec = filterConfigSpec(configSpec);
+        }
+
+        public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
+            int[] num_config = new int[1];
+            if (!egl.eglChooseConfig(display, mConfigSpec, null, 0,
+                    num_config)) {
+                throw new IllegalArgumentException("eglChooseConfig failed");
+            }
+
+            int numConfigs = num_config[0];
+
+            if (numConfigs <= 0) {
+                throw new IllegalArgumentException(
+                        "No configs match configSpec");
+            }
+
+            EGLConfig[] configs = new EGLConfig[numConfigs];
+            if (!egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs,
+                    num_config)) {
+                throw new IllegalArgumentException("eglChooseConfig#2 failed");
+            }
+            EGLConfig config = chooseConfig(egl, display, configs);
+            if (config == null) {
+                throw new IllegalArgumentException("No config chosen");
+            }
+            return config;
+        }
+
+        abstract EGLConfig chooseConfig(EGL10 egl, EGLDisplay display,
+                EGLConfig[] configs);
+
+        protected int[] mConfigSpec;
+
+        private int[] filterConfigSpec(int[] configSpec) {
+            if (mEGLContextClientVersion != 2) {
+                return configSpec;
+            }
+            /* We know none of the subclasses define EGL_RENDERABLE_TYPE.
+             * And we know the configSpec is well formed.
+             */
+            int len = configSpec.length;
+            int[] newConfigSpec = new int[len + 2];
+            System.arraycopy(configSpec, 0, newConfigSpec, 0, len-1);
+            newConfigSpec[len-1] = EGL10.EGL_RENDERABLE_TYPE;
+            newConfigSpec[len] = 4; /* EGL_OPENGL_ES2_BIT */
+            newConfigSpec[len+1] = EGL10.EGL_NONE;
+            return newConfigSpec;
+        }
+    }
+
+    /**
+     * Choose a configuration with exactly the specified r,g,b,a sizes,
+     * and at least the specified depth and stencil sizes.
+     */
+    private class ComponentSizeChooser extends BaseConfigChooser {
+        public ComponentSizeChooser(int redSize, int greenSize, int blueSize,
+                int alphaSize, int depthSize, int stencilSize) {
+            super(new int[] {
+                    EGL10.EGL_RED_SIZE, redSize,
+                    EGL10.EGL_GREEN_SIZE, greenSize,
+                    EGL10.EGL_BLUE_SIZE, blueSize,
+                    EGL10.EGL_ALPHA_SIZE, alphaSize,
+                    EGL10.EGL_DEPTH_SIZE, depthSize,
+                    EGL10.EGL_STENCIL_SIZE, stencilSize,
+                    EGL10.EGL_NONE});
+            mValue = new int[1];
+            mRedSize = redSize;
+            mGreenSize = greenSize;
+            mBlueSize = blueSize;
+            mAlphaSize = alphaSize;
+            mDepthSize = depthSize;
+            mStencilSize = stencilSize;
+       }
+
+        @Override
+        public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display,
+                EGLConfig[] configs) {
+            for (EGLConfig config : configs) {
+                int d = findConfigAttrib(egl, display, config,
+                        EGL10.EGL_DEPTH_SIZE, 0);
+                int s = findConfigAttrib(egl, display, config,
+                        EGL10.EGL_STENCIL_SIZE, 0);
+                if ((d >= mDepthSize) && (s >= mStencilSize)) {
+                    int r = findConfigAttrib(egl, display, config,
+                            EGL10.EGL_RED_SIZE, 0);
+                    int g = findConfigAttrib(egl, display, config,
+                             EGL10.EGL_GREEN_SIZE, 0);
+                    int b = findConfigAttrib(egl, display, config,
+                              EGL10.EGL_BLUE_SIZE, 0);
+                    int a = findConfigAttrib(egl, display, config,
+                            EGL10.EGL_ALPHA_SIZE, 0);
+                    if ((r == mRedSize) && (g == mGreenSize)
+                            && (b == mBlueSize) && (a == mAlphaSize)) {
+                        return config;
+                    }
+                }
+            }
+            return null;
+        }
+
+        private int findConfigAttrib(EGL10 egl, EGLDisplay display,
+                EGLConfig config, int attribute, int defaultValue) {
+
+            if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) {
+                return mValue[0];
+            }
+            return defaultValue;
+        }
+
+        private int[] mValue;
+        // Subclasses can adjust these values:
+        protected int mRedSize;
+        protected int mGreenSize;
+        protected int mBlueSize;
+        protected int mAlphaSize;
+        protected int mDepthSize;
+        protected int mStencilSize;
+        }
+
+    /**
+     * This class will choose a RGB_888 surface with
+     * or without a depth buffer.
+     *
+     */
+    private class SimpleEGLConfigChooser extends ComponentSizeChooser {
+        public SimpleEGLConfigChooser(boolean withDepthBuffer) {
+            super(8, 8, 8, 0, withDepthBuffer ? 16 : 0, 0);
+        }
+    }
+
+    /**
+     * An EGL helper class.
+     */
+
+    private static class EglHelper {
+        public EglHelper(WeakReference<GLSurfaceViewCustom> glSurfaceViewWeakRef) {
+            mGLSurfaceViewWeakRef = glSurfaceViewWeakRef;
+        }
+
+        /**
+         * Initialize EGL for a given configuration spec.
+         * @param configSpec
+         */
+        public void start() {
+            if (LOG_EGL) {
+                Log.w("EglHelper", "start() tid=" + Thread.currentThread().getId());
+            }
+            /*
+             * Get an EGL instance
+             */
+            mEgl = (EGL10) EGLContext.getEGL();
+
+            /*
+             * Get to the default display.
+             */
+            mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
+
+            if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
+                throw new RuntimeException("eglGetDisplay failed");
+            }
+
+            /*
+             * We can now initialize EGL for that display
+             */
+            int[] version = new int[2];
+            if(!mEgl.eglInitialize(mEglDisplay, version)) {
+                throw new RuntimeException("eglInitialize failed");
+            }
+            GLSurfaceViewCustom view = mGLSurfaceViewWeakRef.get();
+            if (view == null) {
+                mEglConfig = null;
+                mEglContext = null;
+            } else {
+                mEglConfig = view.mEGLConfigChooser.chooseConfig(mEgl, mEglDisplay);
+
+                /*
+                * Create an EGL context. We want to do this as rarely as we can, because an
+                * EGL context is a somewhat heavy object.
+                */
+                mEglContext = view.mEGLContextFactory.createContext(mEgl, mEglDisplay, mEglConfig);
+            }
+            if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) {
+                mEglContext = null;
+                throwEglException("createContext");
+            }
+            if (LOG_EGL) {
+                Log.w("EglHelper", "createContext " + mEglContext + " tid=" + Thread.currentThread().getId());
+            }
+
+            mEglSurface = null;
+        }
+
+        /**
+         * Create an egl surface for the current SurfaceHolder surface. If a surface
+         * already exists, destroy it before creating the new surface.
+         *
+         * @return true if the surface was created successfully.
+         */
+        public boolean createSurface() {
+            if (LOG_EGL) {
+                Log.w("EglHelper", "createSurface()  tid=" + Thread.currentThread().getId());
+            }
+            /*
+             * Check preconditions.
+             */
+            if (mEgl == null) {
+                throw new RuntimeException("egl not initialized");
+            }
+            if (mEglDisplay == null) {
+                throw new RuntimeException("eglDisplay not initialized");
+            }
+            if (mEglConfig == null) {
+                throw new RuntimeException("mEglConfig not initialized");
+            }
+
+            /*
+             *  The window size has changed, so we need to create a new
+             *  surface.
+             */
+            destroySurfaceImp();
+
+            /*
+             * Create an EGL surface we can render into.
+             */
+            GLSurfaceViewCustom view = mGLSurfaceViewWeakRef.get();
+            if (view != null) {
+                mEglSurface = view.mEGLWindowSurfaceFactory.createWindowSurface(mEgl,
+                        mEglDisplay, mEglConfig, view.getHolder());
+            } else {
+                mEglSurface = null;
+            }
+
+            if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
+                int error = mEgl.eglGetError();
+                if (error == EGL10.EGL_BAD_NATIVE_WINDOW) {
+                    Log.e("EglHelper", "createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
+                }
+                return false;
+            }
+
+            /*
+             * Before we can issue GL commands, we need to make sure
+             * the context is current and bound to a surface.
+             */
+            if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
+                /*
+                 * Could not make the context current, probably because the underlying
+                 * SurfaceView surface has been destroyed.
+                 */
+                logEglErrorAsWarning("EGLHelper", "eglMakeCurrent", mEgl.eglGetError());
+                return false;
+            }
+
+            return true;
+        }
+
+        /**
+         * Create a GL object for the current EGL context.
+         * @return
+         */
+        GL createGL() {
+
+            GL gl = mEglContext.getGL();
+            GLSurfaceViewCustom view = mGLSurfaceViewWeakRef.get();
+            if (view != null) {
+                if (view.mGLWrapper != null) {
+                    gl = view.mGLWrapper.wrap(gl);
+                }
+
+/*                if ((view.mDebugFlags & (DEBUG_CHECK_GL_ERROR | DEBUG_LOG_GL_CALLS)) != 0) {
+                    int configFlags = 0;
+                    Writer log = null;
+                    if ((view.mDebugFlags & DEBUG_CHECK_GL_ERROR) != 0) {
+                        configFlags |= GLDebugHelper.CONFIG_CHECK_GL_ERROR;
+                    }
+                    if ((view.mDebugFlags & DEBUG_LOG_GL_CALLS) != 0) {
+                        log = new LogWriter();
+                    }
+                    gl = GLDebugHelper.wrap(gl, configFlags, log);
+                }*/
+            }
+            return gl;
+        }
+
+        /**
+         * Display the current render surface.
+         * @return the EGL error code from eglSwapBuffers.
+         */
+        public int swap() {
+            if (! mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
+                return mEgl.eglGetError();
+            }
+            return EGL10.EGL_SUCCESS;
+        }
+
+        public void destroySurface() {
+            if (LOG_EGL) {
+                Log.w("EglHelper", "destroySurface()  tid=" + Thread.currentThread().getId());
+            }
+            destroySurfaceImp();
+        }
+
+        private void destroySurfaceImp() {
+            if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) {
+                mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE,
+                        EGL10.EGL_NO_SURFACE,
+                        EGL10.EGL_NO_CONTEXT);
+                GLSurfaceViewCustom view = mGLSurfaceViewWeakRef.get();
+                if (view != null) {
+                    view.mEGLWindowSurfaceFactory.destroySurface(mEgl, mEglDisplay, mEglSurface);
+                }
+                mEglSurface = null;
+            }
+        }
+
+        public void finish() {
+            if (LOG_EGL) {
+                Log.w("EglHelper", "finish() tid=" + Thread.currentThread().getId());
+            }
+            if (mEglContext != null) {
+                GLSurfaceViewCustom view = mGLSurfaceViewWeakRef.get();
+                if (view != null) {
+                    view.mEGLContextFactory.destroyContext(mEgl, mEglDisplay, mEglContext);
+                }
+                mEglContext = null;
+            }
+            if (mEglDisplay != null) {
+                mEgl.eglTerminate(mEglDisplay);
+                mEglDisplay = null;
+            }
+        }
+
+        private void throwEglException(String function) {
+            throwEglException(function, mEgl.eglGetError());
+        }
+
+        public static void throwEglException(String function, int error) {
+            String message = formatEglError(function, error);
+            if (LOG_THREADS) {
+                Log.e("EglHelper", "throwEglException tid=" + Thread.currentThread().getId() + " "
+                        + message);
+            }
+            throw new RuntimeException(message);
+        }
+
+        public static void logEglErrorAsWarning(String tag, String function, int error) {
+            Log.w(tag, formatEglError(function, error));
+        }
+
+        public static String formatEglError(String function, int error) {
+            return function + " failed: " + error;
+        }
+
+        private WeakReference<GLSurfaceViewCustom> mGLSurfaceViewWeakRef;
+        EGL10 mEgl;
+        EGLDisplay mEglDisplay;
+        EGLSurface mEglSurface;
+        EGLConfig mEglConfig;
+        EGLContext mEglContext;
+
+    }
+
+    /**
+     * A generic GL Thread. Takes care of initializing EGL and GL. Delegates
+     * to a Renderer instance to do the actual drawing. Can be configured to
+     * render continuously or on request.
+     *
+     * All potentially blocking synchronization is done through the
+     * sGLThreadManager object. This avoids multiple-lock ordering issues.
+     *
+     */
+    static class GLThread extends Thread {
+        GLThread(WeakReference<GLSurfaceViewCustom> glSurfaceViewWeakRef) {
+            super();
+            mWidth = 0;
+            mHeight = 0;
+            mRequestRender = true;
+            mRenderMode = RENDERMODE_CONTINUOUSLY;
+            mGLSurfaceViewWeakRef = glSurfaceViewWeakRef;
+        }
+
+        @Override
+        public void run() {
+            setName("GLThread " + getId());
+            if (LOG_THREADS) {
+                Log.i("GLThread", "starting tid=" + getId());
+            }
+
+            try {
+                guardedRun();
+            } catch (InterruptedException e) {
+                // fall thru and exit normally
+            } finally {
+                sGLThreadManager.threadExiting(this);
+            }
+        }
+
+        /*
+         * This private method should only be called inside a
+         * synchronized(sGLThreadManager) block.
+         */
+        private void stopEglSurfaceLocked() {
+            if (mHaveEglSurface) {
+                mHaveEglSurface = false;
+                mEglHelper.destroySurface();
+            }
+        }
+
+        /*
+         * This private method should only be called inside a
+         * synchronized(sGLThreadManager) block.
+         */
+        private void stopEglContextLocked() {
+            if (mHaveEglContext) {
+                mEglHelper.finish();
+                mHaveEglContext = false;
+                sGLThreadManager.releaseEglContextLocked(this);
+            }
+        }
+        private void guardedRun() throws InterruptedException {
+            mEglHelper = new EglHelper(mGLSurfaceViewWeakRef);
+            mHaveEglContext = false;
+            mHaveEglSurface = false;
+            try {
+                GL10 gl = null;
+                boolean createEglContext = false;
+                boolean createEglSurface = false;
+                boolean createGlInterface = false;
+                boolean lostEglContext = false;
+                boolean sizeChanged = false;
+                boolean wantRenderNotification = false;
+                boolean doRenderNotification = false;
+                boolean askedToReleaseEglContext = false;
+                int w = 0;
+                int h = 0;
+                Runnable event = null;
+
+                while (true) {
+                    synchronized (sGLThreadManager) {
+                        while (true) {
+                            if (mShouldExit) {
+                                return;
+                            }
+
+                            if (! mEventQueue.isEmpty()) {
+                                event = mEventQueue.remove(0);
+                                break;
+                            }
+
+                            // Update the pause state.
+                            boolean pausing = false;
+                            if (mPaused != mRequestPaused) {
+                                pausing = mRequestPaused;
+                                mPaused = mRequestPaused;
+                                sGLThreadManager.notifyAll();
+                                if (LOG_PAUSE_RESUME) {
+                                    Log.i("GLThread", "mPaused is now " + mPaused + " tid=" + getId());
+                                }
+                            }
+
+                            // Do we need to give up the EGL context?
+                            if (mShouldReleaseEglContext) {
+                                if (LOG_SURFACE) {
+                                    Log.i("GLThread", "releasing EGL context because asked to tid=" + getId());
+                                }
+                                stopEglSurfaceLocked();
+                                stopEglContextLocked();
+                                mShouldReleaseEglContext = false;
+                                askedToReleaseEglContext = true;
+                            }
+
+                            // Have we lost the EGL context?
+                            if (lostEglContext) {
+                                stopEglSurfaceLocked();
+                                stopEglContextLocked();
+                                lostEglContext = false;
+                            }
+
+                            // When pausing, release the EGL surface:
+                            if (pausing && mHaveEglSurface) {
+                                if (LOG_SURFACE) {
+                                    Log.i("GLThread", "releasing EGL surface because paused tid=" + getId());
+                                }
+                                stopEglSurfaceLocked();
+                            }
+
+                            // When pausing, optionally release the EGL Context:
+                            if (pausing && mHaveEglContext) {
+                                GLSurfaceViewCustom view = mGLSurfaceViewWeakRef.get();
+                                boolean preserveEglContextOnPause = view == null ?
+                                        false : view.mPreserveEGLContextOnPause;
+                                if (!preserveEglContextOnPause || sGLThreadManager.shouldReleaseEGLContextWhenPausing()) {
+                                    stopEglContextLocked();
+                                    if (LOG_SURFACE) {
+                                        Log.i("GLThread", "releasing EGL context because paused tid=" + getId());
+                                    }
+                                }
+                            }
+
+                            // When pausing, optionally terminate EGL:
+                            if (pausing) {
+                                if (sGLThreadManager.shouldTerminateEGLWhenPausing()) {
+                                    mEglHelper.finish();
+                                    if (LOG_SURFACE) {
+                                        Log.i("GLThread", "terminating EGL because paused tid=" + getId());
+                                    }
+                                }
+                            }
+
+                            // Have we lost the SurfaceView surface?
+                            if ((! mHasSurface) && (! mWaitingForSurface)) {
+                                if (LOG_SURFACE) {
+                                    Log.i("GLThread", "noticed surfaceView surface lost tid=" + getId());
+                                }
+                                if (mHaveEglSurface) {
+                                    stopEglSurfaceLocked();
+                                }
+                                mWaitingForSurface = true;
+                                mSurfaceIsBad = false;
+                                sGLThreadManager.notifyAll();
+                            }
+
+                            // Have we acquired the surface view surface?
+                            if (mHasSurface && mWaitingForSurface) {
+                                if (LOG_SURFACE) {
+                                    Log.i("GLThread", "noticed surfaceView surface acquired tid=" + getId());
+                                }
+                                mWaitingForSurface = false;
+                                sGLThreadManager.notifyAll();
+                            }
+
+                            if (doRenderNotification) {
+                                if (LOG_SURFACE) {
+                                    Log.i("GLThread", "sending render notification tid=" + getId());
+                                }
+                                wantRenderNotification = false;
+                                doRenderNotification = false;
+                                mRenderComplete = true;
+                                sGLThreadManager.notifyAll();
+                            }
+
+                            // Ready to draw?
+                            if (readyToDraw()) {
+
+                                // If we don't have an EGL context, try to acquire one.
+                                if (! mHaveEglContext) {
+                                    if (askedToReleaseEglContext) {
+                                        askedToReleaseEglContext = false;
+                                    } else if (sGLThreadManager.tryAcquireEglContextLocked(this)) {
+                                        try {
+                                            mEglHelper.start();
+                                        } catch (RuntimeException t) {
+                                            sGLThreadManager.releaseEglContextLocked(this);
+                                            throw t;
+                                        }
+                                        mHaveEglContext = true;
+                                        createEglContext = true;
+
+                                        sGLThreadManager.notifyAll();
+                                    }
+                                }
+
+                                if (mHaveEglContext && !mHaveEglSurface) {
+                                    mHaveEglSurface = true;
+                                    createEglSurface = true;
+                                    createGlInterface = true;
+                                    sizeChanged = true;
+                                }
+
+                                if (mHaveEglSurface) {
+                                    if (mSizeChanged) {
+                                        sizeChanged = true;
+                                        w = mWidth;
+                                        h = mHeight;
+                                        wantRenderNotification = true;
+                                        if (LOG_SURFACE) {
+                                            Log.i("GLThread",
+                                                    "noticing that we want render notification tid="
+                                                    + getId());
+                                        }
+
+                                        // Destroy and recreate the EGL surface.
+                                        createEglSurface = true;
+
+                                        mSizeChanged = false;
+                                    }
+                                    mRequestRender = false;
+                                    sGLThreadManager.notifyAll();
+                                    break;
+                                }
+                            }
+
+                            // By design, this is the only place in a GLThread thread where we wait().
+                            if (LOG_THREADS) {
+                                Log.i("GLThread", "waiting tid=" + getId()
+                                    + " mHaveEglContext: " + mHaveEglContext
+                                    + " mHaveEglSurface: " + mHaveEglSurface
+                                    + " mPaused: " + mPaused
+                                    + " mHasSurface: " + mHasSurface
+                                    + " mSurfaceIsBad: " + mSurfaceIsBad
+                                    + " mWaitingForSurface: " + mWaitingForSurface
+                                    + " mWidth: " + mWidth
+                                    + " mHeight: " + mHeight
+                                    + " mRequestRender: " + mRequestRender
+                                    + " mRenderMode: " + mRenderMode);
+                            }
+                            sGLThreadManager.wait();
+                        }
+                    } // end of synchronized(sGLThreadManager)
+
+                    if (event != null) {
+                        event.run();
+                        event = null;
+                        continue;
+                    }
+
+                    if (createEglSurface) {
+                        if (LOG_SURFACE) {
+                            Log.w("GLThread", "egl createSurface");
+                        }
+                        if (!mEglHelper.createSurface()) {
+                            synchronized(sGLThreadManager) {
+                                mSurfaceIsBad = true;
+                                sGLThreadManager.notifyAll();
+                            }
+                            continue;
+                        }
+                        createEglSurface = false;
+                    }
+
+                    if (createGlInterface) {
+                        gl = (GL10) mEglHelper.createGL();
+
+                        sGLThreadManager.checkGLDriver(gl);
+                        createGlInterface = false;
+                    }
+
+                    if (createEglContext) {
+                        if (LOG_RENDERER) {
+                            Log.w("GLThread", "onSurfaceCreated");
+                        }
+                        GLSurfaceViewCustom view = mGLSurfaceViewWeakRef.get();
+                        if (view != null) {
+                            view.mRenderer.onSurfaceCreated(gl, mEglHelper.mEglConfig);
+                        }
+                        createEglContext = false;
+                    }
+
+                    if (sizeChanged) {
+                        if (LOG_RENDERER) {
+                            Log.w("GLThread", "onSurfaceChanged(" + w + ", " + h + ")");
+                        }
+                        GLSurfaceViewCustom view = mGLSurfaceViewWeakRef.get();
+                        if (view != null) {
+                            view.mRenderer.onSurfaceChanged(gl, w, h);
+                        }
+                        sizeChanged = false;
+                    }
+
+                    if (LOG_RENDERER_DRAW_FRAME) {
+                        Log.w("GLThread", "onDrawFrame tid=" + getId());
+                    }
+                    {
+                        GLSurfaceViewCustom view = mGLSurfaceViewWeakRef.get();
+                        if (view != null) {
+                            view.mRenderer.onDrawFrame(gl);
+                        }
+                    }
+                    int swapError = mEglHelper.swap();
+                    switch (swapError) {
+                        case EGL10.EGL_SUCCESS:
+                            GLSurfaceViewCustom view = mGLSurfaceViewWeakRef.get();
+                            if (view != null) {
+                                view.mRenderer.onEglSwapBuffers();
+                            }
+                            break;
+                        case EGL11.EGL_CONTEXT_LOST:
+                            if (LOG_SURFACE) {
+                                Log.i("GLThread", "egl context lost tid=" + getId());
+                            }
+                            lostEglContext = true;
+                            break;
+                        default:
+                            // Other errors typically mean that the current surface is bad,
+                            // probably because the SurfaceView surface has been destroyed,
+                            // but we haven't been notified yet.
+                            // Log the error to help developers understand why rendering stopped.
+                            EglHelper.logEglErrorAsWarning("GLThread", "eglSwapBuffers", swapError);
+
+                            synchronized(sGLThreadManager) {
+                                mSurfaceIsBad = true;
+                                sGLThreadManager.notifyAll();
+                            }
+                            break;
+                    }
+
+                    if (wantRenderNotification) {
+                        doRenderNotification = true;
+                    }
+                }
+
+            } finally {
+                /*
+                 * clean-up everything...
+                 */
+                synchronized (sGLThreadManager) {
+                    stopEglSurfaceLocked();
+                    stopEglContextLocked();
+                }
+            }
+        }
+
+        public boolean ableToDraw() {
+            return mHaveEglContext && mHaveEglSurface && readyToDraw();
+        }
+
+        private boolean readyToDraw() {
+            return (!mPaused) && mHasSurface && (!mSurfaceIsBad)
+                && (mWidth > 0) && (mHeight > 0)
+                && (mRequestRender || (mRenderMode == RENDERMODE_CONTINUOUSLY));
+        }
+
+        public void setRenderMode(int renderMode) {
+            if ( !((RENDERMODE_WHEN_DIRTY <= renderMode) && (renderMode <= RENDERMODE_CONTINUOUSLY)) ) {
+                throw new IllegalArgumentException("renderMode");
+            }
+            synchronized(sGLThreadManager) {
+                mRenderMode = renderMode;
+                sGLThreadManager.notifyAll();
+            }
+        }
+
+        public int getRenderMode() {
+            synchronized(sGLThreadManager) {
+                return mRenderMode;
+            }
+        }
+
+        public void requestRender() {
+            synchronized(sGLThreadManager) {
+                mRequestRender = true;
+                sGLThreadManager.notifyAll();
+            }
+        }
+
+        public void surfaceCreated() {
+            synchronized(sGLThreadManager) {
+                if (LOG_THREADS) {
+                    Log.i("GLThread", "surfaceCreated tid=" + getId());
+                }
+                mHasSurface = true;
+                sGLThreadManager.notifyAll();
+                while((mWaitingForSurface) && (!mExited)) {
+                    try {
+                        sGLThreadManager.wait();
+                    } catch (InterruptedException e) {
+                        Thread.currentThread().interrupt();
+                    }
+                }
+            }
+        }
+
+        public void surfaceDestroyed() {
+            synchronized(sGLThreadManager) {
+                if (LOG_THREADS) {
+                    Log.i("GLThread", "surfaceDestroyed tid=" + getId());
+                }
+                mHasSurface = false;
+                sGLThreadManager.notifyAll();
+                while((!mWaitingForSurface) && (!mExited)) {
+                    try {
+                        sGLThreadManager.wait();
+                    } catch (InterruptedException e) {
+                        Thread.currentThread().interrupt();
+                    }
+                }
+            }
+        }
+
+        public void onPause() {
+            synchronized (sGLThreadManager) {
+                if (LOG_PAUSE_RESUME) {
+                    Log.i("GLThread", "onPause tid=" + getId());
+                }
+                mRequestPaused = true;
+                sGLThreadManager.notifyAll();
+                while ((! mExited) && (! mPaused)) {
+                    if (LOG_PAUSE_RESUME) {
+                        Log.i("Main thread", "onPause waiting for mPaused.");
+                    }
+                    try {
+                        sGLThreadManager.wait();
+                    } catch (InterruptedException ex) {
+                        Thread.currentThread().interrupt();
+                    }
+                }
+            }
+        }
+
+        public void onResume() {
+            synchronized (sGLThreadManager) {
+                if (LOG_PAUSE_RESUME) {
+                    Log.i("GLThread", "onResume tid=" + getId());
+                }
+                mRequestPaused = false;
+                mRequestRender = true;
+                mRenderComplete = false;
+                sGLThreadManager.notifyAll();
+                while ((! mExited) && mPaused && (!mRenderComplete)) {
+                    if (LOG_PAUSE_RESUME) {
+                        Log.i("Main thread", "onResume waiting for !mPaused.");
+                    }
+                    try {
+                        sGLThreadManager.wait();
+                    } catch (InterruptedException ex) {
+                        Thread.currentThread().interrupt();
+                    }
+                }
+            }
+        }
+
+        public void onWindowResize(int w, int h) {
+            synchronized (sGLThreadManager) {
+                mWidth = w;
+                mHeight = h;
+                mSizeChanged = true;
+                mRequestRender = true;
+                mRenderComplete = false;
+                sGLThreadManager.notifyAll();
+
+                // Wait for thread to react to resize and render a frame
+                while (! mExited && !mPaused && !mRenderComplete
+                        && ableToDraw()) {
+                    if (LOG_SURFACE) {
+                        Log.i("Main thread", "onWindowResize waiting for render complete from tid=" + getId());
+                    }
+                    try {
+                        sGLThreadManager.wait();
+                    } catch (InterruptedException ex) {
+                        Thread.currentThread().interrupt();
+                    }
+                }
+            }
+        }
+
+        public void requestExitAndWait() {
+            // don't call this from GLThread thread or it is a guaranteed
+            // deadlock!
+            synchronized(sGLThreadManager) {
+                mShouldExit = true;
+                sGLThreadManager.notifyAll();
+                while (! mExited) {
+                    try {
+                        sGLThreadManager.wait();
+                    } catch (InterruptedException ex) {
+                        Thread.currentThread().interrupt();
+                    }
+                }
+            }
+        }
+
+        public void requestReleaseEglContextLocked() {
+            mShouldReleaseEglContext = true;
+            sGLThreadManager.notifyAll();
+        }
+
+        /**
+         * Queue an "event" to be run on the GL rendering thread.
+         * @param r the runnable to be run on the GL rendering thread.
+         */
+        public void queueEvent(Runnable r) {
+            if (r == null) {
+                throw new IllegalArgumentException("r must not be null");
+            }
+            synchronized(sGLThreadManager) {
+                mEventQueue.add(r);
+                sGLThreadManager.notifyAll();
+            }
+        }
+
+        // Once the thread is started, all accesses to the following member
+        // variables are protected by the sGLThreadManager monitor
+        private boolean mShouldExit;
+        private boolean mExited;
+        private boolean mRequestPaused;
+        private boolean mPaused;
+        private boolean mHasSurface;
+        private boolean mSurfaceIsBad;
+        private boolean mWaitingForSurface;
+        private boolean mHaveEglContext;
+        private boolean mHaveEglSurface;
+        private boolean mShouldReleaseEglContext;
+        private int mWidth;
+        private int mHeight;
+        private int mRenderMode;
+        private boolean mRequestRender;
+        private boolean mRenderComplete;
+        private ArrayList<Runnable> mEventQueue = new ArrayList<Runnable>();
+        private boolean mSizeChanged = true;
+
+        // End of member variables protected by the sGLThreadManager monitor.
+
+        private EglHelper mEglHelper;
+
+        /**
+         * Set once at thread construction time, nulled out when the parent view is garbage
+         * called. This weak reference allows the GLSurfaceView to be garbage collected while
+         * the GLThread is still alive.
+         */
+        private WeakReference<GLSurfaceViewCustom> mGLSurfaceViewWeakRef;
+
+    }
+
+    static class LogWriter extends Writer {
+
+        @Override public void close() {
+            flushBuilder();
+        }
+
+        @Override public void flush() {
+            flushBuilder();
+        }
+
+        @Override public void write(char[] buf, int offset, int count) {
+            for(int i = 0; i < count; i++) {
+                char c = buf[offset + i];
+                if ( c == '\n') {
+                    flushBuilder();
+                }
+                else {
+                    mBuilder.append(c);
+                }
+            }
+        }
+
+        private void flushBuilder() {
+            if (mBuilder.length() > 0) {
+                Log.v("GLSurfaceViewCustom", mBuilder.toString());
+                mBuilder.delete(0, mBuilder.length());
+            }
+        }
+
+        private StringBuilder mBuilder = new StringBuilder();
+    }
+
+
+    private void checkRenderThreadState() {
+        if (mGLThread != null) {
+            throw new IllegalStateException(
+                    "setRenderer has already been called for this instance.");
+        }
+    }
+
+    private static class GLThreadManager {
+        private static String TAG = "GLThreadManager";
+
+        public synchronized void threadExiting(GLThread thread) {
+            if (LOG_THREADS) {
+                Log.i("GLThread", "exiting tid=" +  thread.getId());
+            }
+            thread.mExited = true;
+            if (mEglOwner == thread) {
+                mEglOwner = null;
+            }
+            notifyAll();
+        }
+
+        /*
+         * Tries once to acquire the right to use an EGL
+         * context. Does not block. Requires that we are already
+         * in the sGLThreadManager monitor when this is called.
+         *
+         * @return true if the right to use an EGL context was acquired.
+         */
+        public boolean tryAcquireEglContextLocked(GLThread thread) {
+            if (mEglOwner == thread || mEglOwner == null) {
+                mEglOwner = thread;
+                notifyAll();
+                return true;
+            }
+            checkGLESVersion();
+            if (mMultipleGLESContextsAllowed) {
+                return true;
+            }
+            // Notify the owning thread that it should release the context.
+            // TODO: implement a fairness policy. Currently
+            // if the owning thread is drawing continuously it will just
+            // reacquire the EGL context.
+            if (mEglOwner != null) {
+                mEglOwner.requestReleaseEglContextLocked();
+            }
+            return false;
+        }
+
+        /*
+         * Releases the EGL context. Requires that we are already in the
+         * sGLThreadManager monitor when this is called.
+         */
+        public void releaseEglContextLocked(GLThread thread) {
+            if (mEglOwner == thread) {
+                mEglOwner = null;
+            }
+            notifyAll();
+        }
+
+        public synchronized boolean shouldReleaseEGLContextWhenPausing() {
+            // Release the EGL context when pausing even if
+            // the hardware supports multiple EGL contexts.
+            // Otherwise the device could run out of EGL contexts.
+            return mLimitedGLESContexts;
+        }
+
+        public synchronized boolean shouldTerminateEGLWhenPausing() {
+            checkGLESVersion();
+            return !mMultipleGLESContextsAllowed;
+        }
+
+        public synchronized void checkGLDriver(GL10 gl) {
+            if (! mGLESDriverCheckComplete) {
+                checkGLESVersion();
+                String renderer = gl.glGetString(GL10.GL_RENDERER);
+                if (mGLESVersion < kGLES_20) {
+                    mMultipleGLESContextsAllowed =
+                        ! renderer.startsWith(kMSM7K_RENDERER_PREFIX);
+                    notifyAll();
+                }
+                mLimitedGLESContexts = !mMultipleGLESContextsAllowed;
+                if (LOG_SURFACE) {
+                    Log.w(TAG, "checkGLDriver renderer = \"" + renderer + "\" multipleContextsAllowed = "
+                        + mMultipleGLESContextsAllowed
+                        + " mLimitedGLESContexts = " + mLimitedGLESContexts);
+                }
+                mGLESDriverCheckComplete = true;
+            }
+        }
+
+        private void checkGLESVersion() {
+            if (! mGLESVersionCheckComplete) {
+                mGLESVersion = kGLES_20; /*SystemProperties.getInt(
+                        "ro.opengles.version",
+                        ConfigurationInfo.GL_ES_VERSION_UNDEFINED); */
+                if (mGLESVersion >= kGLES_20) {
+                    mMultipleGLESContextsAllowed = true;
+                }
+                if (LOG_SURFACE) {
+                    Log.w(TAG, "checkGLESVersion mGLESVersion =" +
+                            " " + mGLESVersion + " mMultipleGLESContextsAllowed = " + mMultipleGLESContextsAllowed);
+                }
+                mGLESVersionCheckComplete = true;
+            }
+        }
+
+        /**
+         * This check was required for some pre-Android-3.0 hardware. Android 3.0 provides
+         * support for hardware-accelerated views, therefore multiple EGL contexts are
+         * supported on all Android 3.0+ EGL drivers.
+         */
+        private boolean mGLESVersionCheckComplete;
+        private int mGLESVersion;
+        private boolean mGLESDriverCheckComplete;
+        private boolean mMultipleGLESContextsAllowed;
+        private boolean mLimitedGLESContexts;
+        private static final int kGLES_20 = 0x20000;
+        private static final String kMSM7K_RENDERER_PREFIX =
+            "Q3Dimension MSM7500 ";
+        private GLThread mEglOwner;
+    }
+
+    private static final GLThreadManager sGLThreadManager = new GLThreadManager();
+
+    private final WeakReference<GLSurfaceViewCustom> mThisWeakRef =
+            new WeakReference<GLSurfaceViewCustom>(this);
+    private GLThread mGLThread;
+    private Renderer mRenderer;
+    private boolean mDetached;
+    private EGLConfigChooser mEGLConfigChooser;
+    private EGLContextFactory mEGLContextFactory;
+    private EGLWindowSurfaceFactory mEGLWindowSurfaceFactory;
+    private GLWrapper mGLWrapper;
+    private int mDebugFlags;
+    private int mEGLContextClientVersion;
+    private boolean mPreserveEGLContextOnPause;
+}
diff --git a/tests/tests/openglperf/src/android/openglperf/cts/GlPlanetsActivity.java b/tests/tests/openglperf/src/android/openglperf/cts/GlPlanetsActivity.java
index 5a8c0a8..7aa1d21 100644
--- a/tests/tests/openglperf/src/android/openglperf/cts/GlPlanetsActivity.java
+++ b/tests/tests/openglperf/src/android/openglperf/cts/GlPlanetsActivity.java
@@ -34,14 +34,15 @@
     public static final String INTENT_EXTRA_USE_VBO_VERTICES = "useVboVertices";
     public static final String INTENT_EXTRA_USE_VBO_INDICES = "useVboIndiices";
     public static final String INTENT_EXTRA_NUM_FRAMES = "numFrames";
-    public static final String INTENT_EXTRA_INDICES_PER_VERTEX = "numIndicesPerVertex";
+    public static final String INTENT_EXTRA_NUM_INDEX_BUFFERS = "numIndexBuffers";
 
     public static final String INTENT_RESULT_FPS = "fps";
     public static final String INTENT_RESULT_NUM_TRIANGLES = "numTrigngles";
 
     private final Semaphore mSem = new Semaphore(0);
-    private float mFps;
+    private float mAverageFps;
     private int mNumTriangles;
+    private int[] mFrameInterval;
 
     private PlanetsSurfaceView mView;
 
@@ -50,14 +51,23 @@
         return mSem.tryAcquire(timeoutInSecs, TimeUnit.SECONDS);
     }
 
-    public float getFps() {
-        return mFps;
+    public float getAverageFps() {
+        return mAverageFps;
     }
 
     public int getNumTriangles() {
         return mNumTriangles;
     }
 
+    /**
+     * Time interval between each frame's rendering in ms.
+     * The first value will be invalid, so client should discard them.
+     * @return can return null if INTENT_EXTRA_NUM_FRAMES was not set in intent.
+     */
+    public int[] getFrameInterval() {
+        return mFrameInterval;
+    }
+
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -70,16 +80,17 @@
         param.mUseVboForIndices = intent.getBooleanExtra(INTENT_EXTRA_USE_VBO_INDICES,
                 param.mUseVboForIndices);
         param.mNumFrames = intent.getIntExtra(INTENT_EXTRA_NUM_FRAMES, param.mNumFrames);
-        param.mNumIndicesPerVertex = intent.getIntExtra(INTENT_EXTRA_INDICES_PER_VERTEX,
+        param.mNumIndicesPerVertex = intent.getIntExtra(INTENT_EXTRA_NUM_INDEX_BUFFERS,
                 param.mNumIndicesPerVertex);
         mView = new PlanetsSurfaceView(this, param, this);
         setContentView(mView);
     }
 
     @Override
-    public void onRenderCompletion(float fps, int numTriangles) {
-        mFps = fps;
+    public void onRenderCompletion(float averageFps, int numTriangles,  int[] frameInterval) {
+        mAverageFps = averageFps;
         mNumTriangles = numTriangles;
+        mFrameInterval = frameInterval;
         mSem.release();
     }
 
diff --git a/tests/tests/openglperf/src/android/openglperf/cts/GlVboPerfTest.java b/tests/tests/openglperf/src/android/openglperf/cts/GlVboPerfTest.java
index afd435c..2e9e10b 100644
--- a/tests/tests/openglperf/src/android/openglperf/cts/GlVboPerfTest.java
+++ b/tests/tests/openglperf/src/android/openglperf/cts/GlVboPerfTest.java
@@ -87,14 +87,14 @@
     }
 
     private void runRendering(int numPlanets, boolean useVboVertex, boolean useVboIndex,
-            int indicesPerVertex) throws Exception {
+            int numIndexBuffers) throws Exception {
         Intent intent = new Intent();
         intent.putExtra(GlPlanetsActivity.INTENT_EXTRA_NUM_FRAMES,
                 NUM_FRAMES_TO_RENDER);
         intent.putExtra(GlPlanetsActivity.INTENT_EXTRA_NUM_PLANETS, numPlanets);
         intent.putExtra(GlPlanetsActivity.INTENT_EXTRA_USE_VBO_VERTICES, useVboVertex);
         intent.putExtra(GlPlanetsActivity.INTENT_EXTRA_USE_VBO_INDICES, useVboIndex);
-        intent.putExtra(GlPlanetsActivity.INTENT_EXTRA_INDICES_PER_VERTEX, indicesPerVertex);
+        intent.putExtra(GlPlanetsActivity.INTENT_EXTRA_NUM_INDEX_BUFFERS, numIndexBuffers);
 
         setActivityIntent(intent);
         final GlPlanetsActivity activity = getActivity();
@@ -102,7 +102,7 @@
                 .waitForGlPlanetsCompletionWithTimeout(RENDERING_TIMEOUT);
         assertTrue("timeout while waiting for rendering completion", waitResult);
 
-        mFps = activity.getFps();
+        mFps = activity.getAverageFps();
         mNumTriangles = activity.getNumTriangles();
 
         cleanUpActivity();
diff --git a/tests/tests/openglperf/src/android/openglperf/cts/OpenGlPerfNative.java b/tests/tests/openglperf/src/android/openglperf/cts/OpenGlPerfNative.java
new file mode 100644
index 0000000..d63a640
--- /dev/null
+++ b/tests/tests/openglperf/src/android/openglperf/cts/OpenGlPerfNative.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2012 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.openglperf.cts;
+
+public class OpenGlPerfNative {
+    static {
+        System.loadLibrary("ctsopenglperf_jni");
+    }
+    /**
+     * wait for the completion of the pending Egl/Gl operation using eglClientWaitSyncKHR.
+     * @param waitTimeInNs maximum time to wait in nano secs
+     * @return true for success, false for timeout
+     */
+    public static native boolean waitForEglCompletion(long waitTimeInNs);
+}
diff --git a/tests/tests/openglperf/src/android/openglperf/cts/PlanetsRenderer.java b/tests/tests/openglperf/src/android/openglperf/cts/PlanetsRenderer.java
index 5b90089..b286e46 100644
--- a/tests/tests/openglperf/src/android/openglperf/cts/PlanetsRenderer.java
+++ b/tests/tests/openglperf/src/android/openglperf/cts/PlanetsRenderer.java
@@ -17,10 +17,10 @@
 package android.openglperf.cts;
 
 import android.content.Context;
+import android.cts.util.WatchDog;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.opengl.GLES20;
-import android.opengl.GLSurfaceView;
 import android.opengl.GLUtils;
 import android.opengl.Matrix;
 import android.util.Log;
@@ -34,18 +34,21 @@
 import javax.microedition.khronos.egl.EGLConfig;
 import javax.microedition.khronos.opengles.GL10;
 
-
-public class PlanetsRenderer implements GLSurfaceView.Renderer {
+/**
+ * OpenGl renderer rendering given number of planets with different GL configuration.
+ */
+public class PlanetsRenderer implements GLSurfaceViewCustom.Renderer {
 
     private static final String TAG = "PlanetsRenderer";
     // texture is from
     // http://en.wikipedia.org/wiki/File:Mercator_projection_SW.jpg
     private static final String TEXTURE_FILE = "world_512_512.jpg";
+    private static final long EGL_SWAP_BUFFERS_WAIT_TIME_IN_NS = 100 * 1000 * 1000 * 1000L;
 
     private final Context mContext;
     private final PlanetsRenderingParam mParam;
     private final RenderCompletionListener mListener;
-    private final RenderingWatchDog mWatchDog;
+    private final WatchDog mWatchDog;
 
     private final Sphere[] mSpheres;
     private final int mNumSpheres;
@@ -65,9 +68,11 @@
     private int mFrameCount = 0;
     private static final int FPS_DISPLAY_INTERVAL = 50;
     private long mLastFPSTime;
+    private long mLastRenderingTime;
     // for total FPS measurement
     private long mRenderingStartTime;
     private long mMeasurementStartTime;
+    private int[] mFrameInterval = null;
 
     private int mProgram; // shader program
     private int mMVPMatrixHandle;
@@ -95,7 +100,7 @@
      * @param listener
      */
     public PlanetsRenderer(Context context, PlanetsRenderingParam param,
-            RenderCompletionListener listener, RenderingWatchDog watchDog) {
+            RenderCompletionListener listener, WatchDog watchDog) {
         resetTimer();
         mContext = context;
         mParam = param;
@@ -104,6 +109,9 @@
         mNumIndices = mNumSpheres * mParam.mNumIndicesPerVertex;
         mSpheres = new Sphere[mNumSpheres];
 
+        if (mParam.mNumFrames > 0) {
+            mFrameInterval = new int[mParam.mNumFrames];
+        }
         printParams();
 
         // for big model, this construction phase takes time...
@@ -121,6 +129,7 @@
         measureTime("construction");
     }
 
+    @Override
     public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {
         mProgram = createProgram(getVertexShader(), getFragmentShader());
         if (mProgram == 0) {
@@ -137,31 +146,12 @@
         mTextureId = createTexture2D();
     }
 
+    @Override
     public void onDrawFrame(GL10 glUnused) {
         mWatchDog.reset();
         long currentTime = System.currentTimeMillis();
-
         mFrameCount++;
-        if ((mFrameCount % FPS_DISPLAY_INTERVAL == 0) && (mFrameCount != 0)) {
-            float fps = (((float) FPS_DISPLAY_INTERVAL)
-                    / ((float) (currentTime - mLastFPSTime)) * 1000.0f);
-            // FPS is not correct if activity is paused/resumed.
-            Log.i(TAG, "FPS " + fps);
-            mLastFPSTime = currentTime;
-        }
-
-        if ((mFrameCount == mParam.mNumFrames) && (mParam.mNumFrames > 0)) {
-            long timePassed = currentTime - mRenderingStartTime;
-            float fps = ((float) mParam.mNumFrames) / ((float) timePassed) * 1000.0f;
-            printGlInfos();
-            printParams();
-            int numTriangles = mNumSpheres * mSpheres[0].getTotalIndices() / 3;
-            Log.i(TAG, "Final FPS " + fps + " Num triangles " + numTriangles);
-            if (mListener != null) {
-                mListener.onRenderCompletion(fps, numTriangles);
-                return;
-            }
-        }
+        mLastRenderingTime = currentTime;
 
         float angle = 0.090f * ((int) (currentTime % 4000L));
         Matrix.setRotateM(mMMatrix, 0, angle, 0, 0, 1.0f);
@@ -232,6 +222,36 @@
         }
     }
 
+    @Override
+    public void onEglSwapBuffers() {
+        if (!OpenGlPerfNative.waitForEglCompletion(EGL_SWAP_BUFFERS_WAIT_TIME_IN_NS)) {
+            Log.w(TAG, "time-out or error while waiting for eglSwapBuffers completion");
+        }
+        long currentTime = System.currentTimeMillis();
+        if (mFrameCount == 0) {
+            mRenderingStartTime = currentTime;
+        }
+        if (mFrameCount < mParam.mNumFrames) {
+            mFrameInterval[mFrameCount] = (int)(currentTime - mLastRenderingTime);
+        }
+
+        if ((mFrameCount == mParam.mNumFrames) && (mParam.mNumFrames > 0)) {
+            long timePassed = currentTime - mRenderingStartTime;
+            float fps = ((float) mParam.mNumFrames) / ((float) timePassed) * 1000.0f;
+            printGlInfos();
+            printParams();
+            int numTriangles = mNumSpheres * mSpheres[0].getTotalIndices() / 3;
+            Log.i(TAG, "Final FPS " + fps + " Num triangles " + numTriangles + " start time " +
+                    mRenderingStartTime + " finish time " + currentTime);
+            if (mListener != null) {
+                mListener.onRenderCompletion(fps, numTriangles, mFrameInterval);
+                mFrameCount++; // to prevent entering here again
+                return;
+            }
+        }
+    }
+
+    @Override
     public void onSurfaceChanged(GL10 glUnused, int width, int height) {
         mWidth = width;
         mHeight = height;
diff --git a/tests/tests/openglperf/src/android/openglperf/cts/PlanetsSurfaceView.java b/tests/tests/openglperf/src/android/openglperf/cts/PlanetsSurfaceView.java
index 2bfd001..072988d 100644
--- a/tests/tests/openglperf/src/android/openglperf/cts/PlanetsSurfaceView.java
+++ b/tests/tests/openglperf/src/android/openglperf/cts/PlanetsSurfaceView.java
@@ -17,11 +17,11 @@
 package android.openglperf.cts;
 
 import android.content.Context;
-import android.opengl.GLSurfaceView;
+import android.cts.util.WatchDog;
 
-class PlanetsSurfaceView extends GLSurfaceView {
+class PlanetsSurfaceView extends GLSurfaceViewCustom {
     private final long RENDERING_TIMEOUT = 1900; // in msec, close to 2 secs
-    private final RenderingWatchDog mWatchDog = new RenderingWatchDog(RENDERING_TIMEOUT);
+    private final WatchDog mWatchDog = new WatchDog(RENDERING_TIMEOUT);
 
     public PlanetsSurfaceView(Context context, PlanetsRenderingParam param,
             RenderCompletionListener listener) {
@@ -44,4 +44,4 @@
         setRenderMode(RENDERMODE_CONTINUOUSLY);
         super.onResume();
     }
-}
\ No newline at end of file
+}
diff --git a/tests/tests/openglperf/src/android/openglperf/cts/RenderCompletionListener.java b/tests/tests/openglperf/src/android/openglperf/cts/RenderCompletionListener.java
index 1643087..a5bbfa2 100644
--- a/tests/tests/openglperf/src/android/openglperf/cts/RenderCompletionListener.java
+++ b/tests/tests/openglperf/src/android/openglperf/cts/RenderCompletionListener.java
@@ -16,11 +16,15 @@
 
 package android.openglperf.cts;
 
+/**
+ * Interface used to notify the completion of requested rendering.
+ */
 public interface RenderCompletionListener {
     /**
-     * @param fps total frame rate
+     * @param averageFps average of total frames
      * @param numTriangles Number of triangles in geometric model
+     * @param frameInterval interval for each frame in ms. Do not use the first one and the last one.
      */
-    void onRenderCompletion(float fps, int numTriangles);
+    void onRenderCompletion(float averageFps, int numTriangles, int[] frameInterval);
 
 }
diff --git a/tests/tests/openglperf/src/android/openglperf/cts/Sphere.java b/tests/tests/openglperf/src/android/openglperf/cts/Sphere.java
index 026ba97..8020db6 100644
--- a/tests/tests/openglperf/src/android/openglperf/cts/Sphere.java
+++ b/tests/tests/openglperf/src/android/openglperf/cts/Sphere.java
@@ -49,7 +49,7 @@
      * @param x,y,z the origin of the sphere
      * @param r the radius of the sphere
      */
-    public Sphere(int nSlices, float x, float y, float z, float r, int indicesPerVertex) {
+    public Sphere(int nSlices, float x, float y, float z, float r, int numIndexBuffers) {
 
         int iMax = nSlices + 1;
         int nVertices = iMax * iMax;
@@ -64,17 +64,17 @@
         // 3 vertex coords + 2 texture coords
         mVertices = ByteBuffer.allocateDirect(nVertices * 5 * FLOAT_SIZE)
                 .order(ByteOrder.nativeOrder()).asFloatBuffer();
-        mIndices = new ShortBuffer[indicesPerVertex];
-        mNumIndices = new int[indicesPerVertex];
+        mIndices = new ShortBuffer[numIndexBuffers];
+        mNumIndices = new int[numIndexBuffers];
         // first evenly distribute to n-1 buffers, then put remaining ones to the last one.
-        int noIndicesPerBuffer = (mTotalIndices / indicesPerVertex / 6) * 6;
-        for (int i = 0; i < indicesPerVertex - 1; i++) {
+        int noIndicesPerBuffer = (mTotalIndices / numIndexBuffers / 6) * 6;
+        for (int i = 0; i < numIndexBuffers - 1; i++) {
             mNumIndices[i] = noIndicesPerBuffer;
         }
-        mNumIndices[indicesPerVertex - 1] = mTotalIndices - noIndicesPerBuffer *
-                (indicesPerVertex - 1);
+        mNumIndices[numIndexBuffers - 1] = mTotalIndices - noIndicesPerBuffer *
+                (numIndexBuffers - 1);
 
-        for (int i = 0; i < indicesPerVertex; i++) {
+        for (int i = 0; i < numIndexBuffers; i++) {
             mIndices[i] = ByteBuffer.allocateDirect(mNumIndices[i] * SHORT_SIZE)
                     .order(ByteOrder.nativeOrder()).asShortBuffer();
         }
@@ -123,7 +123,7 @@
         mIndices[bufferNum].put(indexBuffer, 0, mNumIndices[bufferNum]);
 
         mVertices.position(0);
-        for (int i = 0; i < indicesPerVertex; i++) {
+        for (int i = 0; i < numIndexBuffers; i++) {
             mIndices[i].position(0);
         }
     }
diff --git a/tests/tests/os/src/android/os/cts/UsbDebuggingTest.java b/tests/tests/os/src/android/os/cts/UsbDebuggingTest.java
new file mode 100644
index 0000000..60583e7
--- /dev/null
+++ b/tests/tests/os/src/android/os/cts/UsbDebuggingTest.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2013 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.os.cts;
+
+import android.os.Build;
+import android.os.SystemProperties;
+import android.test.AndroidTestCase;
+import java.io.File;
+
+public class UsbDebuggingTest extends AndroidTestCase {
+
+    public void testUsbDebugging() {
+        // Secure USB debugging must be enabled
+        assertEquals("1", SystemProperties.get("ro.adb.secure"));
+
+        // Don't ship vendor keys in user build
+        if ("user".equals(Build.TYPE)) {
+            File keys = new File("/adb_keys");
+            assertFalse(keys.exists());
+        }
+    }
+
+}
diff --git a/tests/tests/os/src/android/os/cts/WorkSourceTest.java b/tests/tests/os/src/android/os/cts/WorkSourceTest.java
new file mode 100644
index 0000000..f3986b1
--- /dev/null
+++ b/tests/tests/os/src/android/os/cts/WorkSourceTest.java
@@ -0,0 +1,557 @@
+/*
+ * Copyright (C) 2013 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.os.cts;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import android.os.WorkSource;
+import android.test.AndroidTestCase;
+
+public class WorkSourceTest extends AndroidTestCase {
+    private Constructor<WorkSource> mConstructWS;
+    private Object[] mConstructWSArgs = new Object[1];
+    private Method mAddUid;
+    private Object[] mAddUidArgs = new Object[1];
+    private Method mAddUidName;
+    private Object[] mAddUidNameArgs = new Object[2];
+    private Method mAddReturningNewbs;
+    private Object[] mAddReturningNewbsArgs = new Object[1];
+    private Method mSetReturningDiffs;
+    private Object[] mSetReturningDiffsArgs = new Object[1];
+    private Method mStripNames;
+    private Object[] mStripNamesArgs = new Object[0];
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mConstructWS = WorkSource.class.getConstructor(new Class[] { int.class });
+        mAddUid = WorkSource.class.getMethod("add", new Class[] { int.class });
+        mAddUidName = WorkSource.class.getMethod("add", new Class[] { int.class, String.class });
+        mAddReturningNewbs = WorkSource.class.getMethod("addReturningNewbs", new Class[] { WorkSource.class });
+        mSetReturningDiffs = WorkSource.class.getMethod("setReturningDiffs", new Class[] { WorkSource.class });
+        mStripNames = WorkSource.class.getMethod("stripNames", new Class[] {  });
+    }
+
+    private WorkSource wsNew(int uid) throws IllegalArgumentException,
+            InstantiationException, IllegalAccessException, InvocationTargetException {
+        mConstructWSArgs[0] = uid;
+        return mConstructWS.newInstance(mConstructWSArgs);
+    }
+
+    private WorkSource wsNew(int[] uids) throws IllegalArgumentException,
+            InstantiationException, IllegalAccessException, InvocationTargetException {
+        WorkSource ws = new WorkSource();
+        for (int i=0; i<uids.length; i++) {
+            wsAdd(ws, uids[i]);
+        }
+        checkWorkSource("Constructed", ws, uids);
+        return ws;
+    }
+
+    private WorkSource wsNew(int[] uids, String[] names) throws IllegalArgumentException,
+            InstantiationException, IllegalAccessException, InvocationTargetException {
+        WorkSource ws = new WorkSource();
+        for (int i=0; i<uids.length; i++) {
+            wsAdd(ws, uids[i], names[i]);
+        }
+        checkWorkSource("Constructed", ws, uids, names);
+        return ws;
+    }
+
+    private boolean wsAdd(WorkSource ws, int uid) throws IllegalArgumentException,
+            InstantiationException, IllegalAccessException, InvocationTargetException {
+        mAddUidArgs[0] = uid;
+        return (Boolean)mAddUid.invoke(ws, mAddUidArgs);
+    }
+
+    private boolean wsAdd(WorkSource ws, int uid, String name) throws IllegalArgumentException,
+            InstantiationException, IllegalAccessException, InvocationTargetException {
+        mAddUidNameArgs[0] = uid;
+        mAddUidNameArgs[1] = name;
+        return (Boolean)mAddUidName.invoke(ws, mAddUidNameArgs);
+    }
+
+    private WorkSource wsAddReturningNewbs(WorkSource ws, WorkSource other) throws IllegalArgumentException,
+            InstantiationException, IllegalAccessException, InvocationTargetException {
+        mAddReturningNewbsArgs[0] = other;
+        return (WorkSource)mAddReturningNewbs.invoke(ws, mAddReturningNewbsArgs);
+    }
+
+    private WorkSource[] wsSetReturningDiffs(WorkSource ws, WorkSource other) throws IllegalArgumentException,
+            InstantiationException, IllegalAccessException, InvocationTargetException {
+        mSetReturningDiffsArgs[0] = other;
+        return (WorkSource[])mSetReturningDiffs.invoke(ws, mSetReturningDiffsArgs);
+    }
+
+    private WorkSource wsStripNames(WorkSource ws) throws IllegalArgumentException,
+            InstantiationException, IllegalAccessException, InvocationTargetException {
+        return (WorkSource)mStripNames.invoke(ws);
+    }
+
+    private void printArrays(StringBuilder sb, int[] uids, String[] names) {
+        sb.append("{ ");
+        for (int i=0; i<uids.length; i++) {
+            if (i > 0) sb.append(", ");
+            sb.append(uids[i]);
+            if (names != null) {
+                sb.append(" ");
+                sb.append(names[i]);
+            }
+        }
+        sb.append(" }");
+    }
+
+    private void failWorkSource(String op, WorkSource ws, int[] uids) {
+        StringBuilder sb = new StringBuilder();
+        sb.append(op);
+        sb.append(": Expected: ");
+        printArrays(sb, uids, null);
+        sb.append(", got: ");
+        sb.append(ws);
+        fail(sb.toString());
+    }
+
+    private void failWorkSource(String op, WorkSource ws, int[] uids, String[] names) {
+        StringBuilder sb = new StringBuilder();
+        sb.append(op);
+        sb.append(": Expected: ");
+        printArrays(sb, uids, names);
+        sb.append(", got: ");
+        sb.append(ws);
+        fail(sb.toString());
+    }
+
+    private void checkWorkSource(String op, WorkSource ws, int[] uids) {
+        if (ws == null || uids == null) {
+            if (ws != null) {
+                fail(op + ": WorkSource is not null " + ws +", but expected null");
+            }
+            if (uids != null) {
+                fail(op + "WorkSource is null, but expected non-null: " + uids);
+            }
+            return;
+        }
+        if (ws.size() != uids.length) {
+            failWorkSource(op, ws, uids);
+        }
+        for (int i=0; i<uids.length; i++) {
+            if (uids[i] != ws.get(i)) {
+                failWorkSource(op, ws, uids);
+            }
+        }
+    }
+
+    private void checkWorkSource(String op, WorkSource ws, int[] uids, String[] names) {
+        if (ws == null || uids == null) {
+            if (ws != null) {
+                fail(op + ": WorkSource is not null " + ws +", but expected null");
+            }
+            if (uids != null) {
+                fail(op + "WorkSource is null, but expected non-null: " + uids);
+            }
+            return;
+        }
+        if (ws.size() != uids.length) {
+            failWorkSource(op, ws, uids, names);
+        }
+        for (int i=0; i<uids.length; i++) {
+            if (uids[i] != ws.get(i) || !names[i].equals(ws.getName(i))) {
+                failWorkSource(op, ws, uids, names);
+            }
+        }
+    }
+
+    public void testConstructEmpty() {
+        checkWorkSource("Empty", new WorkSource(), new int[] { });
+    }
+
+    public void testConstructSingle() throws Exception {
+        checkWorkSource("Single 1", wsNew(1), new int[] { 1 });
+    }
+
+    public void testAddRawOrdered() throws Exception {
+        WorkSource ws = wsNew(1);
+        wsAdd(ws, 2);
+        checkWorkSource("First", ws, new int[] { 1 , 2 });
+        wsAdd(ws, 20);
+        checkWorkSource("Second", ws, new int[] { 1 , 2, 20 });
+        wsAdd(ws, 100);
+        checkWorkSource("Third", ws, new int[] { 1, 2, 20, 100 });
+    }
+
+    public void testAddRawRevOrdered() throws Exception {
+        WorkSource ws = wsNew(100);
+        wsAdd(ws, 20);
+        checkWorkSource("First", ws, new int[] { 20, 100 });
+        wsAdd(ws, 2);
+        checkWorkSource("Second", ws, new int[] { 2, 20, 100 });
+        wsAdd(ws, 1);
+        checkWorkSource("Third", ws, new int[] { 1, 2, 20, 100 });
+    }
+
+    public void testAddRawUnordered() throws Exception {
+        WorkSource ws = wsNew(10);
+        wsAdd(ws, 2);
+        checkWorkSource("First", ws, new int[] { 2, 10 });
+        wsAdd(ws, 5);
+        checkWorkSource("Second", ws, new int[] { 2, 5, 10 });
+        wsAdd(ws, 1);
+        checkWorkSource("Third", ws, new int[] { 1, 2, 5, 10 });
+        wsAdd(ws, 100);
+        checkWorkSource("Fourth", ws, new int[] { 1, 2, 5, 10, 100 });
+    }
+
+    public void testAddWsOrdered() throws Exception {
+        WorkSource ws = wsNew(1);
+        ws.add(wsNew(2));
+        checkWorkSource("First", ws, new int[] { 1 , 2 });
+        ws.add(wsNew(20));
+        checkWorkSource("Second", ws, new int[] { 1 , 2, 20 });
+        ws.add(wsNew(100));
+        checkWorkSource("Third", ws, new int[] { 1 , 2, 20, 100 });
+    }
+
+    public void testAddWsRevOrdered() throws Exception {
+        WorkSource ws = wsNew(100);
+        ws.add(wsNew(20));
+        checkWorkSource("First", ws, new int[] { 20, 100 });
+        ws.add(wsNew(2));
+        checkWorkSource("Second", ws, new int[] { 2, 20, 100 });
+        ws.add(wsNew(1));
+        checkWorkSource("Third", ws, new int[] { 1, 2, 20, 100 });
+    }
+
+    public void testAddWsUnordered() throws Exception {
+        WorkSource ws = wsNew(10);
+        ws.add(wsNew(2));
+        checkWorkSource("First", ws, new int[] { 2, 10 });
+        ws.add(wsNew(5));
+        checkWorkSource("Second", ws, new int[] { 2, 5, 10 });
+        ws.add(wsNew(1));
+        checkWorkSource("Third", ws, new int[] { 1, 2, 5, 10 });
+        ws.add(wsNew(100));
+        checkWorkSource("Fourth", ws, new int[] { 1, 2, 5, 10, 100 });
+    }
+
+    private void doTestCombineTwoUids(int[] lhs, int[] rhs, int[] expected, int[] newbs,
+            int[] gones) throws Exception {
+        WorkSource ws1 = wsNew(lhs);
+        WorkSource ws2 = wsNew(rhs);
+        ws1.add(ws2);
+        checkWorkSource("Add result", ws1, expected);
+        ws1 = wsNew(lhs);
+        WorkSource wsNewbs = wsAddReturningNewbs(ws1, ws2);
+        checkWorkSource("AddReturning result", ws1, expected);
+        checkWorkSource("Newbs", wsNewbs, newbs);
+        ws1 = wsNew(lhs);
+        WorkSource[] res = wsSetReturningDiffs(ws1, ws2);
+        checkWorkSource("SetReturning result", ws1, rhs);
+        checkWorkSource("Newbs", res[0], newbs);
+        checkWorkSource("Gones", res[1], gones);
+    }
+
+    private int[] makeRepeatingIntArray(String[] stringarray, int value) {
+        if (stringarray == null) {
+            return null;
+        }
+        int[] res = new int[stringarray.length];
+        for (int i=0; i<stringarray.length; i++) {
+            res[i] = value;
+        }
+        return res;
+    }
+
+    private void doTestCombineTwoNames(String[] lhsnames, String[] rhsnames,
+            String[] expectednames, String[] newbnames,
+            String[] gonenames) throws Exception {
+        int[] lhs = makeRepeatingIntArray(lhsnames, 0);
+        int[] rhs = makeRepeatingIntArray(rhsnames, 0);
+        int[] expected = makeRepeatingIntArray(expectednames, 0);
+        int[] newbs = makeRepeatingIntArray(newbnames, 0);
+        int[] gones = makeRepeatingIntArray(gonenames, 0);
+        doTestCombineTwoUidsNames(lhs, lhsnames, rhs, rhsnames, expected, expectednames,
+                newbs, newbnames, gones, gonenames);
+    }
+
+    private void doTestCombineTwoUidsNames(int[] lhs, String[] lhsnames, int[] rhs, String[] rhsnames,
+            int[] expected, String[] expectednames, int[] newbs, String[] newbnames,
+            int[] gones, String[] gonenames) throws Exception {
+        WorkSource ws1 = wsNew(lhs, lhsnames);
+        WorkSource ws2 = wsNew(rhs, rhsnames);
+        ws1.add(ws2);
+        checkWorkSource("Add result", ws1, expected, expectednames);
+        ws1 = wsNew(lhs, lhsnames);
+        WorkSource wsNewbs = wsAddReturningNewbs(ws1, ws2);
+        checkWorkSource("AddReturning result", ws1, expected, expectednames);
+        checkWorkSource("Newbs", wsNewbs, newbs, newbnames);
+        ws1 = wsNew(lhs, lhsnames);
+        WorkSource[] res = wsSetReturningDiffs(ws1, ws2);
+        checkWorkSource("SetReturning result", ws1, rhs, rhsnames);
+        checkWorkSource("Newbs", res[0], newbs, newbnames);
+        checkWorkSource("Gones", res[1], gones, gonenames);
+    }
+
+    private String[] makeRepeatingStringArray(int[] intarray, String value) {
+        if (intarray == null) {
+            return null;
+        }
+        String[] res = new String[intarray.length];
+        for (int i=0; i<intarray.length; i++) {
+            res[i] = value;
+        }
+        return res;
+    }
+
+    private String[] makeStringArray(int[] intarray) {
+        if (intarray == null) {
+            return null;
+        }
+        String[] res = new String[intarray.length];
+        for (int i=0; i<intarray.length; i++) {
+            res[i] = Character.toString((char)('A' + intarray[i]));
+        }
+        return res;
+    }
+
+    private void doTestCombineTwo(int[] lhs, int[] rhs, int[] expected, int[] newbs,
+            int[] gones) throws Exception {
+        doTestCombineTwoUids(lhs, rhs, expected, newbs, gones);
+        doTestCombineTwoUidsNames(lhs, makeRepeatingStringArray(lhs, "A"),
+                rhs, makeRepeatingStringArray(rhs, "A"),
+                expected, makeRepeatingStringArray(expected, "A"),
+                newbs, makeRepeatingStringArray(newbs, "A"),
+                gones, makeRepeatingStringArray(gones, "A"));
+        doTestCombineTwoNames(makeStringArray(lhs), makeStringArray(rhs),
+                makeStringArray(expected), makeStringArray(newbs), makeStringArray(gones));
+    }
+
+    public void testCombineMultiFront() throws Exception {
+        doTestCombineTwo(
+                new int[] { 10, 20, 30, 40 },
+                new int[] { 1, 2, 15, 16 },
+                new int[] { 1, 2, 10, 15, 16, 20, 30, 40 },
+                new int[] { 1, 2, 15, 16 },
+                new int[] { 10, 20, 30, 40 });
+    }
+
+    public void testCombineMultiMiddle() throws Exception {
+        doTestCombineTwo(
+                new int[] { 10, 20, 30, 40 },
+                new int[] { 12, 14, 22 },
+                new int[] { 10, 12, 14, 20, 22, 30, 40 },
+                new int[] { 12, 14, 22 },
+                new int[] { 10, 20, 30, 40 });
+    }
+
+    public void testCombineMultiEnd() throws Exception {
+        doTestCombineTwo(
+                new int[] { 10, 20, 30, 40 },
+                new int[] { 35, 45, 50 },
+                new int[] { 10, 20, 30, 35, 40, 45, 50 },
+                new int[] { 35, 45, 50 },
+                new int[] { 10, 20, 30, 40 });
+    }
+
+    public void testCombineMultiFull() throws Exception {
+        doTestCombineTwo(
+                new int[] { 10, 20, 30, 40 },
+                new int[] { 1, 2, 15, 35, 50 },
+                new int[] { 1, 2, 10, 15, 20, 30, 35, 40, 50 },
+                new int[] { 1, 2, 15, 35, 50 },
+                new int[] { 10, 20, 30, 40 });
+    }
+
+    public void testCombineMultiSame() throws Exception {
+        doTestCombineTwo(
+                new int[] { 10, 20, 30, 40 },
+                new int[] { 10, 20, 30 },
+                new int[] { 10, 20, 30, 40 },
+                null,
+                new int[] { 40 });
+    }
+
+    public void testCombineMultiSomeSame() throws Exception {
+        doTestCombineTwo(
+                new int[] { 10, 20, 30, 40 },
+                new int[] { 1, 30, 40 },
+                new int[] { 1, 10, 20, 30, 40 },
+                new int[] { 1 },
+                new int[] { 10, 20 });
+    }
+
+    public void testCombineMultiSomeSameUidsNames() throws Exception {
+        doTestCombineTwoUidsNames(
+                new int[]    { 10,  10,  20,  30,  30,  30,  40 },
+                new String[] { "A", "B", "A", "A", "B", "C", "A" },
+                new int[]    { 1,   30,  40,  50 },
+                new String[] { "A", "A", "B", "A" },
+                new int[]    { 1,   10,  10,  20,  30,  30,  30,  40,  40,  50 },
+                new String[] { "A", "A", "B", "A", "A", "B", "C", "A", "B", "A" },
+                new int[]    { 1,   40,  50 },
+                new String[] { "A", "B", "A" },
+                new int[]    { 10,  10,  20,  30,  30,  40 },
+                new String[] { "A", "B", "A", "B", "C", "A" });
+    }
+
+    private void doTestRemoveUids(int[] lhs, int[] rhs, int[] result, boolean diff) throws Exception {
+        WorkSource ws1 = wsNew(lhs);
+        WorkSource ws2 = wsNew(rhs);
+        boolean diffres = ws1.remove(ws2);
+        if (diffres != diff) {
+            StringBuilder sb = new StringBuilder();
+            sb.append("Expected diff ");
+            sb.append(diff);
+            sb.append(" but got ");
+            sb.append(diffres);
+            sb.append(" when removing ");
+            sb.append(ws2);
+            sb.append(" from ");
+            sb.append(ws1);
+            fail(sb.toString());
+        }
+        checkWorkSource("Remove", ws1, result);
+    }
+
+    private void doTestRemoveNames(String[] lhsnames, String[] rhsnames,
+            String[] resultnames, boolean diff) throws Exception {
+        int[] lhs = makeRepeatingIntArray(lhsnames, 0);
+        int[] rhs = makeRepeatingIntArray(rhsnames, 0);
+        int[] result = makeRepeatingIntArray(resultnames, 0);
+        WorkSource ws1 = wsNew(lhs, lhsnames);
+        WorkSource ws2 = wsNew(rhs, rhsnames);
+        boolean diffres = ws1.remove(ws2);
+        if (diffres != diff) {
+            StringBuilder sb = new StringBuilder();
+            sb.append("Expected diff ");
+            sb.append(diff);
+            sb.append(" but got ");
+            sb.append(diffres);
+            sb.append(" when removing ");
+            sb.append(ws2);
+            sb.append(" from ");
+            sb.append(ws1);
+            fail(sb.toString());
+        }
+        checkWorkSource("Remove", ws1, result, resultnames);
+    }
+
+    private void doTestRemoveUidsNames(int[] lhs, String[] lhsnames, int[] rhs, String[] rhsnames,
+            int[] result, String[] resultnames, boolean diff) throws Exception {
+        WorkSource ws1 = wsNew(lhs, lhsnames);
+        WorkSource ws2 = wsNew(rhs, rhsnames);
+        boolean diffres = ws1.remove(ws2);
+        if (diffres != diff) {
+            StringBuilder sb = new StringBuilder();
+            sb.append("Expected diff ");
+            sb.append(diff);
+            sb.append(" but got ");
+            sb.append(diffres);
+            sb.append(" when removing ");
+            sb.append(ws2);
+            sb.append(" from ");
+            sb.append(ws1);
+            fail(sb.toString());
+        }
+        checkWorkSource("Remove", ws1, result, resultnames);
+    }
+
+    private void doTestRemove(int[] lhs, int[] rhs, int[] result, boolean diff) throws Exception {
+        doTestRemoveUids(lhs, rhs, result, diff);
+        doTestRemoveUidsNames(lhs, makeRepeatingStringArray(lhs, "A"),
+                rhs, makeRepeatingStringArray(rhs, "A"),
+                result, makeRepeatingStringArray(result, "A"),
+                diff);
+        doTestRemoveNames(makeStringArray(lhs), makeStringArray(rhs),
+                makeStringArray(result), diff);
+    }
+
+    public void testRemoveNone() throws Exception {
+        doTestRemove(
+                new int[] { 10, 20, 30, 40 },
+                new int[] { 1, 2, 35, 50 },
+                new int[] { 10, 20, 30, 40 },
+                false);
+    }
+
+    public void testRemoveMultiFront() throws Exception {
+        doTestRemove(
+                new int[] { 10, 20, 30, 40 },
+                new int[] { 1, 2, 10, 30 },
+                new int[] { 20, 40 },
+                true);
+    }
+
+    public void testRemoveMultiMiddle() throws Exception {
+        doTestRemove(
+                new int[] { 10, 20, 30, 40 },
+                new int[] { 20, 30 },
+                new int[] { 10, 40 },
+                true);
+    }
+
+    public void testRemoveMultiEnd() throws Exception {
+        doTestRemove(
+                new int[] { 10, 20, 30, 40 },
+                new int[] { 30, 40, 50 },
+                new int[] { 10, 20 },
+                true);
+    }
+
+    public void testRemoveMultiFull() throws Exception {
+        doTestRemove(
+                new int[] { 10, 20, 30, 40 },
+                new int[] { 1, 2, 20, 25, 35, 40 },
+                new int[] { 10, 30 },
+                true);
+    }
+
+    public void testRemoveMultiAll() throws Exception {
+        doTestRemove(
+                new int[] { 10, 20, 30, 40 },
+                new int[] { 10, 20, 30, 40 },
+                new int[] { },
+                true);
+    }
+
+    private void doTestStripNames(int[] uids, String[] names, int[] expected) throws Exception {
+        WorkSource ws1 = wsNew(uids, names);
+        WorkSource res = wsStripNames(ws1);
+        checkWorkSource("StripNames", res, expected);
+    }
+
+    public void testStripNamesSimple() throws Exception {
+        doTestStripNames(
+                new int[]    { 10,  20,  30,  40 },
+                new String[] { "A", "A", "A", "A" },
+                new int[]    { 10, 20, 30, 40 });
+    }
+
+    public void testStripNamesFull() throws Exception {
+        doTestStripNames(
+                new int[]    { 10,  10,  10,  10 },
+                new String[] { "A", "B", "C", "D" },
+                new int[]    { 10 });
+    }
+
+    public void testStripNamesComplex() throws Exception {
+        doTestStripNames(
+                new int[]    { 10,  20,  20,  30,  40,  40 },
+                new String[] { "A", "A", "B", "A", "A", "B" },
+                new int[]    { 10, 20, 30, 40 });
+    }
+}
diff --git a/tests/tests/permission/src/android/permission/cts/NoLocationPermissionTest.java b/tests/tests/permission/src/android/permission/cts/NoLocationPermissionTest.java
index 80dffba..da9057e 100644
--- a/tests/tests/permission/src/android/permission/cts/NoLocationPermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/NoLocationPermissionTest.java
@@ -110,33 +110,6 @@
     }
 
     /**
-     * Verify that get cell location requires permissions.
-     * <p>
-     * Requires Permission: {@link
-     * android.Manifest.permission#ACCESS_COARSE_LOCATION.}
-     */
-    @SmallTest
-    public void testListenCellLocation2() {
-        TelephonyManager telephonyManager = (TelephonyManager) getContext().getSystemService(
-                Context.TELEPHONY_SERVICE);
-        PhoneStateListener phoneStateListener = new PhoneStateListener();
-
-        try {
-            telephonyManager.getNeighboringCellInfo();
-            fail("TelephonyManager.getNeighbouringCellInfo did not throw SecurityException as expected");
-        } catch (SecurityException e) {
-            // expected
-        }
-
-        try {
-            telephonyManager.getAllCellInfo();
-            fail("TelephonyManager.getAllCellInfo did not throw SecurityException as expected");
-        } catch (SecurityException e) {
-            // expected
-        }
-    }
-
-    /**
      * Helper method to verify that calling requestLocationUpdates with given
      * provider throws SecurityException.
      * 
diff --git a/tests/tests/permission2/src/android/permission2/cts/NoReceiveGsmSmsPermissionTest.java b/tests/tests/permission2/src/android/permission2/cts/NoReceiveGsmSmsPermissionTest.java
deleted file mode 100644
index 9b20d1d..0000000
--- a/tests/tests/permission2/src/android/permission2/cts/NoReceiveGsmSmsPermissionTest.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2009 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.permission2.cts;
-
-import android.app.PendingIntent;
-import android.telephony.gsm.SmsManager;
-
-/**
- * Verify Sms and Mms cannot be received without required permissions.
- * Uses {@link android.telephony.gsm.SmsManager}.
- */
-@SuppressWarnings("deprecation")
-public class NoReceiveGsmSmsPermissionTest extends NoReceiveSmsPermissionTest {
-
-    protected void sendSms(PendingIntent sentIntent, PendingIntent deliveryIntent,
-            String currentNumber) {
-        SmsManager.getDefault().sendTextMessage(currentNumber, null, "test message",
-                sentIntent, deliveryIntent);
-    }
-}
diff --git a/tests/tests/permission2/src/android/permission2/cts/ProtectedBroadcastsTest.java b/tests/tests/permission2/src/android/permission2/cts/ProtectedBroadcastsTest.java
index 877a89e..f7e5443 100755
--- a/tests/tests/permission2/src/android/permission2/cts/ProtectedBroadcastsTest.java
+++ b/tests/tests/permission2/src/android/permission2/cts/ProtectedBroadcastsTest.java
@@ -80,7 +80,8 @@
         "android.net.wifi.p2p.CONNECTION_STATE_CHANGE",
         "android.net.wifi.p2p.PERSISTENT_GROUPS_CHANGED",
         "android.net.conn.TETHER_STATE_CHANGED",
-        "android.net.conn.INET_CONDITION_ACTION"
+        "android.net.conn.INET_CONDITION_ACTION",
+        "android.net.conn.CAPTIVE_PORTAL_TEST_COMPLETED"
     };
 
     /**
diff --git a/tests/tests/provider/src/android/provider/cts/CalendarTest.java b/tests/tests/provider/src/android/provider/cts/CalendarTest.java
index e460453..3c10d0c 100644
--- a/tests/tests/provider/src/android/provider/cts/CalendarTest.java
+++ b/tests/tests/provider/src/android/provider/cts/CalendarTest.java
@@ -1769,15 +1769,34 @@
 
         // Test that inserting a valid color index works
         ev = EventHelper.getNewEventValues(account, seed++, cal_id, false);
-        ev.put(Events.EVENT_COLOR_KEY, ColorHelper.DEFAULT_INDICES[ColorHelper.E_COLOR_0]);
+        final String defaultColorIndex = ColorHelper.DEFAULT_INDICES[ColorHelper.E_COLOR_0];
+        ev.put(Events.EVENT_COLOR_KEY, defaultColorIndex);
 
         Uri uri = mContentResolver.insert(Events.CONTENT_URI, ev);
         long eventId2 = ContentUris.parseId(uri);
         assertTrue(eventId2 >= 0);
         // And updates the event's color to the one in the table
-        ev.put(Events.EVENT_COLOR, ColorHelper.DEFAULT_COLORS[ColorHelper.E_COLOR_0]);
+        final int expectedColor = ColorHelper.DEFAULT_COLORS[ColorHelper.E_COLOR_0];
+        ev.put(Events.EVENT_COLOR, expectedColor);
         verifyEvent(ev, eventId2);
 
+        // Test that event iterator has COLOR columns
+        final EntityIterator iterator = EventsEntity.newEntityIterator(mContentResolver.query(
+                ContentUris.withAppendedId(EventsEntity.CONTENT_URI, eventId2),
+                null, null, null, null), mContentResolver);
+        assertTrue("Empty Iterator", iterator.hasNext());
+        final Entity entity = iterator.next();
+        final ContentValues values = entity.getEntityValues();
+        assertTrue("Missing EVENT_COLOR", values.containsKey(EventsEntity.EVENT_COLOR));
+        assertEquals("Wrong EVENT_COLOR",
+                expectedColor,
+                (int) values.getAsInteger(EventsEntity.EVENT_COLOR));
+        assertTrue("Missing EVENT_COLOR_KEY", values.containsKey(EventsEntity.EVENT_COLOR_KEY));
+        assertEquals("Wrong EVENT_COLOR_KEY",
+                defaultColorIndex,
+                values.getAsString(EventsEntity.EVENT_COLOR_KEY));
+        iterator.close();
+
         // Test that updating a valid color index also updates the color in an
         // event
         ev.clear();
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_AlbumsTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_AlbumsTest.java
index 100d9e1..b265cbf 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_AlbumsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_AlbumsTest.java
@@ -45,12 +45,15 @@
     }
 
     public void testGetContentUri() {
-        assertNotNull(mContentResolver.query(
+        Cursor c = null;
+        assertNotNull(c = mContentResolver.query(
                 Albums.getContentUri(MediaStoreAudioTestHelper.INTERNAL_VOLUME_NAME), null, null,
                 null, null));
-        assertNotNull(mContentResolver.query(
+        c.close();
+        assertNotNull(c = mContentResolver.query(
                 Albums.getContentUri(MediaStoreAudioTestHelper.EXTERNAL_VOLUME_NAME), null, null,
                 null, null));
+        c.close();
 
         // can not accept any other volume names
         String volume = "fakeVolume";
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_ArtistsTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_ArtistsTest.java
index 2e3cc1b..9fe31c5 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_ArtistsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_ArtistsTest.java
@@ -37,12 +37,15 @@
     }
 
     public void testGetContentUri() {
-        assertNotNull(mContentResolver.query(
+        Cursor c = null;
+        assertNotNull(c = mContentResolver.query(
                 Artists.getContentUri(MediaStoreAudioTestHelper.INTERNAL_VOLUME_NAME), null, null,
                     null, null));
-        assertNotNull(mContentResolver.query(
+        c.close();
+        assertNotNull(c = mContentResolver.query(
                 Artists.getContentUri(MediaStoreAudioTestHelper.INTERNAL_VOLUME_NAME), null, null,
                 null, null));
+        c.close();
 
         // can not accept any other volume names
         String volume = "fakeVolume";
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_Artists_AlbumsTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_Artists_AlbumsTest.java
index fdba619..72d9067 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_Artists_AlbumsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_Artists_AlbumsTest.java
@@ -39,12 +39,16 @@
     }
 
     public void testGetContentUri() {
+        Cursor c = null;
         Uri contentUri = MediaStore.Audio.Artists.Albums.getContentUri(
                 MediaStoreAudioTestHelper.INTERNAL_VOLUME_NAME, 1);
-        assertNotNull(mContentResolver.query(contentUri, null, null, null, null));
+        assertNotNull(c = mContentResolver.query(contentUri, null, null, null, null));
+        c.close();
+
         contentUri = MediaStore.Audio.Artists.Albums.getContentUri(
                 MediaStoreAudioTestHelper.EXTERNAL_VOLUME_NAME, 1);
-        assertNotNull(mContentResolver.query(contentUri, null, null, null, null));
+        assertNotNull(c = mContentResolver.query(contentUri, null, null, null, null));
+        c.close();
 
         // can not accept any other volume names
         String volume = "fakeVolume";
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_GenresTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_GenresTest.java
index 1138233..8d41b38 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_GenresTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_GenresTest.java
@@ -39,14 +39,16 @@
     }
 
     public void testGetContentUri() {
-        assertNotNull(mContentResolver.query(
+        Cursor c = null;
+        assertNotNull(c = mContentResolver.query(
                 Genres.getContentUri(MediaStoreAudioTestHelper.EXTERNAL_VOLUME_NAME), null, null,
                     null, null));
-
+        c.close();
         try {
-            assertNotNull(mContentResolver.query(
+            assertNotNull(c = mContentResolver.query(
                     Genres.getContentUri(MediaStoreAudioTestHelper.INTERNAL_VOLUME_NAME), null,
                         null, null, null));
+            c.close();
             fail("Should throw SQLException as the internal datatbase has no genre");
         } catch (SQLException e) {
             // expected
@@ -98,6 +100,7 @@
         // Insert an audio file into the content provider.
         ContentValues values = Audio1.getInstance().getContentValues(true);
         Uri audioUri = mContentResolver.insert(Media.EXTERNAL_CONTENT_URI, values);
+        assertNotNull(audioUri);
         long audioId = ContentUris.parseId(audioUri);
         assertTrue(audioId != -1);
 
@@ -105,6 +108,7 @@
         values.clear();
         values.put(Genres.NAME, "Soda Pop");
         Uri genreUri = mContentResolver.insert(Genres.EXTERNAL_CONTENT_URI, values);
+        assertNotNull(genreUri);
         long genreId = ContentUris.parseId(genreUri);
         assertTrue(genreId != -1);
 
@@ -131,6 +135,8 @@
             if (cursor != null) {
                 cursor.close();
             }
+            assertEquals(1, mContentResolver.delete(audioUri, null, null));
+            assertEquals(1, mContentResolver.delete(genreUri, null, null));
         }
     }
 }
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_Genres_MembersTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_Genres_MembersTest.java
index 2e389b6..7310fa1 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_Genres_MembersTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_Genres_MembersTest.java
@@ -65,14 +65,17 @@
     }
 
     public void testGetContentUri() {
-        assertNotNull(mContentResolver.query(
+        Cursor c = null;
+        assertNotNull(c = mContentResolver.query(
                 Members.getContentUri(MediaStoreAudioTestHelper.EXTERNAL_VOLUME_NAME, 1), null,
                     null, null, null));
+        c.close();
 
         try {
-            assertNotNull(mContentResolver.query(
+            assertNotNull(c = mContentResolver.query(
                     Members.getContentUri(MediaStoreAudioTestHelper.INTERNAL_VOLUME_NAME, 1), null,
                         null, null, null));
+            c.close();
             fail("Should throw SQLException as the internal datatbase has no genre");
         } catch (SQLException e) {
             // expected
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_MediaTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_MediaTest.java
index 8a4e3ee..82c2342 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_MediaTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_MediaTest.java
@@ -38,12 +38,15 @@
     }
 
     public void testGetContentUri() {
-        assertNotNull(mContentResolver.query(
+        Cursor c = null;
+        assertNotNull(c = mContentResolver.query(
                 Media.getContentUri(MediaStoreAudioTestHelper.INTERNAL_VOLUME_NAME), null, null,
                     null, null));
-        assertNotNull(mContentResolver.query(
+        c.close();
+        assertNotNull(c = mContentResolver.query(
                 Media.getContentUri(MediaStoreAudioTestHelper.EXTERNAL_VOLUME_NAME), null, null,
                     null, null));
+        c.close();
 
         // can not accept any other volume names
         String volume = "faveVolume";
@@ -51,14 +54,17 @@
     }
 
     public void testGetContentUriForPath() {
+        Cursor c = null;
         String externalPath = Environment.getExternalStorageDirectory().getPath();
-        assertNotNull(mContentResolver.query(Media.getContentUriForPath(externalPath), null, null,
+        assertNotNull(c = mContentResolver.query(Media.getContentUriForPath(externalPath), null, null,
                 null, null));
+        c.close();
 
         String internalPath =
             getInstrumentation().getTargetContext().getFilesDir().getAbsolutePath();
-        assertNotNull(mContentResolver.query(Media.getContentUriForPath(internalPath), null, null,
+        assertNotNull(c = mContentResolver.query(Media.getContentUriForPath(internalPath), null, null,
                 null, null));
+        c.close();
     }
 
     public void testStoreAudioMediaInternal() {
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_PlaylistsTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_PlaylistsTest.java
index 0d36212..03a81b8 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_PlaylistsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_PlaylistsTest.java
@@ -39,15 +39,18 @@
     }
 
     public void testGetContentUri() {
-        assertNotNull(mContentResolver.query(
+        Cursor c = null;
+        assertNotNull(c = mContentResolver.query(
                 Playlists.getContentUri(MediaStoreAudioTestHelper.EXTERNAL_VOLUME_NAME), null, null,
                 null, null));
+        c.close();
 
         // can not accept any other volume names
         try {
-            assertNotNull(mContentResolver.query(
+            assertNotNull(c = mContentResolver.query(
                     Playlists.getContentUri(MediaStoreAudioTestHelper.INTERNAL_VOLUME_NAME), null,
                     null, null, null));
+            c.close();
             fail("Should throw SQLException as the internal datatbase has no playlist");
         } catch (SQLException e) {
             // expected
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_Playlists_MembersTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_Playlists_MembersTest.java
index aa524aa..bf97848 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_Playlists_MembersTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_Playlists_MembersTest.java
@@ -27,6 +27,9 @@
 import android.provider.MediaStore.Audio.Playlists.Members;
 import android.provider.cts.MediaStoreAudioTestHelper.Audio1;
 import android.provider.cts.MediaStoreAudioTestHelper.Audio2;
+import android.provider.cts.MediaStoreAudioTestHelper.Audio3;
+import android.provider.cts.MediaStoreAudioTestHelper.Audio4;
+import android.provider.cts.MediaStoreAudioTestHelper.Audio5;
 import android.provider.cts.MediaStoreAudioTestHelper.MockAudioMediaInfo;
 import android.test.InstrumentationTestCase;
 
@@ -114,9 +117,9 @@
         mContentResolver = getInstrumentation().getContext().getContentResolver();
         mIdOfAudio1 = insertAudioItem(Audio1.getInstance());
         mIdOfAudio2 = insertAudioItem(Audio2.getInstance());
-        mIdOfAudio3 = insertAudioItem(Audio1.getInstance());
-        mIdOfAudio4 = insertAudioItem(Audio1.getInstance());
-        mIdOfAudio5 = insertAudioItem(Audio1.getInstance());
+        mIdOfAudio3 = insertAudioItem(Audio3.getInstance());
+        mIdOfAudio4 = insertAudioItem(Audio4.getInstance());
+        mIdOfAudio5 = insertAudioItem(Audio5.getInstance());
     }
 
     @Override
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_FilesTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_FilesTest.java
index 1e8a4ac..79b2e57 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_FilesTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_FilesTest.java
@@ -84,6 +84,10 @@
         assertEquals(1, mResolver.update(fileUri, values, null, null));
         assertStringColumn(fileUri, MediaColumns.DATA, updatedPath);
 
+        // check that inserting a duplicate entry fails
+        Uri foo = mResolver.insert(allFilesUri, values);
+        assertNull(foo);
+
         // Delete the file and observe that the file count decreased.
         assertEquals(1, mResolver.delete(fileUri, null, null));
         assertEquals(fileCount, getFileCount(allFilesUri));
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Images_MediaTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Images_MediaTest.java
index fa269e4..740e31b 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Images_MediaTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_Images_MediaTest.java
@@ -178,10 +178,13 @@
     }
 
     public void testGetContentUri() {
-        assertNotNull(mContentResolver.query(Media.getContentUri("internal"), null, null, null,
+        Cursor c = null;
+        assertNotNull(c = mContentResolver.query(Media.getContentUri("internal"), null, null, null,
                 null));
-        assertNotNull(mContentResolver.query(Media.getContentUri("external"), null, null, null,
+        c.close();
+        assertNotNull(c = mContentResolver.query(Media.getContentUri("external"), null, null, null,
                 null));
+        c.close();
 
         // can not accept any other volume names
         String volume = "fakeVolume";
@@ -294,6 +297,7 @@
         } finally {
             // delete
             assertEquals(1, mContentResolver.delete(uri, null, null));
+            new File(externalPath).delete();
         }
     }
 
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Images_ThumbnailsTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Images_ThumbnailsTest.java
index 8ecea7b..e8a13a9 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Images_ThumbnailsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_Images_ThumbnailsTest.java
@@ -211,10 +211,13 @@
     }
 
     public void testGetContentUri() {
-        assertNotNull(mContentResolver.query(Thumbnails.getContentUri("internal"), null, null,
+        Cursor c = null;
+        assertNotNull(c = mContentResolver.query(Thumbnails.getContentUri("internal"), null, null,
                 null, null));
-        assertNotNull(mContentResolver.query(Thumbnails.getContentUri("external"), null, null,
+        c.close();
+        assertNotNull(c = mContentResolver.query(Thumbnails.getContentUri("external"), null, null,
                 null, null));
+        c.close();
 
         // can not accept any other volume names
         String volume = "fakeVolume";
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Video_MediaTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Video_MediaTest.java
index f4d01e3..c3eb0b8 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Video_MediaTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_Video_MediaTest.java
@@ -45,10 +45,13 @@
     }
 
     public void testGetContentUri() {
-        assertNotNull(mContentResolver.query(Media.getContentUri("internal"), null, null, null,
+        Cursor c = null;
+        assertNotNull(c = mContentResolver.query(Media.getContentUri("internal"), null, null, null,
                 null));
-        assertNotNull(mContentResolver.query(Media.getContentUri("external"), null, null, null,
+        c.close();
+        assertNotNull(c = mContentResolver.query(Media.getContentUri("external"), null, null, null,
                 null));
+        c.close();
 
         // can not accept any other volume names
         String volume = "fakeVolume";
@@ -180,6 +183,7 @@
         } finally {
             // delete
             assertEquals(1, mContentResolver.delete(uri, null, null));
+            new File(externalVideoPath).delete();
         }
 
         // check that the video file is removed when deleting the database entry
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Video_ThumbnailsTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Video_ThumbnailsTest.java
index 2a4b46c..8f6f729 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Video_ThumbnailsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_Video_ThumbnailsTest.java
@@ -99,6 +99,8 @@
             assertFalse("thumbnail file should no longer exist", new File(path).exists());
         }
         c.close();
+
+        assertEquals(1, mResolver.delete(videoUri, null, null));
     }
 
     private Uri insertVideo() throws IOException {
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/AllocationTest.java b/tests/tests/renderscript/src/android/renderscript/cts/AllocationTest.java
index feac159..4a1533c 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/AllocationTest.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/AllocationTest.java
@@ -28,6 +28,8 @@
 import android.renderscript.Type.Builder;
 import android.renderscript.Type.CubemapFace;
 
+import android.util.Log;
+
 public class AllocationTest extends RSBaseGraphics {
 
     // Test power of two and non power of two, equal and non-equal sizes
@@ -188,10 +190,10 @@
 
     void helperCreateFromBitmap(Bitmap B,
                                 Allocation.MipmapControl mc) {
-        for (int i = 0; i < 1; i++) {
-            for (int j = 0; j < 1; j++) {
-                for (int k = 0; k < 1; k++) {
-                    for (int l = 0; l < 1; l++) {
+        for (int i = 0; i <= 1; i++) {
+            for (int j = 0; j <= 1; j++) {
+                for (int k = 0; k <= 1; k++) {
+                    for (int l = 0; l <= 1; l++) {
                         int u = 0;
                         u |= (i * Allocation.USAGE_SCRIPT);
                         u |= (j * Allocation.USAGE_GRAPHICS_TEXTURE);
@@ -684,6 +686,101 @@
             }
         }
     }
+
+    public void testCopyFromAllocation() {
+        int nElemsX = 256;
+        int nElemsY = 32;
+        Type.Builder b = new Type.Builder(mRS, Element.I8(mRS));
+        Type.Builder b2 = new Type.Builder(mRS, Element.I32(mRS));
+        Allocation srcA = Allocation.createTyped(mRS, b.setX(nElemsX).setY(nElemsY).create());
+        Allocation dstA = Allocation.createTyped(mRS, b.setX(nElemsX).setY(nElemsY).create());
+
+        // wrong dimensionality
+        Allocation srcB_bad = Allocation.createTyped(mRS, b.setX(nElemsX).setY(1).create());
+        Allocation srcC_bad = Allocation.createTyped(mRS, b.setX(nElemsY).setY(nElemsX).create());
+        Allocation srcD_bad = Allocation.createTyped(mRS, b.setX(nElemsX*2).setY(nElemsY*2).create());
+
+        // wrong element type
+        Allocation srcE_bad = Allocation.createTyped(mRS, b2.setX(nElemsX).setY(nElemsY).create());
+
+        try {
+            dstA.copyFrom(srcB_bad);
+            fail("should throw RSIllegalArgumentException");
+        } catch (RSIllegalArgumentException e) {
+        }
+
+        try {
+            dstA.copyFrom(srcC_bad);
+            fail("should throw RSIllegalArgumentException");
+        } catch (RSIllegalArgumentException e) {
+        }
+
+        try {
+            dstA.copyFrom(srcD_bad);
+            fail("should throw RSIllegalArgumentException");
+        } catch (RSIllegalArgumentException e) {
+        }
+
+        try {
+            dstA.copyFrom(srcE_bad);
+            fail("should throw RSIllegalArgumentException");
+        } catch (RSIllegalArgumentException e) {
+        }
+
+        dstA.copyFrom(srcA);
+
+    }
+
+    public void testSetElementAt() {
+        Type.Builder b = new Type.Builder(mRS, Element.I32(mRS));
+        Allocation largeArray = Allocation.createTyped(mRS, b.setX(48).create());
+        Allocation singleElement = Allocation.createTyped(mRS, b.setX(1).create());
+
+        ScriptC_setelementat script = new ScriptC_setelementat(mRS, mRes, R.raw.setelementat);
+
+        script.set_memset_toValue(1);
+        script.forEach_memset(singleElement);
+
+        script.set_dimX(48);
+        script.set_array(largeArray);
+
+        script.forEach_setLargeArray(singleElement);
+
+        int[] result = new int[1];
+
+        script.set_compare_value(10);
+        script.forEach_compare(largeArray);
+        script.forEach_getCompareResult(singleElement);
+        singleElement.copyTo(result);
+        assertTrue(result[0] == 2);
+    }
+
+    public void testSetElementAt2D() {
+        Type.Builder b = new Type.Builder(mRS, Element.I32(mRS));
+
+        Allocation singleElement = Allocation.createTyped(mRS, b.setX(1).create());
+        Allocation largeArray = Allocation.createTyped(mRS, b.setX(48).setY(16).create());
+
+        ScriptC_setelementat script = new ScriptC_setelementat(mRS, mRes, R.raw.setelementat);
+
+        script.set_memset_toValue(1);
+        script.forEach_memset(singleElement);
+
+        script.set_dimX(48);
+        script.set_dimY(16);
+        script.set_array(largeArray);
+
+        script.forEach_setLargeArray2D(singleElement);
+
+        int[] result = new int[1];
+
+        script.set_compare_value(10);
+        script.forEach_compare(largeArray);
+        script.forEach_getCompareResult(singleElement);
+        singleElement.copyTo(result);
+        assertTrue(result[0] == 2);
+    }
+
 }
 
 
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/ComputeTest.java b/tests/tests/renderscript/src/android/renderscript/cts/ComputeTest.java
index d095dd0..40611fd 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/ComputeTest.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/ComputeTest.java
@@ -782,4 +782,44 @@
         }
         checkForErrors();
     }
+
+    /**
+     * Test script instancing.
+     */
+    public void testInstance() {
+        ScriptC_instance instance_1 = new ScriptC_instance(mRS);
+        ScriptC_instance instance_2 = new ScriptC_instance(mRS);
+
+        Type t = new Type.Builder(mRS, Element.I32(mRS)).setX(1).create();
+        Allocation ai1 = Allocation.createTyped(mRS, t);
+        Allocation ai2 = Allocation.createTyped(mRS, t);
+
+        instance_1.set_i(1);
+        instance_2.set_i(2);
+        instance_1.set_ai(ai1);
+        instance_2.set_ai(ai2);
+
+        // We now check to ensure that the global is not being shared across
+        // our separate script instances. Our invoke here merely sets the
+        // instanced allocation with the instanced global variable's value.
+        // If globals are being shared (i.e. not instancing scripts), then
+        // both instanced allocations will have the same resulting value
+        // (depending on the order in which the invokes complete).
+        instance_1.invoke_instance_test();
+        instance_2.invoke_instance_test();
+
+        int i1[] = new int[1];
+        int i2[] = new int[1];
+
+        ai1.copyTo(i1);
+        ai2.copyTo(i2);
+
+        // 3-step check ensures that a fortunate race condition wouldn't let us
+        // pass accidentally.
+        assertEquals(2, i2[0]);
+        assertEquals(1, i1[0]);
+        assertEquals(2, i2[0]);
+
+        checkForErrors();
+    }
 }
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/ElementTest.java b/tests/tests/renderscript/src/android/renderscript/cts/ElementTest.java
index 2ad45b1..ead4528 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/ElementTest.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/ElementTest.java
@@ -487,11 +487,12 @@
         assertEquals(DataKind.PIXEL_RGB, DataKind.valueOf("PIXEL_RGB"));
         assertEquals(DataKind.PIXEL_RGBA, DataKind.valueOf("PIXEL_RGBA"));
         assertEquals(DataKind.PIXEL_DEPTH, DataKind.valueOf("PIXEL_DEPTH"));
+        assertEquals(DataKind.PIXEL_YUV, DataKind.valueOf("PIXEL_YUV"));
         // Make sure no new enums are added
-        assertEquals(7, DataKind.values().length);
+        assertEquals(8, DataKind.values().length);
 
         for (DataKind dk : DataKind.values()) {
-            if (dk != DataKind.USER) {
+            if (dk != DataKind.USER && dk != DataKind.PIXEL_YUV) {
                 Element.createPixel(mRS, DataType.UNSIGNED_8, dk);
             }
         }
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/ForEachTest.java b/tests/tests/renderscript/src/android/renderscript/cts/ForEachTest.java
index f633168..433b7e6 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/ForEachTest.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/ForEachTest.java
@@ -471,6 +471,7 @@
         typeBuilder.setX(X).setY(Y);
         Allocation A = Allocation.createTyped(mRS, typeBuilder.create());
         s.bind_a(A);
+        s.set_aRaw(A);
         s.forEach_root(A);
         s.invoke_verify_root();
         s.forEach_foo(A, A);
@@ -491,6 +492,7 @@
         typeBuilder.setX(X).setY(Y);
         Allocation A = Allocation.createTyped(mRS, typeBuilder.create());
         s.bind_a(A);
+        s.set_aRaw(A);
         s.forEach_foo(A, A);
         s.invoke_verify_foo();
         s.invoke_noroot_test();
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/ImageProcessingTest.java b/tests/tests/renderscript/src/android/renderscript/cts/ImageProcessingTest.java
new file mode 100644
index 0000000..fae20f4
--- /dev/null
+++ b/tests/tests/renderscript/src/android/renderscript/cts/ImageProcessingTest.java
@@ -0,0 +1,338 @@
+/*
+ * Copyright (C) 2012 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.renderscript.cts;
+
+import android.renderscript.Allocation;
+
+import android.renderscript.Byte2;
+import android.renderscript.Byte3;
+import android.renderscript.Byte4;
+
+import android.renderscript.Double2;
+import android.renderscript.Double3;
+import android.renderscript.Double4;
+
+import android.renderscript.Element;
+
+import android.renderscript.Float2;
+import android.renderscript.Float3;
+import android.renderscript.Float4;
+
+import android.renderscript.Int2;
+import android.renderscript.Int3;
+import android.renderscript.Int4;
+
+import android.renderscript.Long2;
+import android.renderscript.Long3;
+import android.renderscript.Long4;
+
+import android.renderscript.RSRuntimeException;
+
+import android.renderscript.Short2;
+import android.renderscript.Short3;
+import android.renderscript.Short4;
+
+import android.renderscript.Matrix4f;
+
+import android.renderscript.Type;
+
+import android.renderscript.ScriptGroup;
+
+import android.renderscript.ScriptIntrinsicBlend;
+import android.renderscript.ScriptIntrinsicBlur;
+import android.renderscript.ScriptIntrinsicColorMatrix;
+import android.renderscript.ScriptIntrinsicConvolve3x3;
+import android.renderscript.ScriptIntrinsicConvolve5x5;
+import android.renderscript.ScriptIntrinsicLUT;
+
+import com.android.cts.stub.R;
+
+public class ImageProcessingTest extends RSBaseCompute {
+    private Allocation a1, a2;
+
+    private final int MAX_RADIUS = 25;
+    private final int dimX = 256;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        Type t = new Type.Builder(mRS, Element.U8_4(mRS)).setX(dimX).setY(dimX).create();
+        a1 = Allocation.createTyped(mRS, t);
+        a2 = Allocation.createTyped(mRS, t);
+    }
+
+    public void testBlur() {
+        ScriptIntrinsicBlur mBlur;
+        mBlur = ScriptIntrinsicBlur.create(mRS, Element.U8_4(mRS));
+
+        Allocation a1_copy, a2_copy;
+        a1_copy = Allocation.createTyped(mRS, a1.getType());
+        a2_copy = Allocation.createTyped(mRS, a2.getType());
+
+        for (int i = 1; i < MAX_RADIUS; i++) {
+
+            a1_copy.copy2DRangeFrom(0, 0, a1.getType().getX(), a1.getType().getY(), a1, 0, 0);
+
+            mBlur.setRadius(i);
+            mBlur.setInput(a1_copy);
+
+            mBlur.forEach(a2_copy);
+
+            // validate
+
+        }
+
+    }
+
+    public void testBlend() {
+        ScriptIntrinsicBlend mBlend;
+        mBlend = ScriptIntrinsicBlend.create(mRS, Element.U8_4(mRS));
+
+        Allocation a1_copy, a2_copy;
+        a1_copy = Allocation.createTyped(mRS, a1.getType());
+        a2_copy = Allocation.createTyped(mRS, a2.getType());
+
+        for (int i = 0; i < 14; i++) {
+
+            a1_copy.copy2DRangeFrom(0, 0, a1.getType().getX(), a1.getType().getY(), a1, 0, 0);
+            a2_copy.copy2DRangeFrom(0, 0, a2.getType().getX(), a2.getType().getY(), a2, 0, 0);
+
+            switch (i) {
+            case 0:
+                mBlend.forEachSrc(a1_copy, a2_copy);
+                break;
+            case 1:
+                mBlend.forEachDst(a1_copy, a2_copy);
+                break;
+            case 2:
+                mBlend.forEachSrcOver(a1_copy, a2_copy);
+                break;
+            case 3:
+                mBlend.forEachDstOver(a1_copy, a2_copy);
+                break;
+            case 4:
+                mBlend.forEachSrcIn(a1_copy, a2_copy);
+                break;
+            case 5:
+                mBlend.forEachDstIn(a1_copy, a2_copy);
+                break;
+            case 6:
+                mBlend.forEachSrcOut(a1_copy, a2_copy);
+                break;
+            case 7:
+                mBlend.forEachDstOut(a1_copy, a2_copy);
+                break;
+            case 8:
+                mBlend.forEachSrcAtop(a1_copy, a2_copy);
+                break;
+            case 9:
+                mBlend.forEachDstAtop(a1_copy, a2_copy);
+                break;
+            case 10:
+                mBlend.forEachXor(a1_copy, a2_copy);
+                break;
+            case 11:
+                mBlend.forEachAdd(a1_copy, a2_copy);
+                break;
+            case 12:
+                mBlend.forEachSubtract(a1_copy, a2_copy);
+                break;
+            case 13:
+                mBlend.forEachMultiply(a1_copy, a2_copy);
+                break;
+            }
+
+            // validate
+
+        }
+
+    }
+
+    public void testColorMatrix() {
+        ScriptIntrinsicColorMatrix mColorMatrix;
+        mColorMatrix = ScriptIntrinsicColorMatrix.create(mRS, Element.U8_4(mRS));
+
+        Allocation a1_copy, a2_copy;
+        a1_copy = Allocation.createTyped(mRS, a1.getType());
+        a2_copy = Allocation.createTyped(mRS, a2.getType());
+
+        Matrix4f m = new Matrix4f();
+        m.set(1, 0, 0.2f);
+        m.set(1, 1, 0.9f);
+        m.set(1, 2, 0.2f);
+
+        //test greyscale
+        mColorMatrix.setGreyscale();
+
+        a1_copy.copy2DRangeFrom(0, 0, a1.getType().getX(), a1.getType().getY(), a1, 0, 0);
+        a2_copy.copy2DRangeFrom(0, 0, a2.getType().getX(), a2.getType().getY(), a2, 0, 0);
+
+        mColorMatrix.forEach(a1_copy, a2_copy);
+
+        //validate greyscale
+
+
+        //test color matrix
+        mColorMatrix.setColorMatrix(m);
+
+        a1_copy.copy2DRangeFrom(0, 0, a1.getType().getX(), a1.getType().getY(), a1, 0, 0);
+        a2_copy.copy2DRangeFrom(0, 0, a2.getType().getX(), a2.getType().getY(), a2, 0, 0);
+
+        mColorMatrix.forEach(a1_copy, a2_copy);
+
+        //validate color matrix
+
+
+    }
+
+
+    public void testConvolve3x3() {
+        ScriptIntrinsicConvolve3x3 mConvolve3x3;
+        mConvolve3x3 = ScriptIntrinsicConvolve3x3.create(mRS, Element.U8_4(mRS));
+
+        Allocation a1_copy, a2_copy;
+        a1_copy = Allocation.createTyped(mRS, a1.getType());
+        a2_copy = Allocation.createTyped(mRS, a2.getType());
+
+        a1_copy.copy2DRangeFrom(0, 0, a1.getType().getX(), a1.getType().getY(), a1, 0, 0);
+
+        float f[] = new float[9];
+        f[0] =  0.f;    f[1] = -1.f;    f[2] =  0.f;
+        f[3] = -1.f;    f[4] =  5.f;    f[5] = -1.f;
+        f[6] =  0.f;    f[7] = -1.f;    f[8] =  0.f;
+
+        mConvolve3x3.setCoefficients(f);
+        mConvolve3x3.setInput(a1_copy);
+        mConvolve3x3.forEach(a2_copy);
+
+        // validate
+
+    }
+
+    public void testConvolve5x5() {
+        ScriptIntrinsicConvolve5x5 mConvolve5x5;
+        mConvolve5x5 = ScriptIntrinsicConvolve5x5.create(mRS, Element.U8_4(mRS));
+
+        Allocation a1_copy, a2_copy;
+        a1_copy = Allocation.createTyped(mRS, a1.getType());
+        a2_copy = Allocation.createTyped(mRS, a2.getType());
+
+        a1_copy.copy2DRangeFrom(0, 0, a1.getType().getX(), a1.getType().getY(), a1, 0, 0);
+
+        float f[] = new float[25];
+        f[0] = -1.f; f[1] = -3.f; f[2] = -4.f; f[3] = -3.f; f[4] = -1.f;
+        f[5] = -3.f; f[6] =  0.f; f[7] =  6.f; f[8] =  0.f; f[9] = -3.f;
+        f[10]= -4.f; f[11]=  6.f; f[12]= 20.f; f[13]=  6.f; f[14]= -4.f;
+        f[15]= -3.f; f[16]=  0.f; f[17]=  6.f; f[18]=  0.f; f[19]= -3.f;
+        f[20]= -1.f; f[21]= -3.f; f[22]= -4.f; f[23]= -3.f; f[24]= -1.f;
+
+        mConvolve5x5.setCoefficients(f);
+        mConvolve5x5.setInput(a1_copy);
+        mConvolve5x5.forEach(a2_copy);
+
+        // validate
+
+    }
+
+    public void testLUT() {
+        ScriptIntrinsicLUT mLUT;
+        mLUT = ScriptIntrinsicLUT.create(mRS, Element.U8_4(mRS));
+
+        Allocation a1_copy, a2_copy;
+        a1_copy = Allocation.createTyped(mRS, a1.getType());
+        a2_copy = Allocation.createTyped(mRS, a2.getType());
+
+        a1_copy.copy2DRangeFrom(0, 0, a1.getType().getX(), a1.getType().getY(), a1, 0, 0);
+
+        for (int ct=0; ct < 256; ct++) {
+            float f = ((float)ct) / 255.f;
+
+            float r = f;
+            if (r < 0.5f) {
+                r = 4.0f * r * r * r;
+            } else {
+                r = 1.0f - r;
+                r = 1.0f - (4.0f * r * r * r);
+            }
+            mLUT.setRed(ct, (int)(r * 255.f + 0.5f));
+
+            float g = f;
+            if (g < 0.5f) {
+                g = 2.0f * g * g;
+            } else {
+                g = 1.0f - g;
+                g = 1.0f - (2.0f * g * g);
+            }
+            mLUT.setGreen(ct, (int)(g * 255.f + 0.5f));
+
+            float b = f * 0.5f + 0.25f;
+            mLUT.setBlue(ct, (int)(b * 255.f + 0.5f));
+        }
+
+        mLUT.forEach(a1_copy, a2_copy);
+
+        // validate
+
+    }
+
+    public void testScriptGroup() {
+        ScriptGroup group;
+
+        ScriptIntrinsicConvolve3x3 mConvolve3x3;
+        ScriptIntrinsicColorMatrix mColorMatrix;
+
+        mConvolve3x3 = ScriptIntrinsicConvolve3x3.create(mRS, Element.U8_4(mRS));
+        mColorMatrix = ScriptIntrinsicColorMatrix.create(mRS, Element.U8_4(mRS));
+
+        Allocation a1_copy, a2_copy;
+        a1_copy = Allocation.createTyped(mRS, a1.getType());
+        a2_copy = Allocation.createTyped(mRS, a2.getType());
+
+        a1_copy.copy2DRangeFrom(0, 0, a1.getType().getX(), a1.getType().getY(), a1, 0, 0);
+
+        float f[] = new float[9];
+        f[0] =  0.f;    f[1] = -1.f;    f[2] =  0.f;
+        f[3] = -1.f;    f[4] =  5.f;    f[5] = -1.f;
+        f[6] =  0.f;    f[7] = -1.f;    f[8] =  0.f;
+
+        mConvolve3x3.setCoefficients(f);
+
+        Matrix4f m = new Matrix4f();
+        m.set(1, 0, 0.2f);
+        m.set(1, 1, 0.9f);
+        m.set(1, 2, 0.2f);
+        mColorMatrix.setColorMatrix(m);
+
+        Type connect = new Type.Builder(mRS, Element.U8_4(mRS)).setX(dimX).setY(dimX).create();
+
+        ScriptGroup.Builder b = new ScriptGroup.Builder(mRS);
+        b.addKernel(mConvolve3x3.getKernelID());
+        b.addKernel(mColorMatrix.getKernelID());
+        b.addConnection(connect, mConvolve3x3.getKernelID(), mColorMatrix.getKernelID());
+        group = b.create();
+
+        mConvolve3x3.setInput(a1_copy);
+        group.setOutput(mColorMatrix.getKernelID(), a2_copy);
+        group.execute();
+
+        // validate
+
+    }
+
+
+}
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/KernelTest.java b/tests/tests/renderscript/src/android/renderscript/cts/KernelTest.java
index 7caacfc..56b5e89 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/KernelTest.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/KernelTest.java
@@ -471,6 +471,7 @@
         typeBuilder.setX(X).setY(Y);
         Allocation A = Allocation.createTyped(mRS, typeBuilder.create());
         s.bind_a(A);
+        s.set_aRaw(A);
         s.forEach_root(A);
         s.invoke_verify_root();
         s.forEach_foo(A, A);
@@ -491,6 +492,7 @@
         typeBuilder.setX(X).setY(Y);
         Allocation A = Allocation.createTyped(mRS, typeBuilder.create());
         s.bind_a(A);
+        s.set_aRaw(A);
         s.forEach_foo(A, A);
         s.invoke_verify_foo();
         s.invoke_noroot_test();
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/SamplerTest.java b/tests/tests/renderscript/src/android/renderscript/cts/SamplerTest.java
index 35e813e..fa30f9f 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/SamplerTest.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/SamplerTest.java
@@ -115,6 +115,9 @@
         assertTrue(Sampler.WRAP_LINEAR(mRS) != null);
         assertTrue(Sampler.WRAP_LINEAR_MIP_LINEAR(mRS) != null);
         assertTrue(Sampler.WRAP_NEAREST(mRS) != null);
+        assertTrue(Sampler.MIRRORED_REPEAT_NEAREST(mRS) != null);
+        assertTrue(Sampler.MIRRORED_REPEAT_LINEAR(mRS) != null);
+        assertTrue(Sampler.MIRRORED_REPEAT_LINEAR_MIP_LINEAR(mRS) != null);
     }
 
     public void testSamplerValue() {
@@ -124,9 +127,10 @@
         assertEquals(Value.LINEAR_MIP_NEAREST, Value.valueOf("LINEAR_MIP_NEAREST"));
         assertEquals(Value.WRAP, Value.valueOf("WRAP"));
         assertEquals(Value.CLAMP, Value.valueOf("CLAMP"));
+        assertEquals(Value.MIRRORED_REPEAT, Value.valueOf("MIRRORED_REPEAT"));
 
         // Make sure no new enums are added
-        assertEquals(6, Value.values().length);
+        assertEquals(7, Value.values().length);
     }
 }
 
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_11.java b/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_11.java
index 6ad1748..a16059a 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_11.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_11.java
@@ -27,7 +27,16 @@
  * @hide
  */
 public class ScriptC_set_target_api_11 extends ScriptC {
+    private static final String __rs_resource_name = "set_target_api_11";
     // Constructor
+    public  ScriptC_set_target_api_11(RenderScript rs) {
+        this(rs,
+             rs.getApplicationContext().getResources(),
+             rs.getApplicationContext().getResources().getIdentifier(
+                 __rs_resource_name, "raw",
+                 rs.getApplicationContext().getPackageName()));
+    }
+
     public  ScriptC_set_target_api_11(RenderScript rs, Resources resources, int id) {
         super(rs, resources, id);
     }
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_12.java b/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_12.java
index dc772d5..5c31f94 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_12.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_12.java
@@ -27,7 +27,16 @@
  * @hide
  */
 public class ScriptC_set_target_api_12 extends ScriptC {
+    private static final String __rs_resource_name = "set_target_api_12";
     // Constructor
+    public  ScriptC_set_target_api_12(RenderScript rs) {
+        this(rs,
+             rs.getApplicationContext().getResources(),
+             rs.getApplicationContext().getResources().getIdentifier(
+                 __rs_resource_name, "raw",
+                 rs.getApplicationContext().getPackageName()));
+    }
+
     public  ScriptC_set_target_api_12(RenderScript rs, Resources resources, int id) {
         super(rs, resources, id);
     }
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_13.java b/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_13.java
index e1580e4..e12bf00 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_13.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_13.java
@@ -27,7 +27,16 @@
  * @hide
  */
 public class ScriptC_set_target_api_13 extends ScriptC {
+    private static final String __rs_resource_name = "set_target_api_13";
     // Constructor
+    public  ScriptC_set_target_api_13(RenderScript rs) {
+        this(rs,
+             rs.getApplicationContext().getResources(),
+             rs.getApplicationContext().getResources().getIdentifier(
+                 __rs_resource_name, "raw",
+                 rs.getApplicationContext().getPackageName()));
+    }
+
     public  ScriptC_set_target_api_13(RenderScript rs, Resources resources, int id) {
         super(rs, resources, id);
     }
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_14.java b/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_14.java
index 42d08c6..97879a5 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_14.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_14.java
@@ -27,7 +27,16 @@
  * @hide
  */
 public class ScriptC_set_target_api_14 extends ScriptC {
+    private static final String __rs_resource_name = "set_target_api_14";
     // Constructor
+    public  ScriptC_set_target_api_14(RenderScript rs) {
+        this(rs,
+             rs.getApplicationContext().getResources(),
+             rs.getApplicationContext().getResources().getIdentifier(
+                 __rs_resource_name, "raw",
+                 rs.getApplicationContext().getPackageName()));
+    }
+
     public  ScriptC_set_target_api_14(RenderScript rs, Resources resources, int id) {
         super(rs, resources, id);
     }
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_15.java b/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_15.java
index 6acfede..636a128 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_15.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_15.java
@@ -27,7 +27,16 @@
  * @hide
  */
 public class ScriptC_set_target_api_15 extends ScriptC {
+    private static final String __rs_resource_name = "set_target_api_15";
     // Constructor
+    public  ScriptC_set_target_api_15(RenderScript rs) {
+        this(rs,
+             rs.getApplicationContext().getResources(),
+             rs.getApplicationContext().getResources().getIdentifier(
+                 __rs_resource_name, "raw",
+                 rs.getApplicationContext().getPackageName()));
+    }
+
     public  ScriptC_set_target_api_15(RenderScript rs, Resources resources, int id) {
         super(rs, resources, id);
     }
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_16.java b/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_16.java
index f111005..435316f 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_16.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_16.java
@@ -27,7 +27,16 @@
  * @hide
  */
 public class ScriptC_set_target_api_16 extends ScriptC {
+    private static final String __rs_resource_name = "set_target_api_16";
     // Constructor
+    public  ScriptC_set_target_api_16(RenderScript rs) {
+        this(rs,
+             rs.getApplicationContext().getResources(),
+             rs.getApplicationContext().getResources().getIdentifier(
+                 __rs_resource_name, "raw",
+                 rs.getApplicationContext().getPackageName()));
+    }
+
     public  ScriptC_set_target_api_16(RenderScript rs, Resources resources, int id) {
         super(rs, resources, id);
     }
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_17.java b/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_17.java
new file mode 100644
index 0000000..c706ca6
--- /dev/null
+++ b/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_17.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2011-2012 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 is auto-generated. DO NOT MODIFY!
+ * The source Renderscript file: set_target_api_17.rs
+ */
+package android.renderscript.cts;
+
+import android.renderscript.*;
+import android.content.res.Resources;
+
+/**
+ * @hide
+ */
+public class ScriptC_set_target_api_17 extends ScriptC {
+    private static final String __rs_resource_name = "set_target_api_17";
+    // Constructor
+    public  ScriptC_set_target_api_17(RenderScript rs) {
+        this(rs,
+             rs.getApplicationContext().getResources(),
+             rs.getApplicationContext().getResources().getIdentifier(
+                 __rs_resource_name, "raw",
+                 rs.getApplicationContext().getPackageName()));
+    }
+
+    public  ScriptC_set_target_api_17(RenderScript rs, Resources resources, int id) {
+        super(rs, resources, id);
+    }
+
+    private final static int mExportFuncIdx_check = 0;
+    public void invoke_check(int version) {
+        FieldPacker check_fp = new FieldPacker(4);
+        check_fp.addI32(version);
+        invoke(mExportFuncIdx_check, check_fp);
+    }
+
+}
+
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_too_high.java b/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_too_high.java
new file mode 100644
index 0000000..f54ab46
--- /dev/null
+++ b/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_too_high.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2011-2012 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 is auto-generated. DO NOT MODIFY!
+ * The source Renderscript file: set_target_api_16.rs
+ */
+package android.renderscript.cts;
+
+import android.renderscript.*;
+import android.content.res.Resources;
+
+/**
+ * @hide
+ */
+public class ScriptC_set_target_api_too_high extends ScriptC {
+    private static final String __rs_resource_name = "set_target_api_too_high";
+    // Constructor
+    public  ScriptC_set_target_api_too_high(RenderScript rs) {
+        this(rs,
+             rs.getApplicationContext().getResources(),
+             rs.getApplicationContext().getResources().getIdentifier(
+                 __rs_resource_name, "raw",
+                 rs.getApplicationContext().getPackageName()));
+    }
+
+    public  ScriptC_set_target_api_too_high(RenderScript rs, Resources resources, int id) {
+        super(rs, resources, id);
+    }
+
+    private final static int mExportFuncIdx_check = 0;
+    public void invoke_check(int version) {
+        FieldPacker check_fp = new FieldPacker(4);
+        check_fp.addI32(version);
+        invoke(mExportFuncIdx_check, check_fp);
+    }
+
+}
+
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/ScriptGroupTest.java b/tests/tests/renderscript/src/android/renderscript/cts/ScriptGroupTest.java
index e73195c..64496ef 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/ScriptGroupTest.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/ScriptGroupTest.java
@@ -262,7 +262,6 @@
         compare.forEach_compare(out);
         compare.forEach_getCompareResult(resultAlloc);
         resultAlloc.copyTo(result);
-        Log.e("ARGH", "result = " + result[0]);
         assertTrue(result[0] == 2);
     }
 
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/VersionTest.java b/tests/tests/renderscript/src/android/renderscript/cts/VersionTest.java
index 6c9e7e9..c3966a9 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/VersionTest.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/VersionTest.java
@@ -17,6 +17,7 @@
 package android.renderscript.cts;
 
 import android.renderscript.RenderScript.RSMessageHandler;
+import android.renderscript.RSRuntimeException;
 import com.android.cts.stub.R;
 
 /**
@@ -82,4 +83,26 @@
         waitForMessage();
         checkForErrors();
     }
+
+    public void testVersion17() {
+        ScriptC_set_target_api_17 test17 =
+                new ScriptC_set_target_api_17(mRS,
+                                              mRes,
+                                              R.raw.set_target_api_17);
+        test17.invoke_check(17);
+        waitForMessage();
+        checkForErrors();
+    }
+
+    public void testVersion_too_high() {
+        try {
+            ScriptC_set_target_api_too_high test_too_high =
+                    new ScriptC_set_target_api_too_high(mRS,
+                                                        mRes,
+                                                        R.raw.set_target_api_too_high);
+            fail("should throw RSRuntimeException");
+        } catch (RSRuntimeException e) {
+        }
+        checkForErrors();
+    }
 }
diff --git a/tests/tests/security/src/android/security/cts/BannedFilesTest.java b/tests/tests/security/src/android/security/cts/BannedFilesTest.java
index ada9aaf..6ce8588 100644
--- a/tests/tests/security/src/android/security/cts/BannedFilesTest.java
+++ b/tests/tests/security/src/android/security/cts/BannedFilesTest.java
@@ -68,7 +68,7 @@
         if (!FileUtils.getFileStatus(file, fs, false)) {
             return;
         }
-        assertTrue((fs.mode & FileUtils.S_ISUID) == 0);
-        assertTrue((fs.mode & FileUtils.S_ISGID) == 0);
+        assertTrue("File \"" + file + "\" is setUID", (fs.mode & FileUtils.S_ISUID) == 0);
+        assertTrue("File \"" + file + "\" is setGID", (fs.mode & FileUtils.S_ISGID) == 0);
     }
 }
diff --git a/tests/tests/security/src/android/security/cts/CertificateData.java b/tests/tests/security/src/android/security/cts/CertificateData.java
index 1714461..1b039a5 100644
--- a/tests/tests/security/src/android/security/cts/CertificateData.java
+++ b/tests/tests/security/src/android/security/cts/CertificateData.java
@@ -40,6 +40,7 @@
       "40:54:DA:6F:1C:3F:40:74:AC:ED:0F:EC:CD:DB:79:D1:53:FB:90:1D",
       "43:F9:B1:10:D5:BA:FD:48:22:52:31:B0:D0:08:2B:37:2F:EF:9A:54",
       "F4:8B:11:BF:DE:AB:BE:94:54:20:71:E6:41:DE:6B:BE:88:2B:40:B9",
+      "58:E8:AB:B0:36:15:33:FB:80:F7:9B:1B:6D:29:D3:FF:8D:5F:00:F0",
       "96:56:CD:7B:57:96:98:95:D0:E1:41:46:68:06:FB:B8:C6:11:06:87",
       "55:A6:72:3E:CB:F2:EC:CD:C3:23:74:70:19:9D:2A:BE:11:E3:81:D1",
       "D6:9B:56:11:48:F0:1C:77:C5:45:78:C1:09:26:DF:5B:85:69:76:AD",
@@ -48,6 +49,7 @@
       "AD:7E:1C:28:B0:64:EF:8F:60:03:40:20:14:C3:D0:E3:37:0E:B5:8A",
       "8D:17:84:D5:37:F3:03:7D:EC:70:FE:57:8B:51:9A:99:E6:10:D7:B0",
       "AE:50:83:ED:7C:F4:5C:BC:8F:61:C6:21:FE:68:5D:79:42:21:15:6E",
+      "DA:FA:F7:FA:66:84:EC:06:8F:14:50:BD:C7:C2:81:A5:BC:A9:64:57",
       "5F:4E:1F:CF:31:B7:91:3B:85:0B:54:F6:E5:FF:50:1A:2B:6F:C6:CF",
       "74:F8:A3:C3:EF:E7:B3:90:06:4B:83:90:3C:21:64:60:20:E5:DF:CE",
       "85:B5:FF:67:9B:0C:79:96:1F:C8:6E:44:22:00:46:13:DB:17:92:84",
@@ -64,6 +66,7 @@
       "8C:F4:27:FD:79:0C:3A:D1:66:06:8D:E8:1E:57:EF:BB:93:22:72:D4",
       "56:E0:FA:C0:3B:8F:18:23:55:18:E5:D3:11:CA:E8:C2:43:31:AB:66",
       "02:72:68:29:3E:5F:5D:17:AA:A4:B3:C3:E6:36:1E:1F:92:57:5E:AA",
+      "2F:78:3D:25:52:18:A7:4A:65:39:71:B5:2C:A2:9C:45:15:6F:E9:19",
       "97:81:79:50:D8:1C:96:70:CC:34:D8:09:CF:79:44:31:36:7E:F4:74",
       "85:A4:08:C0:9C:19:3E:5D:51:58:7D:CD:D6:13:30:FD:8C:DE:37:BF",
       "58:11:9F:0E:12:82:87:EA:50:FD:D9:87:45:6F:4F:78:DC:FA:D6:D4",
@@ -98,6 +101,7 @@
       "69:BD:8C:F4:9C:D3:00:FB:59:2E:17:93:CA:55:6A:F3:EC:AA:35:FB",
       "13:2D:0D:45:53:4B:69:97:CD:B2:D5:C3:39:E2:55:76:60:9B:5C:C6",
       "5F:B7:EE:06:33:E2:59:DB:AD:0C:4C:9A:E6:D3:8F:1A:61:C7:DC:25",
+      "49:0A:75:74:DE:87:0A:47:FE:58:EE:F6:C7:6B:EB:C6:0B:12:40:99",
       "25:01:90:19:CF:FB:D9:99:1C:B7:68:25:74:8D:94:5F:30:93:95:42",
       "79:98:A3:08:E1:4D:65:85:E6:C2:1E:15:3A:71:9F:BA:5A:D3:4A:D9",
       "B5:1C:06:7C:EE:2B:0C:3D:F8:55:AB:2D:92:F4:FE:39:D4:E7:0F:0E",
@@ -142,6 +146,7 @@
       "21:FC:BD:8E:7F:6C:AF:05:1B:D1:B3:43:EC:A8:E7:61:47:F2:0F:8A",
       "5F:3B:8C:F2:F8:10:B3:7D:78:B4:CE:EC:19:19:C3:73:34:B9:C7:74",
       "2A:C8:D5:8B:57:CE:BF:2F:49:AF:F2:FC:76:8F:51:14:62:90:7A:41",
+      "96:C9:1B:0B:95:B4:10:98:42:FA:D0:D8:22:79:FE:60:FA:B9:16:83",
       "CA:BB:51:67:24:00:58:8E:64:19:F1:D4:08:78:D0:40:3A:A2:02:64",
       "5D:98:9C:DB:15:96:11:36:51:65:64:1B:56:0F:DB:EA:2A:C2:3E:F1",
       "D8:A6:33:2C:E0:03:6F:B1:85:F6:63:4F:7D:6A:06:65:26:32:28:27",
diff --git a/tests/tests/security/src/android/security/cts/KernelSettingsTest.java b/tests/tests/security/src/android/security/cts/KernelSettingsTest.java
index cc4b6a6..6daaffe 100644
--- a/tests/tests/security/src/android/security/cts/KernelSettingsTest.java
+++ b/tests/tests/security/src/android/security/cts/KernelSettingsTest.java
@@ -19,6 +19,7 @@
 import junit.framework.TestCase;
 
 import java.io.BufferedReader;
+import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.FileReader;
 import java.io.IOException;
@@ -86,6 +87,40 @@
         }
     }
 
+    /**
+     * Assert that support for loadable modules is not compiled into the
+     * kernel.
+     *
+     * Loadable modules are often used to implement rootkit like functionality.
+     * In addition, loadable modules enable support for /proc/sys/kernel/modprobe,
+     * which is commonly used by exploit writers to gain root access.
+     *
+     * Support for loadable modules can be removed by editing the Linux kernel
+     * config and removing the CONFIG_KMOD option.
+     */
+    public void testNoLoadableModules() throws IOException {
+        assertFalse(
+            "Support for loadable modules is compiled into the kernel. "
+                + "Loadable modules are often used by rootkits and other "
+                + "exploits and should be disabled. Please remove "
+                + "CONFIG_KMOD from your kernel config and compile "
+                + "all modules directly into the kernel.",
+            new File("/proc/sys/kernel/modprobe").exists());
+    }
+
+    /**
+     * Assert that the kernel config file is not compiled into the kernel.
+     *
+     * Compiling the config file into the kernel leaks the kernel base address
+     * via CONFIG_PHYS_OFFSET. It also wastes a small amount of valuable kernel memory.
+     */
+    public void testNoConfigGz() throws IOException {
+        assertFalse(
+                "/proc/config.gz is readable.  Please recompile your "
+                        + "kernel with CONFIG_IKCONFIG_PROC disabled",
+                new File("/proc/config.gz").exists());
+    }
+
     private String getFile(String filename) throws IOException {
         BufferedReader in = null;
         try {
diff --git a/tests/tests/text/src/android/text/bidi/cts/BidiFormatterTest.java b/tests/tests/text/src/android/text/bidi/cts/BidiFormatterTest.java
new file mode 100644
index 0000000..198a52f
--- /dev/null
+++ b/tests/tests/text/src/android/text/bidi/cts/BidiFormatterTest.java
@@ -0,0 +1,422 @@
+/*
+ * Copyright (C) 2013 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.text.bidi.cts;
+
+import android.test.AndroidTestCase;
+import android.text.TextDirectionHeuristics;
+import android.text.bidi.BidiFormatter;
+
+import java.util.Locale;
+
+public class BidiFormatterTest extends AndroidTestCase {
+
+    private static final BidiFormatter LTR_FMT = BidiFormatter.getInstance(false /* LTR context */);
+    private static final BidiFormatter RTL_FMT = BidiFormatter.getInstance(true /* RTL context */);
+
+    private static final BidiFormatter LTR_FMT_EXIT_RESET =
+            new BidiFormatter.Builder(false /* LTR context */).stereoReset(false).build();
+    private static final BidiFormatter RTL_FMT_EXIT_RESET =
+            new BidiFormatter.Builder(true /* RTL context */).stereoReset(false).build();
+
+    private static final String EN = "abba";
+    private static final String HE = "\u05e0\u05e1";
+
+    private static final String LRM = "\u200E";
+    private static final String RLM = "\u200F";
+    private static final String LRE = "\u202A";
+    private static final String RLE = "\u202B";
+    private static final String PDF = "\u202C";
+
+    private static final String LEFT = "left";
+    private static final String RIGHT = "right";
+
+
+    public void testIsRtlContext() {
+        assertEquals(false, LTR_FMT.isRtlContext());
+        assertEquals(true, RTL_FMT.isRtlContext());
+
+        assertEquals(false, BidiFormatter.getInstance(Locale.ENGLISH).isRtlContext());
+        assertEquals(true, BidiFormatter.getInstance(true).isRtlContext());
+    }
+
+    public void testBuilderIsRtlContext() {
+        assertEquals(false, new BidiFormatter.Builder(false).build().isRtlContext());
+        assertEquals(true, new BidiFormatter.Builder(true).build().isRtlContext());
+    }
+
+    public void testIsRtl() {
+        assertEquals(true, BidiFormatter.getInstance(true).isRtl(HE));
+        assertEquals(true, BidiFormatter.getInstance(false).isRtl(HE));
+
+        assertEquals(false, BidiFormatter.getInstance(true).isRtl(EN));
+        assertEquals(false, BidiFormatter.getInstance(false).isRtl(EN));
+    }
+
+    public void testDirAttrValue() {
+        assertEquals("ltr", LTR_FMT.dirAttrValue(EN));
+        assertEquals("ltr", RTL_FMT.dirAttrValue(EN));
+
+        assertEquals("rtl", LTR_FMT.dirAttrValue(HE));
+        assertEquals("rtl", RTL_FMT.dirAttrValue(HE));
+
+        assertEquals("ltr", LTR_FMT.dirAttrValue(EN, TextDirectionHeuristics.LTR));
+        assertEquals("rtl", LTR_FMT.dirAttrValue(EN, TextDirectionHeuristics.RTL));
+
+        assertEquals("ltr", RTL_FMT.dirAttrValue(EN, TextDirectionHeuristics.LTR));
+        assertEquals("rtl", RTL_FMT.dirAttrValue(EN, TextDirectionHeuristics.RTL));
+
+        assertEquals("ltr", LTR_FMT.dirAttrValue(HE, TextDirectionHeuristics.LTR));
+        assertEquals("rtl", LTR_FMT.dirAttrValue(HE, TextDirectionHeuristics.RTL));
+
+        assertEquals("ltr", RTL_FMT.dirAttrValue(HE, TextDirectionHeuristics.LTR));
+        assertEquals("rtl", RTL_FMT.dirAttrValue(HE, TextDirectionHeuristics.RTL));
+
+        assertEquals("ltr", LTR_FMT.dirAttrValue("", TextDirectionHeuristics.LTR));
+        assertEquals("rtl", LTR_FMT.dirAttrValue("", TextDirectionHeuristics.RTL));
+
+        assertEquals("ltr", RTL_FMT.dirAttrValue("", TextDirectionHeuristics.LTR));
+        assertEquals("rtl", RTL_FMT.dirAttrValue("", TextDirectionHeuristics.RTL));
+    }
+
+    public void testDirAttr() {
+        assertEquals("", LTR_FMT.dirAttr(EN));
+        assertEquals("dir=\"ltr\"", RTL_FMT.dirAttr(EN));
+
+        assertEquals("dir=\"rtl\"", LTR_FMT.dirAttr(HE));
+        assertEquals("", RTL_FMT.dirAttr(HE));
+
+        assertEquals("", LTR_FMT.dirAttr(".", TextDirectionHeuristics.LTR));
+        assertEquals("dir=\"ltr\"", RTL_FMT.dirAttr(".", TextDirectionHeuristics.LTR));
+
+        assertEquals("dir=\"rtl\"", LTR_FMT.dirAttr(".", TextDirectionHeuristics.RTL));
+        assertEquals("", RTL_FMT.dirAttr(".", TextDirectionHeuristics.RTL));
+    }
+
+    public void testMarkAfter() {
+        assertEquals("uniform dir matches LTR context",
+                "", LTR_FMT.markAfter(EN));
+        assertEquals("uniform dir matches RTL context",
+                "", RTL_FMT.markAfter(HE));
+
+        assertEquals("exit dir opposite to LTR context",
+                LRM, LTR_FMT.markAfter(EN + HE, TextDirectionHeuristics.LTR));
+        assertEquals("exit dir opposite to RTL context",
+                RLM, RTL_FMT.markAfter(HE + EN, TextDirectionHeuristics.RTL));
+
+        assertEquals("overall dir (but not exit dir) opposite to LTR context",
+                LRM, LTR_FMT.markAfter(HE + EN, TextDirectionHeuristics.RTL));
+        assertEquals("overall dir (but not exit dir) opposite to RTL context",
+                RLM, RTL_FMT.markAfter(EN + HE, TextDirectionHeuristics.LTR));
+
+        assertEquals("exit dir neutral, overall dir matches LTR context",
+                "", LTR_FMT.markAfter(".", TextDirectionHeuristics.LTR));
+        assertEquals("exit dir neutral, overall dir matches RTL context",
+                "", RTL_FMT.markAfter(".", TextDirectionHeuristics.RTL));
+    }
+
+    public void testMarkBefore() {
+        assertEquals("uniform dir matches LTR context",
+                "", LTR_FMT.markBefore(EN));
+        assertEquals("uniform dir matches RTL context",
+                "", RTL_FMT.markBefore(HE));
+
+        assertEquals("entry dir opposite to LTR context",
+                LRM, LTR_FMT.markBefore(HE + EN, TextDirectionHeuristics.LTR));
+        assertEquals("entry dir opposite to RTL context",
+                RLM, RTL_FMT.markBefore(EN + HE, TextDirectionHeuristics.RTL));
+
+        assertEquals("overall dir (but not entry dir) opposite to LTR context",
+                LRM, LTR_FMT.markBefore(EN + HE, TextDirectionHeuristics.RTL));
+        assertEquals("overall dir (but not entry dir) opposite to RTL context",
+                RLM, RTL_FMT.markBefore(HE + EN, TextDirectionHeuristics.LTR));
+
+        assertEquals("exit dir neutral, overall dir matches LTR context",
+                "", LTR_FMT.markBefore(".", TextDirectionHeuristics.LTR));
+        assertEquals("exit dir neutral, overall dir matches RTL context",
+                "", RTL_FMT.markBefore(".", TextDirectionHeuristics.RTL));
+    }
+
+
+    public void testMark() {
+        assertEquals(LRM, LTR_FMT.mark());
+        assertEquals(RLM, RTL_FMT.mark());
+    }
+
+    public void testStartEdge() {
+        assertEquals(LEFT, LTR_FMT.startEdge());
+        assertEquals(RIGHT, RTL_FMT.startEdge());
+    }
+
+    public void testEndEdge() {
+        assertEquals(RIGHT, LTR_FMT.endEdge());
+        assertEquals(LEFT, RTL_FMT.endEdge());
+    }
+
+    public void testUnicodeWrap() {
+        // Uniform directionality in opposite context.
+        assertEquals("uniform dir opposite to LTR context",
+                RLE + "." + HE + "." + PDF + LRM,
+                LTR_FMT_EXIT_RESET.unicodeWrap("." + HE + "."));
+        assertEquals("uniform dir opposite to LTR context, stereo reset",
+                LRM + RLE + "." + HE + "." + PDF + LRM,
+                LTR_FMT.unicodeWrap("." + HE + "."));
+        assertEquals("uniform dir opposite to LTR context, stereo reset, no isolation",
+                RLE + "." + HE + "." + PDF,
+                LTR_FMT.unicodeWrap("." + HE + ".", false));
+        assertEquals("neutral treated as opposite to LTR context",
+                RLE + "." + PDF + LRM,
+                LTR_FMT_EXIT_RESET.unicodeWrap(".", TextDirectionHeuristics.RTL));
+        assertEquals("uniform dir opposite to RTL context",
+                LRE + "." + EN + "." + PDF + RLM,
+                RTL_FMT_EXIT_RESET.unicodeWrap("." + EN + "."));
+        assertEquals("uniform dir opposite to RTL context, stereo reset",
+                RLM + LRE + "." + EN + "." + PDF + RLM,
+                RTL_FMT.unicodeWrap("." + EN + "."));
+        assertEquals("uniform dir opposite to RTL context, stereo reset, no isolation",
+                LRE + "." + EN + "." + PDF,
+                RTL_FMT.unicodeWrap("." + EN + ".", false));
+        assertEquals("neutral treated as opposite to RTL context",
+                LRE + "." + PDF + RLM,
+                RTL_FMT_EXIT_RESET.unicodeWrap(".", TextDirectionHeuristics.LTR));
+
+        // We test mixed-directionality cases only with an explicit overall directionality parameter
+        // because the estimation logic is outside the sphere of BidiFormatter, and different
+        // estimators will treat them differently.
+
+        // Overall directionality matching context, but with opposite exit directionality.
+        assertEquals("exit dir opposite to LTR context",
+                EN + HE + LRM,
+                LTR_FMT_EXIT_RESET.unicodeWrap(EN + HE, TextDirectionHeuristics.LTR));
+        assertEquals("exit dir opposite to LTR context, stereo reset",
+                EN + HE + LRM,
+                LTR_FMT.unicodeWrap(EN + HE, TextDirectionHeuristics.LTR));
+        assertEquals("exit dir opposite to LTR context, stereo reset, no isolation",
+                EN + HE,
+                LTR_FMT.unicodeWrap(EN + HE, TextDirectionHeuristics.LTR, false));
+
+        assertEquals("exit dir opposite to RTL context",
+                HE + EN + RLM,
+                RTL_FMT_EXIT_RESET.unicodeWrap(HE + EN, TextDirectionHeuristics.RTL));
+        assertEquals("exit dir opposite to RTL context, stereo reset",
+                HE + EN + RLM,
+                RTL_FMT.unicodeWrap(HE + EN, TextDirectionHeuristics.RTL));
+        assertEquals("exit dir opposite to RTL context, stereo reset, no isolation",
+                HE + EN,
+                RTL_FMT.unicodeWrap(HE + EN, TextDirectionHeuristics.RTL, false));
+
+        // Overall directionality matching context, but with opposite entry directionality.
+        assertEquals("entry dir opposite to LTR context",
+                HE + EN,
+                LTR_FMT_EXIT_RESET.unicodeWrap(HE + EN, TextDirectionHeuristics.LTR));
+        assertEquals("entry dir opposite to LTR context, stereo reset",
+                LRM + HE + EN,
+                LTR_FMT.unicodeWrap(HE + EN, TextDirectionHeuristics.LTR));
+        assertEquals("entry dir opposite to LTR context, stereo reset, no isolation",
+                HE + EN,
+                LTR_FMT.unicodeWrap(HE + EN, TextDirectionHeuristics.LTR, false));
+
+        assertEquals("entry dir opposite to RTL context",
+                EN + HE,
+                RTL_FMT_EXIT_RESET.unicodeWrap(EN + HE, TextDirectionHeuristics.RTL));
+        assertEquals("entry dir opposite to RTL context, stereo reset",
+                RLM + EN + HE,
+                RTL_FMT.unicodeWrap(EN + HE, TextDirectionHeuristics.RTL));
+        assertEquals("entry dir opposite to RTL context, stereo reset, no isolation",
+                EN + HE,
+                RTL_FMT.unicodeWrap(EN + HE, TextDirectionHeuristics.RTL, false));
+
+        // Overall directionality matching context, but with opposite entry and exit directionality.
+        assertEquals("entry and exit dir opposite to LTR context",
+                HE + EN + HE + LRM,
+                LTR_FMT_EXIT_RESET.unicodeWrap(HE + EN + HE, TextDirectionHeuristics.LTR));
+        assertEquals("entry and exit dir opposite to LTR context, stereo reset",
+                LRM + HE + EN + HE + LRM,
+                LTR_FMT.unicodeWrap(HE + EN + HE, TextDirectionHeuristics.LTR));
+        assertEquals("entry and exit dir opposite to LTR context, no isolation",
+                HE + EN + HE,
+                LTR_FMT_EXIT_RESET.unicodeWrap(HE + EN + HE, TextDirectionHeuristics.LTR, false));
+
+        assertEquals("entry and exit dir opposite to RTL context",
+                EN + HE + EN + RLM,
+                RTL_FMT_EXIT_RESET.unicodeWrap(EN + HE + EN, TextDirectionHeuristics.RTL));
+        assertEquals("entry and exit dir opposite to RTL context, no isolation",
+                EN + HE + EN,
+                RTL_FMT_EXIT_RESET.unicodeWrap(EN + HE + EN, TextDirectionHeuristics.RTL, false));
+
+        // Entry and exit directionality matching context, but with opposite overall directionality.
+        assertEquals("overall dir (but not entry or exit dir) opposite to LTR context",
+                RLE + EN + HE + EN + PDF + LRM,
+                LTR_FMT_EXIT_RESET.unicodeWrap(EN + HE + EN, TextDirectionHeuristics.RTL));
+        assertEquals("overall dir (but not entry or exit dir) opposite to LTR context, stereo reset",
+                LRM + RLE + EN + HE + EN + PDF + LRM,
+                LTR_FMT.unicodeWrap(EN + HE + EN, TextDirectionHeuristics.RTL));
+        assertEquals("overall dir (but not entry or exit dir) opposite to LTR context, no isolation",
+                RLE + EN + HE + EN + PDF,
+                LTR_FMT_EXIT_RESET.unicodeWrap(EN + HE + EN, TextDirectionHeuristics.RTL, false));
+
+        assertEquals("overall dir (but not entry or exit dir) opposite to RTL context",
+                LRE + HE + EN + HE + PDF + RLM,
+                RTL_FMT_EXIT_RESET.unicodeWrap(HE + EN + HE, TextDirectionHeuristics.LTR));
+        assertEquals("overall dir (but not entry or exit dir) opposite to RTL context, stereo reset",
+                RLM + LRE + HE + EN + HE + PDF + RLM,
+                RTL_FMT.unicodeWrap(HE + EN + HE, TextDirectionHeuristics.LTR));
+        assertEquals("overall dir (but not entry or exit dir) opposite to RTL context, no isolation",
+                LRE + HE + EN + HE + PDF,
+                RTL_FMT_EXIT_RESET.unicodeWrap(HE + EN + HE, TextDirectionHeuristics.LTR, false));
+    }
+
+
+    public void testSpanWrap() {
+        // Uniform directionality in matching context.
+        assertEquals("uniform dir matches LTR context",
+                "&amp; " + EN + "&lt;", LTR_FMT.spanWrap("& " + EN + "<"));
+        assertEquals("neutral treated as matching LTR context",
+                ".", LTR_FMT.spanWrap(".", TextDirectionHeuristics.LTR));
+        assertEquals("uniform dir matches RTL context",
+                "&amp; " + HE + "&lt;", RTL_FMT.spanWrap("& " + HE + "<"));
+        assertEquals("neutral treated as matching RTL context",
+                ".", RTL_FMT.spanWrap(".", TextDirectionHeuristics.RTL));
+
+        // Uniform directionality in opposite context.
+        assertEquals("uniform dir opposite to LTR context",
+                "<span dir=\"rtl\">." + HE + ".</span>" + LRM,
+                LTR_FMT_EXIT_RESET.spanWrap("." + HE + "."));
+        assertEquals("uniform dir opposite to LTR context, stereo reset",
+                LRM + "<span dir=\"rtl\">." + HE + ".</span>" + LRM,
+                LTR_FMT.spanWrap("." + HE + "."));
+        assertEquals("uniform dir opposite to LTR context, no isolation",
+                "<span dir=\"rtl\">." + HE + ".</span>",
+                LTR_FMT_EXIT_RESET.spanWrap("." + HE + ".", false));
+        assertEquals("uniform dir opposite to LTR context, stereo reset, no isolation",
+                "<span dir=\"rtl\">." + HE + ".</span>",
+                LTR_FMT.spanWrap("." + HE + ".", false));
+        assertEquals("neutral treated as opposite to LTR context",
+                "<span dir=\"rtl\">" + "." + "</span>" + LRM,
+                LTR_FMT_EXIT_RESET.spanWrap(".", TextDirectionHeuristics.RTL));
+        assertEquals("uniform dir opposite to RTL context",
+                "<span dir=\"ltr\">." + EN + ".</span>" + RLM,
+                RTL_FMT_EXIT_RESET.spanWrap("." + EN + "."));
+        assertEquals("uniform dir opposite to RTL context, stereo reset",
+                RLM + "<span dir=\"ltr\">." + EN + ".</span>" + RLM,
+                RTL_FMT.spanWrap("." + EN + "."));
+        assertEquals("uniform dir opposite to RTL context, no isolation",
+                "<span dir=\"ltr\">." + EN + ".</span>",
+                RTL_FMT_EXIT_RESET.spanWrap("." + EN + ".", false));
+        assertEquals("uniform dir opposite to RTL context, stereo reset, no isolation",
+                "<span dir=\"ltr\">." + EN + ".</span>",
+                RTL_FMT.spanWrap("." + EN + ".", false));
+        assertEquals("neutral treated as opposite to RTL context",
+                "<span dir=\"ltr\">" + "." + "</span>" + RLM,
+                RTL_FMT_EXIT_RESET.spanWrap(".", TextDirectionHeuristics.LTR));
+
+        // We test mixed-directionality cases only with an explicit overall directionality parameter
+        // because the estimation logic is outside the sphere of BidiFormatter, and different
+        // estimators will treat them differently.
+
+        // Overall directionality matching context, but with opposite exit directionality.
+        assertEquals("exit dir opposite to LTR context",
+                EN + HE + LRM,
+                LTR_FMT_EXIT_RESET.spanWrap(EN + HE, TextDirectionHeuristics.LTR));
+        assertEquals("exit dir opposite to LTR context, stereo reset",
+                EN + HE + LRM,
+                LTR_FMT.spanWrap(EN + HE, TextDirectionHeuristics.LTR));
+        assertEquals("exit dir opposite to LTR context, no isolation",
+                EN + HE,
+                LTR_FMT_EXIT_RESET.spanWrap(EN + HE, TextDirectionHeuristics.LTR, false));
+        assertEquals("exit dir opposite to LTR context, stereo reset, no isolation",
+                EN + HE,
+                LTR_FMT.spanWrap(EN + HE, TextDirectionHeuristics.LTR, false));
+        assertEquals("exit dir opposite to RTL context",
+                HE + EN + RLM,
+                RTL_FMT_EXIT_RESET.spanWrap(HE + EN, TextDirectionHeuristics.RTL));
+        assertEquals("exit dir opposite to RTL context, stereo reset",
+                HE + EN + RLM,
+                RTL_FMT.spanWrap(HE + EN, TextDirectionHeuristics.RTL));
+        assertEquals("exit dir opposite to RTL context, no isolation",
+                HE + EN,
+                RTL_FMT_EXIT_RESET.spanWrap(HE + EN, TextDirectionHeuristics.RTL, false));
+        assertEquals("exit dir opposite to RTL context, stereo reset, no isolation",
+                HE + EN,
+                RTL_FMT.spanWrap( HE + EN, TextDirectionHeuristics.RTL, false));
+
+        // Overall directionality matching context, but with opposite entry directionality.
+        assertEquals("entry dir opposite to LTR context",
+                HE + EN,
+                LTR_FMT_EXIT_RESET.spanWrap(HE + EN, TextDirectionHeuristics.LTR));
+        assertEquals("entry dir opposite to LTR context, stereo reset",
+                LRM + HE + EN,
+                LTR_FMT.spanWrap(HE + EN, TextDirectionHeuristics.LTR));
+        assertEquals("entry dir opposite to LTR context, no isolation",
+                HE + EN,
+                LTR_FMT_EXIT_RESET.spanWrap(HE + EN, TextDirectionHeuristics.LTR, false));
+        assertEquals("entry dir opposite to LTR context, stereo reset, no isolation",
+                HE + EN,
+                LTR_FMT.spanWrap(HE + EN, TextDirectionHeuristics.LTR, false));
+        assertEquals("entry dir opposite to RTL context",
+                EN + HE,
+                RTL_FMT_EXIT_RESET.spanWrap(EN + HE, TextDirectionHeuristics.RTL));
+        assertEquals("entry dir opposite to RTL context, stereo reset",
+                RLM + EN + HE,
+                RTL_FMT.spanWrap(EN + HE, TextDirectionHeuristics.RTL));
+        assertEquals("entry dir opposite to RTL context, no isolation",
+                EN + HE,
+                RTL_FMT_EXIT_RESET.spanWrap(EN + HE, TextDirectionHeuristics.RTL, false));
+        assertEquals("entry dir opposite to RTL context, stereo reset, no isolation",
+                EN + HE,
+                RTL_FMT.spanWrap(EN + HE, TextDirectionHeuristics.RTL, false));
+
+        // Overall directionality matching context, but with opposite entry and exit directionality.
+        assertEquals("entry and exit dir opposite to LTR context",
+                HE + EN + HE + LRM,
+                LTR_FMT_EXIT_RESET.spanWrap(HE + EN + HE, TextDirectionHeuristics.LTR));
+        assertEquals("entry and exit dir opposite to LTR context, stereo reset",
+                LRM + HE + EN + HE + LRM,
+                LTR_FMT.spanWrap(HE + EN + HE, TextDirectionHeuristics.LTR));
+        assertEquals("entry and exit dir opposite to LTR context, no isolation",
+                HE + EN + HE,
+                LTR_FMT_EXIT_RESET.spanWrap(HE + EN + HE, TextDirectionHeuristics.LTR, false));
+        assertEquals("entry and exit dir opposite to RTL context",
+                EN + HE + EN + RLM,
+                RTL_FMT_EXIT_RESET.spanWrap(EN + HE + EN, TextDirectionHeuristics.RTL));
+        assertEquals("entry and exit dir opposite to RTL context, stereo reset",
+                RLM + EN + HE + EN + RLM,
+                RTL_FMT.spanWrap(EN + HE + EN, TextDirectionHeuristics.RTL));
+        assertEquals("entry and exit dir opposite to RTL context, no isolation",
+                EN + HE + EN,
+                RTL_FMT_EXIT_RESET.spanWrap(EN + HE + EN, TextDirectionHeuristics.RTL, false));
+
+        // Entry and exit directionality matching context, but with opposite overall directionality.
+        assertEquals("overall dir (but not entry or exit dir) opposite to LTR context",
+                "<span dir=\"rtl\">" + EN + HE + EN + "</span>" + LRM,
+                LTR_FMT_EXIT_RESET.spanWrap(EN + HE + EN, TextDirectionHeuristics.RTL));
+        assertEquals("overall dir (but not entry or exit dir) opposite to LTR context, stereo reset",
+                LRM + "<span dir=\"rtl\">" + EN + HE + EN + "</span>" + LRM,
+                LTR_FMT.spanWrap(EN + HE + EN, TextDirectionHeuristics.RTL));
+        assertEquals("overall dir (but not entry or exit dir) opposite to LTR context, no isolation",
+                "<span dir=\"rtl\">" + EN + HE + EN + "</span>",
+                LTR_FMT_EXIT_RESET.spanWrap(EN + HE + EN, TextDirectionHeuristics.RTL, false));
+        assertEquals("overall dir (but not entry or exit dir) opposite to RTL context",
+                "<span dir=\"ltr\">" + HE + EN + HE + "</span>" + RLM,
+                RTL_FMT_EXIT_RESET.spanWrap(HE + EN + HE, TextDirectionHeuristics.LTR));
+        assertEquals("overall dir (but not entry or exit dir) opposite to RTL context, stereo reset",
+                RLM + "<span dir=\"ltr\">" + HE + EN + HE + "</span>" + RLM,
+                RTL_FMT.spanWrap(HE + EN + HE, TextDirectionHeuristics.LTR));
+        assertEquals("overall dir (but not entry or exit dir) opposite to RTL context, no isolation",
+                "<span dir=\"ltr\">" + HE + EN + HE + "</span>",
+                RTL_FMT_EXIT_RESET.spanWrap(HE + EN + HE, TextDirectionHeuristics.LTR, false));
+    }
+}
diff --git a/tests/tests/text/src/android/text/cts/EmojiConstants.java b/tests/tests/text/src/android/text/cts/EmojiConstants.java
new file mode 100755
index 0000000..dcae622
--- /dev/null
+++ b/tests/tests/text/src/android/text/cts/EmojiConstants.java
@@ -0,0 +1,759 @@
+/*
+ * Copyright (C) 2013 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.text.cts;
+
+/**
+ * List of Emoji tested by EmojiTest
+ */
+class EmojiConstants {
+
+    /*
+     * Emoji list from Unicode.org:
+     * http://unicode.org/cldr/utility/list-unicodeset.jsp?a=%5Cp%7BEmoji%7D%0D%0A&g=
+     */
+    static int[] emojiCodePoints = {
+        0x000A9, // COPYRIGHT SIGN
+        0x000AE, // REGISTERED SIGN
+        0x02002, // EN SPACE
+        0x02003, // EM SPACE
+        0x02005, // FOUR-PER-EM SPACE
+        0x0203C, // DOUBLE EXCLAMATION MARK
+        0x02049, // EXCLAMATION QUESTION MARK
+        0x020E3, // COMBINING ENCLOSING KEYCAP
+        0x02122, // TRADE MARK SIGN
+        0x02139, // INFORMATION SOURCE
+        0x02194, // LEFT RIGHT ARROW
+        0x02195, // UP DOWN ARROW
+        0x02196, // NORTH WEST ARROW
+        0x02197, // NORTH EAST ARROW
+        0x02198, // SOUTH EAST ARROW
+        0x02199, // SOUTH WEST ARROW
+        0x021A9, // LEFTWARDS ARROW WITH HOOK
+        0x021AA, // RIGHTWARDS ARROW WITH HOOK
+        0x0231A, // WATCH
+        0x0231B, // HOURGLASS
+        0x023E9, // BLACK RIGHT-POINTING DOUBLE TRIANGLE
+        0x023EA, // BLACK LEFT-POINTING DOUBLE TRIANGLE
+        0x023EB, // BLACK UP-POINTING DOUBLE TRIANGLE
+        0x023EC, // BLACK DOWN-POINTING DOUBLE TRIANGLE
+        0x023F0, // ALARM CLOCK
+        0x023F3, // HOURGLASS WITH FLOWING SAND
+        0x024C2, // CIRCLED LATIN CAPITAL LETTER M
+        0x025AA, // BLACK SMALL SQUARE
+        0x025AB, // WHITE SMALL SQUARE
+        0x025B6, // BLACK RIGHT-POINTING TRIANGLE
+        0x025C0, // BLACK LEFT-POINTING TRIANGLE
+        0x025FB, // WHITE MEDIUM SQUARE
+        0x025FC, // BLACK MEDIUM SQUARE
+        0x025FD, // WHITE MEDIUM SMALL SQUARE
+        0x025FE, // BLACK MEDIUM SMALL SQUARE
+        0x02600, // BLACK SUN WITH RAYS
+        0x02601, // CLOUD
+        0x0260E, // BLACK TELEPHONE
+        0x02611, // BALLOT BOX WITH CHECK
+        0x02615, // HOT BEVERAGE
+        0x02668, // HOT SPRINGS
+        0x0267F, // WHEELCHAIR SYMBOL
+        0x026A0, // WARNING SIGN
+        0x026A1, // HIGH VOLTAGE SIGN
+        0x02614, // UMBRELLA WITH RAIN DROPS
+        0x0261D, // WHITE UP POINTING INDEX
+        0x0263A, // WHITE SMILING FACE
+        0x02648, // ARIES
+        0x02649, // TAURUS
+        0x0264A, // GEMINI
+        0x0264B, // CANCER
+        0x0264C, // LEO
+        0x0264D, // VIRGO
+        0x0264E, // LIBRA
+        0x0264F, // SCORPIUS
+        0x02650, // SAGITTARIUS
+        0x02651, // CAPRICORN
+        0x02652, // AQUARIUS
+        0x02653, // PISCES
+        0x026CE, // OPHIUCHUS
+        0x02660, // BLACK SPADE SUIT
+        0x02663, // BLACK CLUB SUIT
+        0x02665, // BLACK HEART SUIT
+        0x02666, // BLACK DIAMOND SUIT
+        0x0267B, // BLACK UNIVERSAL RECYCLING SYMBOL
+        0x02693, // ANCHOR
+        0x026AA, // MEDIUM WHITE CIRCLE
+        0x026AB, // MEDIUM BLACK CIRCLE
+        0x026BD, // SOCCER BALL
+        0x026BE, // BASEBALL
+        0x026C4, // SNOWMAN WITHOUT SNOW
+        0x026C5, // SUN BEHIND CLOUD
+        0x026D4, // NO ENTRY
+        0x026EA, // CHURCH
+        0x026F2, // FOUNTAIN
+        0x026F3, // FLAG IN HOLE
+        0x026F5, // SAILBOAT
+        0x026FA, // TENT
+        0x026FD, // FUEL PUMP
+        0x02702, // BLACK SCISSORS
+        0x02705, // WHITE HEAVY CHECK MARK
+        0x02708, // AIRPLANE
+        0x02709, // ENVELOPE
+        0x0270A, // RAISED FIST
+        0x0270B, // RAISED HAND
+        0x0270C, // VICTORY HAND
+        0x0270F, // PENCIL
+        0x02712, // BLACK NIB
+        0x02714, // HEAVY CHECK MARK
+        0x02716, // HEAVY MULTIPLICATION X
+        0x0274C, // CROSS MARK
+        0x0274E, // NEGATIVE SQUARED CROSS MARK
+        0x02753, // BLACK QUESTION MARK ORNAMENT
+        0x02754, // WHITE QUESTION MARK ORNAMENT
+        0x02755, // WHITE EXCLAMATION MARK ORNAMENT
+        0x02757, // HEAVY EXCLAMATION MARK SYMBOL
+        0x027B0, // CURLY LOOP
+        0x027BF, // DOUBLE CURLY LOOP
+        0x02728, // SPARKLES
+        0x02733, // EIGHT SPOKED ASTERISK
+        0x02734, // EIGHT POINTED BLACK STAR
+        0x02744, // SNOWFLAKE
+        0x02747, // SPARKLE
+        0x02764, // HEAVY BLACK HEART
+        0x02795, // HEAVY PLUS SIGN
+        0x02796, // HEAVY MINUS SIGN
+        0x02797, // HEAVY DIVISION SIGN
+        0x027A1, // BLACK RIGHTWARDS ARROW
+        0x02934, // ARROW POINTING RIGHTWARDS THEN CURVING UPWARDS
+        0x02935, // ARROW POINTING RIGHTWARDS THEN CURVING DOWNWARDS
+        0x02B05, // LEFTWARDS BLACK ARROW
+        0x02B06, // UPWARDS BLACK ARROW
+        0x02B07, // DOWNWARDS BLACK ARROW
+        0x02B1B, // BLACK LARGE SQUARE
+        0x02B1C, // WHITE LARGE SQUARE
+        0x02B50, // WHITE MEDIUM STAR
+        0x02B55, // HEAVY LARGE CIRCLE
+        0x03030, // WAVY DASH
+        0x0303D, // PART ALTERNATION MARK
+        0x03297, // CIRCLED IDEOGRAPH CONGRATULATION
+        0x03299, // CIRCLED IDEOGRAPH SECRET
+        0x1F004, // MAHJONG TILE RED DRAGON
+        0x1F0CF, // PLAYING CARD BLACK JOKER
+        0x1F170, // NEGATIVE SQUARED LATIN CAPITAL LETTER A
+        0x1F171, // NEGATIVE SQUARED LATIN CAPITAL LETTER B
+        0x1F17E, // NEGATIVE SQUARED LATIN CAPITAL LETTER O
+        0x1F17F, // NEGATIVE SQUARED LATIN CAPITAL LETTER P
+        0x1F18E, // NEGATIVE SQUARED AB
+        0x1F191, // SQUARED CL
+        0x1F192, // SQUARED COOL
+        0x1F193, // SQUARED FREE
+        0x1F194, // SQUARED ID
+        0x1F195, // SQUARED NEW
+        0x1F196, // SQUARED NG
+        0x1F197, // SQUARED OK
+        0x1F198, // SQUARED SOS
+        0x1F199, // SQUARED UP WITH EXCLAMATION MARK
+        0x1F19A, // SQUARED VS
+        0x1F1E6, // REGIONAL INDICATOR SYMBOL LETTER A
+        0x1F1E7, // REGIONAL INDICATOR SYMBOL LETTER B
+        0x1F1E8, // REGIONAL INDICATOR SYMBOL LETTER C
+        0x1F1E9, // REGIONAL INDICATOR SYMBOL LETTER D
+        0x1F1EA, // REGIONAL INDICATOR SYMBOL LETTER E
+        0x1F1EB, // REGIONAL INDICATOR SYMBOL LETTER F
+        0x1F1EC, // REGIONAL INDICATOR SYMBOL LETTER G
+        0x1F1ED, // REGIONAL INDICATOR SYMBOL LETTER H
+        0x1F1EE, // REGIONAL INDICATOR SYMBOL LETTER I
+        0x1F1EF, // REGIONAL INDICATOR SYMBOL LETTER J
+        0x1F1F0, // REGIONAL INDICATOR SYMBOL LETTER K
+        0x1F1F1, // REGIONAL INDICATOR SYMBOL LETTER L
+        0x1F1F2, // REGIONAL INDICATOR SYMBOL LETTER M
+        0x1F1F3, // REGIONAL INDICATOR SYMBOL LETTER N
+        0x1F1F4, // REGIONAL INDICATOR SYMBOL LETTER O
+        0x1F1F5, // REGIONAL INDICATOR SYMBOL LETTER P
+        0x1F1F6, // REGIONAL INDICATOR SYMBOL LETTER Q
+        0x1F1F7, // REGIONAL INDICATOR SYMBOL LETTER R
+        0x1F1F8, // REGIONAL INDICATOR SYMBOL LETTER S
+        0x1F1F9, // REGIONAL INDICATOR SYMBOL LETTER T
+        0x1F1FA, // REGIONAL INDICATOR SYMBOL LETTER U
+        0x1F1FB, // REGIONAL INDICATOR SYMBOL LETTER V
+        0x1F1FC, // REGIONAL INDICATOR SYMBOL LETTER W
+        0x1F1FD, // REGIONAL INDICATOR SYMBOL LETTER X
+        0x1F1FE, // REGIONAL INDICATOR SYMBOL LETTER Y
+        0x1F1FF, // REGIONAL INDICATOR SYMBOL LETTER Z
+        0x1F201, // SQUARED KATAKANA KOKO
+        0x1F202, // SQUARED KATAKANA SA
+        0x1F21A, // SQUARED CJK UNIFIED IDEOGRAPH-7121
+        0x1F22F, // SQUARED CJK UNIFIED IDEOGRAPH-6307
+        0x1F232, // SQUARED CJK UNIFIED IDEOGRAPH-7981
+        0x1F233, // SQUARED CJK UNIFIED IDEOGRAPH-7A7A
+        0x1F234, // SQUARED CJK UNIFIED IDEOGRAPH-5408
+        0x1F235, // SQUARED CJK UNIFIED IDEOGRAPH-6E80
+        0x1F236, // SQUARED CJK UNIFIED IDEOGRAPH-6709
+        0x1F237, // SQUARED CJK UNIFIED IDEOGRAPH-6708
+        0x1F238, // SQUARED CJK UNIFIED IDEOGRAPH-7533
+        0x1F239, // SQUARED CJK UNIFIED IDEOGRAPH-5272
+        0x1F23A, // SQUARED CJK UNIFIED IDEOGRAPH-55B6
+        0x1F250, // CIRCLED IDEOGRAPH ADVANTAGE
+        0x1F251, // CIRCLED IDEOGRAPH ACCEPT
+        0x1F300, // CYCLONE
+        0x1F301, // FOGGY
+        0x1F302, // CLOSED UMBRELLA
+        0x1F303, // NIGHT WITH STARS
+        0x1F304, // SUNRISE OVER MOUNTAINS
+        0x1F305, // SUNRISE
+        0x1F306, // CITYSCAPE AT DUSK
+        0x1F307, // SUNSET OVER BUILDINGS
+        0x1F308, // RAINBOW
+        0x1F309, // BRIDGE AT NIGHT
+        0x1F30A, // WATER WAVE
+        0x1F30B, // VOLCANO
+        0x1F30C, // MILKY WAY
+        0x1F30F, // EARTH GLOBE ASIA-AUSTRALIA
+        0x1F311, // NEW MOON SYMBOL
+        0x1F313, // FIRST QUARTER MOON SYMBOL
+        0x1F314, // WAXING GIBBOUS MOON SYMBOL
+        0x1F315, // FULL MOON SYMBOL
+        0x1F319, // CRESCENT MOON
+        0x1F31B, // FIRST QUARTER MOON WITH FACE
+        0x1F31F, // GLOWING STAR
+        0x1F320, // SHOOTING STAR
+        0x1F330, // CHESTNUT
+        0x1F331, // SEEDLING
+        0x1F334, // PALM TREE
+        0x1F335, // CACTUS
+        0x1F337, // TULIP
+        0x1F338, // CHERRY BLOSSOM
+        0x1F339, // ROSE
+        0x1F33A, // HIBISCUS
+        0x1F33B, // SUNFLOWER
+        0x1F33C, // BLOSSOM
+        0x1F33D, // EAR OF MAIZE
+        0x1F33E, // EAR OF RICE
+        0x1F33F, // HERB
+        0x1F340, // FOUR LEAF CLOVER
+        0x1F341, // MAPLE LEAF
+        0x1F342, // FALLEN LEAF
+        0x1F343, // LEAF FLUTTERING IN WIND
+        0x1F344, // MUSHROOM
+        0x1F345, // TOMATO
+        0x1F346, // AUBERGINE
+        0x1F347, // GRAPES
+        0x1F348, // MELON
+        0x1F349, // WATERMELON
+        0x1F34A, // TANGERINE
+        0x1F34C, // BANANA
+        0x1F34D, // PINEAPPLE
+        0x1F34E, // RED APPLE
+        0x1F34F, // GREEN APPLE
+        0x1F351, // PEACH
+        0x1F352, // CHERRIES
+        0x1F353, // STRAWBERRY
+        0x1F354, // HAMBURGER
+        0x1F355, // SLICE OF PIZZA
+        0x1F356, // MEAT ON BONE
+        0x1F357, // POULTRY LEG
+        0x1F358, // RICE CRACKER
+        0x1F359, // RICE BALL
+        0x1F35A, // COOKED RICE
+        0x1F35B, // CURRY AND RICE
+        0x1F35C, // STEAMING BOWL
+        0x1F35D, // SPAGHETTI
+        0x1F35E, // BREAD
+        0x1F35F, // FRENCH FRIES
+        0x1F360, // ROASTED SWEET POTATO
+        0x1F361, // DANGO
+        0x1F362, // ODEN
+        0x1F363, // SUSHI
+        0x1F364, // FRIED SHRIMP
+        0x1F365, // FISH CAKE WITH SWIRL DESIGN
+        0x1F366, // SOFT ICE CREAM
+        0x1F367, // SHAVED ICE
+        0x1F368, // ICE CREAM
+        0x1F369, // DOUGHNUT
+        0x1F36A, // COOKIE
+        0x1F36B, // CHOCOLATE BAR
+        0x1F36C, // CANDY
+        0x1F36D, // LOLLIPOP
+        0x1F36E, // CUSTARD
+        0x1F36F, // HONEY POT
+        0x1F370, // SHORTCAKE
+        0x1F371, // BENTO BOX
+        0x1F372, // POT OF FOOD
+        0x1F373, // COOKING
+        0x1F374, // FORK AND KNIFE
+        0x1F375, // TEACUP WITHOUT HANDLE
+        0x1F376, // SAKE BOTTLE AND CUP
+        0x1F377, // WINE GLASS
+        0x1F378, // COCKTAIL GLASS
+        0x1F379, // TROPICAL DRINK
+        0x1F37A, // BEER MUG
+        0x1F37B, // CLINKING BEER MUGS
+        0x1F380, // RIBBON
+        0x1F381, // WRAPPED PRESENT
+        0x1F382, // BIRTHDAY CAKE
+        0x1F383, // JACK-O-LANTERN
+        0x1F384, // CHRISTMAS TREE
+        0x1F385, // FATHER CHRISTMAS
+        0x1F386, // FIREWORKS
+        0x1F387, // FIREWORK SPARKLER
+        0x1F388, // BALLOON
+        0x1F389, // PARTY POPPER
+        0x1F38A, // CONFETTI BALL
+        0x1F38B, // TANABATA TREE
+        0x1F38C, // CROSSED FLAGS
+        0x1F38D, // PINE DECORATION
+        0x1F38E, // JAPANESE DOLLS
+        0x1F38F, // CARP STREAMER
+        0x1F390, // WIND CHIME
+        0x1F391, // MOON VIEWING CEREMONY
+        0x1F392, // SCHOOL SATCHEL
+        0x1F393, // GRADUATION CAP
+        0x1F3A0, // CAROUSEL HORSE
+        0x1F3A1, // FERRIS WHEEL
+        0x1F3A2, // ROLLER COASTER
+        0x1F3A3, // FISHING POLE AND FISH
+        0x1F3A4, // MICROPHONE
+        0x1F3A5, // MOVIE CAMERA
+        0x1F3A6, // CINEMA
+        0x1F3A7, // HEADPHONE
+        0x1F3A8, // ARTIST PALETTE
+        0x1F3A9, // TOP HAT
+        0x1F3AA, // CIRCUS TENT
+        0x1F3AB, // TICKET
+        0x1F3AC, // CLAPPER BOARD
+        0x1F3AD, // PERFORMING ARTS
+        0x1F3AE, // VIDEO GAME
+        0x1F3AF, // DIRECT HIT
+        0x1F3B0, // SLOT MACHINE
+        0x1F3B1, // BILLIARDS
+        0x1F3B2, // GAME DIE
+        0x1F3B3, // BOWLING
+        0x1F3B4, // FLOWER PLAYING CARDS
+        0x1F3B5, // MUSICAL NOTE
+        0x1F3B6, // MULTIPLE MUSICAL NOTES
+        0x1F3B7, // SAXOPHONE
+        0x1F3B8, // GUITAR
+        0x1F3B9, // MUSICAL KEYBOARD
+        0x1F3BA, // TRUMPET
+        0x1F3BB, // VIOLIN
+        0x1F3BC, // MUSICAL SCORE
+        0x1F3BD, // RUNNING SHIRT WITH SASH
+        0x1F3BE, // TENNIS RACQUET AND BALL
+        0x1F3BF, // SKI AND SKI BOOT
+        0x1F3C0, // BASKETBALL AND HOOP
+        0x1F3C1, // CHEQUERED FLAG
+        0x1F3C2, // SNOWBOARDER
+        0x1F3C3, // RUNNER
+        0x1F3C4, // SURFER
+        0x1F3C6, // TROPHY
+        0x1F3C8, // AMERICAN FOOTBALL
+        0x1F3CA, // SWIMMER
+        0x1F3E0, // HOUSE BUILDING
+        0x1F3E1, // HOUSE WITH GARDEN
+        0x1F3E2, // OFFICE BUILDING
+        0x1F3E3, // JAPANESE POST OFFICE
+        0x1F3E5, // HOSPITAL
+        0x1F3E6, // BANK
+        0x1F3E7, // AUTOMATED TELLER MACHINE
+        0x1F3E8, // HOTEL
+        0x1F3E9, // LOVE HOTEL
+        0x1F3EA, // CONVENIENCE STORE
+        0x1F3EB, // SCHOOL
+        0x1F3EC, // DEPARTMENT STORE
+        0x1F3ED, // FACTORY
+        0x1F3EE, // IZAKAYA LANTERN
+        0x1F3EF, // JAPANESE CASTLE
+        0x1F3F0, // EUROPEAN CASTLE
+        0x1F40C, // SNAIL
+        0x1F40D, // SNAKE
+        0x1F40E, // HORSE
+        0x1F411, // SHEEP
+        0x1F412, // MONKEY
+        0x1F414, // CHICKEN
+        0x1F417, // BOAR
+        0x1F418, // ELEPHANT
+        0x1F419, // OCTOPUS
+        0x1F41A, // SPIRAL SHELL
+        0x1F41B, // BUG
+        0x1F41C, // ANT
+        0x1F41D, // HONEYBEE
+        0x1F41E, // LADY BEETLE
+        0x1F41F, // FISH
+        0x1F420, // TROPICAL FISH
+        0x1F421, // BLOWFISH
+        0x1F422, // TURTLE
+        0x1F423, // HATCHING CHICK
+        0x1F424, // BABY CHICK
+        0x1F425, // FRONT-FACING BABY CHICK
+        0x1F426, // BIRD
+        0x1F427, // PENGUIN
+        0x1F428, // KOALA
+        0x1F429, // POODLE
+        0x1F42B, // BACTRIAN CAMEL
+        0x1F42C, // DOLPHIN
+        0x1F42D, // MOUSE FACE
+        0x1F42E, // COW FACE
+        0x1F42F, // TIGER FACE
+        0x1F430, // RABBIT FACE
+        0x1F431, // CAT FACE
+        0x1F432, // DRAGON FACE
+        0x1F433, // SPOUTING WHALE
+        0x1F434, // HORSE FACE
+        0x1F435, // MONKEY FACE
+        0x1F436, // DOG FACE
+        0x1F437, // PIG FACE
+        0x1F438, // FROG FACE
+        0x1F439, // HAMSTER FACE
+        0x1F43A, // WOLF FACE
+        0x1F43B, // BEAR FACE
+        0x1F43C, // PANDA FACE
+        0x1F43D, // PIG NOSE
+        0x1F43E, // PAW PRINTS
+        0x1F440, // EYES
+        0x1F442, // EAR
+        0x1F443, // NOSE
+        0x1F444, // MOUTH
+        0x1F445, // TONGUE
+        0x1F446, // WHITE UP POINTING BACKHAND INDEX
+        0x1F447, // WHITE DOWN POINTING BACKHAND INDEX
+        0x1F448, // WHITE LEFT POINTING BACKHAND INDEX
+        0x1F449, // WHITE RIGHT POINTING BACKHAND INDEX
+        0x1F44A, // FISTED HAND SIGN
+        0x1F44B, // WAVING HAND SIGN
+        0x1F44C, // OK HAND SIGN
+        0x1F44D, // THUMBS UP SIGN
+        0x1F44E, // THUMBS DOWN SIGN
+        0x1F44F, // CLAPPING HANDS SIGN
+        0x1F450, // OPEN HANDS SIGN
+        0x1F451, // CROWN
+        0x1F452, // WOMANS HAT
+        0x1F453, // EYEGLASSES
+        0x1F454, // NECKTIE
+        0x1F455, // T-SHIRT
+        0x1F456, // JEANS
+        0x1F457, // DRESS
+        0x1F458, // KIMONO
+        0x1F459, // BIKINI
+        0x1F45A, // WOMANS CLOTHES
+        0x1F45B, // PURSE
+        0x1F45C, // HANDBAG
+        0x1F45D, // POUCH
+        0x1F45E, // MANS SHOE
+        0x1F45F, // ATHLETIC SHOE
+        0x1F460, // HIGH-HEELED SHOE
+        0x1F461, // WOMANS SANDAL
+        0x1F462, // WOMANS BOOTS
+        0x1F463, // FOOTPRINTS
+        0x1F464, // BUST IN SILHOUETTE
+        0x1F466, // BOY
+        0x1F467, // GIRL
+        0x1F468, // MAN
+        0x1F469, // WOMAN
+        0x1F46A, // FAMILY
+        0x1F46B, // MAN AND WOMAN HOLDING HANDS
+        0x1F46E, // POLICE OFFICER
+        0x1F46F, // WOMAN WITH BUNNY EARS
+        0x1F470, // BRIDE WITH VEIL
+        0x1F471, // PERSON WITH BLOND HAIR
+        0x1F472, // MAN WITH GUA PI MAO
+        0x1F473, // MAN WITH TURBAN
+        0x1F474, // OLDER MAN
+        0x1F475, // OLDER WOMAN
+        0x1F476, // BABY
+        0x1F477, // CONSTRUCTION WORKER
+        0x1F478, // PRINCESS
+        0x1F479, // JAPANESE OGRE
+        0x1F47A, // JAPANESE GOBLIN
+        0x1F47B, // GHOST
+        0x1F47C, // BABY ANGEL
+        0x1F47D, // EXTRATERRESTRIAL ALIEN
+        0x1F47E, // ALIEN MONSTER
+        0x1F47F, // IMP
+        0x1F480, // SKULL
+        0x1F481, // INFORMATION DESK PERSON
+        0x1F482, // GUARDSMAN
+        0x1F483, // DANCER
+        0x1F484, // LIPSTICK
+        0x1F485, // NAIL POLISH
+        0x1F486, // FACE MASSAGE
+        0x1F487, // HAIRCUT
+        0x1F488, // BARBER POLE
+        0x1F489, // SYRINGE
+        0x1F48A, // PILL
+        0x1F48B, // KISS MARK
+        0x1F48C, // LOVE LETTER
+        0x1F48D, // RING
+        0x1F48E, // GEM STONE
+        0x1F48F, // KISS
+        0x1F490, // BOUQUET
+        0x1F491, // COUPLE WITH HEART
+        0x1F492, // WEDDING
+        0x1F493, // BEATING HEART
+        0x1F494, // BROKEN HEART
+        0x1F495, // TWO HEARTS
+        0x1F496, // SPARKLING HEART
+        0x1F497, // GROWING HEART
+        0x1F498, // HEART WITH ARROW
+        0x1F499, // BLUE HEART
+        0x1F49A, // GREEN HEART
+        0x1F49B, // YELLOW HEART
+        0x1F49C, // PURPLE HEART
+        0x1F49D, // HEART WITH RIBBON
+        0x1F49E, // REVOLVING HEARTS
+        0x1F49F, // HEART DECORATION
+        0x1F4A0, // DIAMOND SHAPE WITH A DOT INSIDE
+        0x1F4A1, // ELECTRIC LIGHT BULB
+        0x1F4A2, // ANGER SYMBOL
+        0x1F4A3, // BOMB
+        0x1F4A4, // SLEEPING SYMBOL
+        0x1F4A5, // COLLISION SYMBOL
+        0x1F4A6, // SPLASHING SWEAT SYMBOL
+        0x1F4A7, // DROPLET
+        0x1F4A8, // DASH SYMBOL
+        0x1F4A9, // PILE OF POO
+        0x1F4AA, // FLEXED BICEPS
+        0x1F4AB, // DIZZY SYMBOL
+        0x1F4AC, // SPEECH BALLOON
+        0x1F4AE, // WHITE FLOWER
+        0x1F4AF, // HUNDRED POINTS SYMBOL
+        0x1F4B0, // MONEY BAG
+        0x1F4B1, // CURRENCY EXCHANGE
+        0x1F4B2, // HEAVY DOLLAR SIGN
+        0x1F4B3, // CREDIT CARD
+        0x1F4B4, // BANKNOTE WITH YEN SIGN
+        0x1F4B5, // BANKNOTE WITH DOLLAR SIGN
+        0x1F4B8, // MONEY WITH WINGS
+        0x1F4B9, // CHART WITH UPWARDS TREND AND YEN SIGN
+        0x1F4BA, // SEAT
+        0x1F4BB, // PERSONAL COMPUTER
+        0x1F4BC, // BRIEFCASE
+        0x1F4BD, // MINIDISC
+        0x1F4BE, // FLOPPY DISK
+        0x1F4BF, // OPTICAL DISC
+        0x1F4C0, // DVD
+        0x1F4C1, // FILE FOLDER
+        0x1F4C2, // OPEN FILE FOLDER
+        0x1F4C3, // PAGE WITH CURL
+        0x1F4C4, // PAGE FACING UP
+        0x1F4C5, // CALENDAR
+        0x1F4C6, // TEAR-OFF CALENDAR
+        0x1F4C7, // CARD INDEX
+        0x1F4C8, // CHART WITH UPWARDS TREND
+        0x1F4C9, // CHART WITH DOWNWARDS TREND
+        0x1F4CA, // BAR CHART
+        0x1F4CB, // CLIPBOARD
+        0x1F4CC, // PUSHPIN
+        0x1F4CD, // ROUND PUSHPIN
+        0x1F4CE, // PAPERCLIP
+        0x1F4CF, // STRAIGHT RULER
+        0x1F4D0, // TRIANGULAR RULER
+        0x1F4D1, // BOOKMARK TABS
+        0x1F4D2, // LEDGER
+        0x1F4D3, // NOTEBOOK
+        0x1F4D4, // NOTEBOOK WITH DECORATIVE COVER
+        0x1F4D5, // CLOSED BOOK
+        0x1F4D6, // OPEN BOOK
+        0x1F4D7, // GREEN BOOK
+        0x1F4D8, // BLUE BOOK
+        0x1F4D9, // ORANGE BOOK
+        0x1F4DA, // BOOKS
+        0x1F4DB, // NAME BADGE
+        0x1F4DC, // SCROLL
+        0x1F4DD, // MEMO
+        0x1F4DE, // TELEPHONE RECEIVER
+        0x1F4DF, // PAGER
+        0x1F4E0, // FAX MACHINE
+        0x1F4E1, // SATELLITE ANTENNA
+        0x1F4E2, // PUBLIC ADDRESS LOUDSPEAKER
+        0x1F4E3, // CHEERING MEGAPHONE
+        0x1F4E4, // OUTBOX TRAY
+        0x1F4E5, // INBOX TRAY
+        0x1F4E6, // PACKAGE
+        0x1F4E7, // E-MAIL SYMBOL
+        0x1F4E8, // INCOMING ENVELOPE
+        0x1F4E9, // ENVELOPE WITH DOWNWARDS ARROW ABOVE
+        0x1F4EA, // CLOSED MAILBOX WITH LOWERED FLAG
+        0x1F4EB, // CLOSED MAILBOX WITH RAISED FLAG
+        0x1F4EE, // POSTBOX
+        0x1F4F0, // NEWSPAPER
+        0x1F4F1, // MOBILE PHONE
+        0x1F4F2, // MOBILE PHONE WITH RIGHTWARDS ARROW AT LEFT
+        0x1F4F3, // VIBRATION MODE
+        0x1F4F4, // MOBILE PHONE OFF
+        0x1F4F6, // ANTENNA WITH BARS
+        0x1F4F7, // CAMERA
+        0x1F4F9, // VIDEO CAMERA
+        0x1F4FA, // TELEVISION
+        0x1F4FB, // RADIO
+        0x1F4FC, // VIDEOCASSETTE
+        0x1F503, // CLOCKWISE DOWNWARDS AND UPWARDS OPEN CIRCLE ARROWS
+        0x1F50A, // SPEAKER WITH THREE SOUND WAVES
+        0x1F50B, // BATTERY
+        0x1F50C, // ELECTRIC PLUG
+        0x1F50D, // LEFT-POINTING MAGNIFYING GLASS
+        0x1F50E, // RIGHT-POINTING MAGNIFYING GLASS
+        0x1F50F, // LOCK WITH INK PEN
+        0x1F510, // CLOSED LOCK WITH KEY
+        0x1F511, // KEY
+        0x1F512, // LOCK
+        0x1F513, // OPEN LOCK
+        0x1F514, // BELL
+        0x1F516, // BOOKMARK
+        0x1F517, // LINK SYMBOL
+        0x1F518, // RADIO BUTTON
+        0x1F53A, // UP-POINTING RED TRIANGLE
+        0x1F53B, // DOWN-POINTING RED TRIANGLE
+        0x1F53C, // UP-POINTING SMALL RED TRIANGLE
+        0x1F53D, // DOWN-POINTING SMALL RED TRIANGLE
+        0x1F519, // BACK WITH LEFTWARDS ARROW ABOVE
+        0x1F51A, // END WITH LEFTWARDS ARROW ABOVE
+        0x1F51B, // ON WITH EXCLAMATION MARK WITH LEFT RIGHT ARROW ABOVE
+        0x1F51C, // SOON WITH RIGHTWARDS ARROW ABOVE
+        0x1F51D, // TOP WITH UPWARDS ARROW ABOVE
+        0x1F51E, // NO ONE UNDER EIGHTEEN SYMBOL
+        0x1F51F, // KEYCAP TEN
+        0x1F520, // INPUT SYMBOL FOR LATIN CAPITAL LETTERS
+        0x1F521, // INPUT SYMBOL FOR LATIN SMALL LETTERS
+        0x1F522, // INPUT SYMBOL FOR NUMBERS
+        0x1F523, // INPUT SYMBOL FOR SYMBOLS
+        0x1F524, // INPUT SYMBOL FOR LATIN LETTERS
+        0x1F525, // FIRE
+        0x1F526, // ELECTRIC TORCH
+        0x1F527, // WRENCH
+        0x1F528, // HAMMER
+        0x1F529, // NUT AND BOLT
+        0x1F52A, // HOCHO
+        0x1F52B, // PISTOL
+        0x1F52E, // CRYSTAL BALL
+        0x1F52F, // SIX POINTED STAR WITH MIDDLE DOT
+        0x1F530, // JAPANESE SYMBOL FOR BEGINNER
+        0x1F531, // TRIDENT EMBLEM
+        0x1F532, // BLACK SQUARE BUTTON
+        0x1F533, // WHITE SQUARE BUTTON
+        0x1F534, // LARGE RED CIRCLE
+        0x1F535, // LARGE BLUE CIRCLE
+        0x1F536, // LARGE ORANGE DIAMOND
+        0x1F537, // LARGE BLUE DIAMOND
+        0x1F538, // SMALL ORANGE DIAMOND
+        0x1F539, // SMALL BLUE DIAMOND
+        0x1F550, // CLOCK FACE ONE OCLOCK
+        0x1F551, // CLOCK FACE TWO OCLOCK
+        0x1F552, // CLOCK FACE THREE OCLOCK
+        0x1F553, // CLOCK FACE FOUR OCLOCK
+        0x1F554, // CLOCK FACE FIVE OCLOCK
+        0x1F555, // CLOCK FACE SIX OCLOCK
+        0x1F556, // CLOCK FACE SEVEN OCLOCK
+        0x1F557, // CLOCK FACE EIGHT OCLOCK
+        0x1F558, // CLOCK FACE NINE OCLOCK
+        0x1F559, // CLOCK FACE TEN OCLOCK
+        0x1F55A, // CLOCK FACE ELEVEN OCLOCK
+        0x1F55B, // CLOCK FACE TWELVE OCLOCK
+        0x1F5FB, // MOUNT FUJI
+        0x1F5FC, // TOKYO TOWER
+        0x1F5FD, // STATUE OF LIBERTY
+        0x1F5FE, // SILHOUETTE OF JAPAN
+        0x1F5FF, // MOYAI
+        0x1F601, // GRINNING FACE WITH SMILING EYES
+        0x1F602, // FACE WITH TEARS OF JOY
+        0x1F603, // SMILING FACE WITH OPEN MOUTH
+        0x1F604, // SMILING FACE WITH OPEN MOUTH AND SMILING EYES
+        0x1F605, // SMILING FACE WITH OPEN MOUTH AND COLD SWEAT
+        0x1F606, // SMILING FACE WITH OPEN MOUTH AND TIGHTLY-CLOSED EYES
+        0x1F609, // WINKING FACE
+        0x1F60A, // SMILING FACE WITH SMILING EYES
+        0x1F60B, // FACE SAVOURING DELICIOUS FOOD
+        0x1F60C, // RELIEVED FACE
+        0x1F60D, // SMILING FACE WITH HEART-SHAPED EYES
+        0x1F60F, // SMIRKING FACE
+        0x1F612, // UNAMUSED FACE
+        0x1F613, // FACE WITH COLD SWEAT
+        0x1F614, // PENSIVE FACE
+        0x1F616, // CONFOUNDED FACE
+        0x1F618, // FACE THROWING A KISS
+        0x1F61A, // KISSING FACE WITH CLOSED EYES
+        0x1F61C, // FACE WITH STUCK-OUT TONGUE AND WINKING EYE
+        0x1F61D, // FACE WITH STUCK-OUT TONGUE AND TIGHTLY-CLOSED EYES
+        0x1F61E, // DISAPPOINTED FACE
+        0x1F620, // ANGRY FACE
+        0x1F621, // POUTING FACE
+        0x1F622, // CRYING FACE
+        0x1F623, // PERSEVERING FACE
+        0x1F624, // FACE WITH LOOK OF TRIUMPH
+        0x1F625, // DISAPPOINTED BUT RELIEVED FACE
+        0x1F628, // FEARFUL FACE
+        0x1F629, // WEARY FACE
+        0x1F62A, // SLEEPY FACE
+        0x1F62B, // TIRED FACE
+        0x1F62D, // LOUDLY CRYING FACE
+        0x1F630, // FACE WITH OPEN MOUTH AND COLD SWEAT
+        0x1F631, // FACE SCREAMING IN FEAR
+        0x1F632, // ASTONISHED FACE
+        0x1F633, // FLUSHED FACE
+        0x1F635, // DIZZY FACE
+        0x1F637, // FACE WITH MEDICAL MASK
+        0x1F638, // GRINNING CAT FACE WITH SMILING EYES
+        0x1F639, // CAT FACE WITH TEARS OF JOY
+        0x1F63A, // SMILING CAT FACE WITH OPEN MOUTH
+        0x1F63B, // SMILING CAT FACE WITH HEART-SHAPED EYES
+        0x1F63C, // CAT FACE WITH WRY SMILE
+        0x1F63D, // KISSING CAT FACE WITH CLOSED EYES
+        0x1F63E, // POUTING CAT FACE
+        0x1F63F, // CRYING CAT FACE
+        0x1F640, // WEARY CAT FACE
+        0x1F645, // FACE WITH NO GOOD GESTURE
+        0x1F646, // FACE WITH OK GESTURE
+        0x1F647, // PERSON BOWING DEEPLY
+        0x1F648, // SEE-NO-EVIL MONKEY
+        0x1F649, // HEAR-NO-EVIL MONKEY
+        0x1F64A, // SPEAK-NO-EVIL MONKEY
+        0x1F64B, // HAPPY PERSON RAISING ONE HAND
+        0x1F64C, // PERSON RAISING BOTH HANDS IN CELEBRATION
+        0x1F64D, // PERSON FROWNING
+        0x1F64E, // PERSON WITH POUTING FACE
+        0x1F64F, // PERSON WITH FOLDED HANDS
+        0x1F680, // ROCKET
+        0x1F683, // RAILWAY CAR
+        0x1F684, // HIGH-SPEED TRAIN
+        0x1F685, // HIGH-SPEED TRAIN WITH BULLET NOSE
+        0x1F687, // METRO
+        0x1F689, // STATION
+        0x1F68C, // BUS
+        0x1F68F, // BUS STOP
+        0x1F691, // AMBULANCE
+        0x1F692, // FIRE ENGINE
+        0x1F693, // POLICE CAR
+        0x1F695, // TAXI
+        0x1F697, // AUTOMOBILE
+        0x1F699, // RECREATIONAL VEHICLE
+        0x1F69A, // DELIVERY TRUCK
+        0x1F6A2, // SHIP
+        0x1F6A4, // SPEEDBOAT
+        0x1F6A5, // HORIZONTAL TRAFFIC LIGHT
+        0x1F6A7, // CONSTRUCTION SIGN
+        0x1F6A8, // POLICE CARS REVOLVING LIGHT
+        0x1F6A9, // TRIANGULAR FLAG ON POST
+        0x1F6AA, // DOOR
+        0x1F6AB, // NO ENTRY SIGN
+        0x1F6AC, // SMOKING SYMBOL
+        0x1F6AD, // NO SMOKING SYMBOL
+        0x1F6B2, // BICYCLE
+        0x1F6B6, // PEDESTRIAN
+        0x1F6B9, // MENS SYMBOL
+        0x1F6BA, // WOMENS SYMBOL
+        0x1F6BB, // RESTROOM
+        0x1F6BC, // BABY SYMBOL
+        0x1F6BD, // TOILET
+        0x1F6BE, // WATER CLOSET
+        0x1F6C0  // BAT
+    };
+}
diff --git a/tests/tests/text/src/android/text/cts/EmojiTest.java b/tests/tests/text/src/android/text/cts/EmojiTest.java
new file mode 100755
index 0000000..867cb8d
--- /dev/null
+++ b/tests/tests/text/src/android/text/cts/EmojiTest.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2013 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.text.cts;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Picture;
+import android.test.ActivityInstrumentationTestCase2;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.View;
+import android.webkit.WebView;
+import android.widget.TextView;
+import android.widget.EditText;
+
+public class EmojiTest extends ActivityInstrumentationTestCase2<EmojiStubActivity> {
+
+    public EmojiTest() {
+        super("com.android.cts.stub", EmojiStubActivity.class);
+    }
+
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    /**
+     * Tests all Emoji are defined in Character class
+     */
+    public void testEmojiCodePoints() {
+        for (int i = 0; i < EmojiConstants.emojiCodePoints.length; i++) {
+            assertTrue(Character.isDefined(EmojiConstants.emojiCodePoints[i]));
+        }
+    }
+
+    /**
+     * Tests Emoji has different glyph for different meaning characters.
+     * Test on Canvas, TextView, EditText and WebView
+     */
+    public void testEmojiGlyph() {
+        CaptureCanvas ccanvas = new CaptureCanvas(getInstrumentation().getContext());
+        CaptureWebView cwebview = new CaptureWebView(getInstrumentation().getContext());
+
+        Bitmap mBitmapA, mBitmapB;  // Emoji displayed Bitmaps to compare
+
+        int comparedCodePoints[][] = {   // Emojis should have different characters
+            {0x1F436, 0x1F435},      // Dog(U+1F436) and Monkey(U+1F435)
+            {0x26BD, 0x26BE},        // Soccer ball(U+26BD) and Baseball(U+26BE)
+            {0x1F47B, 0x1F381},      // Ghost(U+1F47B) and wrapped present(U+1F381)
+            {0x2764, 0x1F494},       // Heavy black heart(U+2764) and broken heart(U+1F494)
+            {0x1F603, 0x1F33B}       // Smiling face with open mouth(U+1F603) and sunflower(U+1F33B)
+        };
+
+        for (int i = 0; i < comparedCodePoints.length; i++) {
+
+            mBitmapA = ccanvas.capture(Character.toChars(comparedCodePoints[i][0]));
+            mBitmapB = ccanvas.capture(Character.toChars(comparedCodePoints[i][1]));
+
+            assertFalse(mBitmapA.sameAs(mBitmapB));
+
+            // cannot reuse CaptureTextView as 2nd setText call throws NullPointerException
+            CaptureTextView cviewA = new CaptureTextView(getInstrumentation().getContext());
+            mBitmapA = cviewA.capture(Character.toChars(comparedCodePoints[i][0]));
+            CaptureTextView cviewB = new CaptureTextView(getInstrumentation().getContext());
+            mBitmapB = cviewB.capture(Character.toChars(comparedCodePoints[i][1]));
+
+            assertFalse(mBitmapA.sameAs(mBitmapB));
+
+            CaptureEditText cedittextA = new CaptureEditText(getInstrumentation().getContext());
+            mBitmapA = cedittextA.capture(Character.toChars(comparedCodePoints[i][0]));
+            CaptureEditText cedittextB = new CaptureEditText(getInstrumentation().getContext());
+            mBitmapB = cedittextB.capture(Character.toChars(comparedCodePoints[i][1]));
+
+            assertFalse(mBitmapA.sameAs(mBitmapB));
+
+            mBitmapA = cwebview.capture(Character.toChars(comparedCodePoints[i][0]));
+            mBitmapB = cwebview.capture(Character.toChars(comparedCodePoints[i][1]));
+
+            assertFalse(mBitmapA.sameAs(mBitmapB));
+
+        }
+    }
+
+    /**
+     * Tests EditText handles Emoji
+     */
+    public void testEmojiEditable() {
+        int testedCodePoints[] = {
+            0xAE,    // registered mark
+            0x2764,    // heavy black heart
+            0x1F353    // strawberry - surrogate pair sample. Count as two characters.
+        };
+
+        String origStr, newStr;
+
+        // delete Emoji by sending KEYCODE_DEL
+        for (int i = 0; i < testedCodePoints.length; i++) {
+            origStr = "Test character  ";
+            // cannot reuse CaptureTextView as 2nd setText call throws NullPointerException
+            EditText editText = new EditText(getInstrumentation().getContext());
+            editText.setText(origStr + String.valueOf(Character.toChars(testedCodePoints[i])));
+
+            // confirm the emoji is added.
+            newStr = editText.getText().toString();
+            assertEquals(newStr.codePointCount(0, newStr.length()), origStr.length() + 1);
+
+            // Delete added character by sending KEYCODE_DEL event
+            editText.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL));
+
+            newStr = editText.getText().toString();
+            assertEquals(newStr.codePointCount(0, newStr.length()), origStr.length() + 1);
+        }
+    }
+
+    private class CaptureCanvas extends View {
+
+        String mTestStr;
+        Paint paint = new Paint();
+
+        CaptureCanvas(Context context) {
+            super(context);
+        }
+
+        public void onDraw(Canvas canvas) {
+            if (mTestStr != null) {
+                canvas.drawText(mTestStr, 50, 50, paint);
+            }
+            return;
+        }
+
+        Bitmap capture(char c[]) {
+            mTestStr = String.valueOf(c);
+            invalidate();
+
+            setDrawingCacheEnabled(true);
+            measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
+                    MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+            layout(0, 0, 200,200);
+
+            Bitmap bitmap = Bitmap.createBitmap(getDrawingCache());
+            setDrawingCacheEnabled(false);
+            return bitmap;
+        }
+
+    }
+
+    private class CaptureTextView extends TextView {
+
+        CaptureTextView(Context context) {
+            super(context);
+        }
+
+        Bitmap capture(char c[]) {
+            setText(String.valueOf(c));
+
+            invalidate();
+
+            setDrawingCacheEnabled(true);
+            measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
+                    MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+            layout(0, 0, 200,200);
+
+            Bitmap bitmap = Bitmap.createBitmap(getDrawingCache());
+            setDrawingCacheEnabled(false);
+            return bitmap;
+        }
+
+    }
+
+    private class CaptureEditText extends EditText {
+
+        CaptureEditText(Context context) {
+            super(context);
+        }
+
+        Bitmap capture(char c[]) {
+            setText(String.valueOf(c));
+
+            invalidate();
+
+            setDrawingCacheEnabled(true);
+            measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
+                    MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+            layout(0, 0, 200,200);
+
+            Bitmap bitmap = Bitmap.createBitmap(getDrawingCache());
+            setDrawingCacheEnabled(false);
+            return bitmap;
+        }
+
+    }
+
+
+    private class CaptureWebView {
+
+        WebView view;
+        Bitmap bitmap;
+        CaptureWebView(Context context) {
+            view = getActivity().getWebView();
+        }
+
+        Bitmap capture(char c[]) {
+
+            view.loadData("<html><body>" + String.valueOf(c) + "</body></html>",
+                    "text/html; charset=utf-8", "utf-8");
+
+            try {
+                Thread.sleep(200);
+            } catch (InterruptedException ie) {
+                return null;
+            }
+
+            Picture picture = view.capturePicture();
+            if (picture == null || picture.getHeight() <= 0 || picture.getWidth() <= 0) {
+                return null;
+            } else {
+                bitmap = Bitmap.createBitmap(picture.getWidth(), picture.getHeight(),
+                        Bitmap.Config.ARGB_8888);
+                Canvas canvas = new Canvas(bitmap);
+                picture.draw(canvas);
+            }
+
+            return bitmap;
+        }
+
+    }
+
+}
+
diff --git a/tests/tests/text/src/android/text/cts/StaticLayoutLineBreakingTest.java b/tests/tests/text/src/android/text/cts/StaticLayoutLineBreakingTest.java
index 3aa8f35..f702c06 100644
--- a/tests/tests/text/src/android/text/cts/StaticLayoutLineBreakingTest.java
+++ b/tests/tests/text/src/android/text/cts/StaticLayoutLineBreakingTest.java
@@ -48,18 +48,14 @@
 
         @Override
         public float getTextRunAdvances(char[] chars, int index, int count,
-                int contextIndex, int contextCount, int flags, float[] advances,
-                int advancesIndex, int reserved) {
+                int contextIndex, int contextCount, float[] advances,
+                int advancesIndex) {
 
             // Conditions copy pasted from Paint
             if (chars == null) {
                 throw new IllegalArgumentException("text cannot be null");
             }
 
-            if (flags != DIRECTION_LTR && flags != DIRECTION_RTL) {
-                throw new IllegalArgumentException("unknown flags value: " + flags);
-            }
-
             if ((index | count | contextIndex | contextCount | advancesIndex
                     | (index - contextIndex) | (contextCount - count)
                     | ((contextIndex + contextCount) - (index + count))
diff --git a/tests/tests/text/src/android/text/method/cts/DigitsKeyListenerTest.java b/tests/tests/text/src/android/text/method/cts/DigitsKeyListenerTest.java
index 0a1b5ae..ea50294 100644
--- a/tests/tests/text/src/android/text/method/cts/DigitsKeyListenerTest.java
+++ b/tests/tests/text/src/android/text/method/cts/DigitsKeyListenerTest.java
@@ -64,8 +64,9 @@
      * 1. filter "123456", return null.
      * 2. filter "a1b2c3d", return "123"
      * 3. filter "-a1.b2c3d", return "123"
-     * 4. filter Spanned("-a1.b2c3d"), return Spanned("123") and copy spans.
-     * 5. filter "", return null
+     * 4. filter "+a1.b2c3d", return "123"
+     * 5. filter Spanned("-a1.b2c3d"), return Spanned("123") and copy spans.
+     * 6. filter "", return null
      */
     public void testFilter1() {
         String source = "123456";
@@ -87,6 +88,11 @@
                 dest, 0, dest.length())).toString());
         assertEquals(destString, dest.toString());
 
+        source = "+a1.b2c3d";
+        assertEquals("123", (digitsKeyListener.filter(source, 0, source.length(),
+                dest, 0, dest.length())).toString());
+        assertEquals(destString, dest.toString());
+
         Object what = new Object();
         Spannable spannableSource = new SpannableString(source);
         spannableSource.setSpan(what, 0, spannableSource.length(), Spanned.SPAN_POINT_POINT);
@@ -103,15 +109,21 @@
 
     /**
      * Check point:
-     * Current accepted characters are '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-'.
+     * Current accepted characters are '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '+'.
      * 1. filter "-123456", return null
-     * 2. filter "-a1.b2c3d", return "-123"
-     * 3. filter "-a1-b2c3d", return "-123"
-     * 4. filter "5-a1-b2c3d", return "5123"
-     * 5. filter Spanned("5-a1-b2c3d"), return Spanned("5123") and copy spans.
-     * 6. filter "", return null
-     * 7. filter "-123456" but dest has '-' after dend, return ""
-     * 8. filter "-123456" but dest has '-' before dstart, return "123456"
+     * 2. filter "+123456", return null
+     * 3. filter "-a1.b2c3d", return "-123"
+     * 4. filter "-a1-b2c3d", return "-123"
+     * 5. filter "+a1-b2c3d", return "+123"
+     * 6. filter "5-a1-b2c3d", return "5123"
+     * 7. filter "5-a1+b2c3d", return "5123"
+     * 8. filter "+5-a1+b2c3d", return "+5123"
+     * 9. filter Spanned("5-a1-b2c3d"), return Spanned("5123") and copy spans.
+     * 10. filter "", return null
+     * 11. filter "-123456" but dest has '-' after dend, return ""
+     * 12. filter "-123456" but dest has '+' after dend, return ""
+     * 13. filter "-123456" but dest has '-' before dstart, return "123456"
+     * 14. filter "+123456" but dest has '-' before dstart, return "123456"
      */
     public void testFilter2() {
         String source = "-123456";
@@ -123,6 +135,11 @@
                 dest, 0, dest.length()));
         assertEquals(destString, dest.toString());
 
+        source = "+123456";
+        assertNull(digitsKeyListener.filter(source, 0, source.length(),
+                dest, 0, dest.length()));
+        assertEquals(destString, dest.toString());
+
         source = "-a1.b2c3d";
         assertEquals("-123", (digitsKeyListener.filter(source, 0, source.length(),
                 dest, 0, dest.length())).toString());
@@ -133,11 +150,27 @@
                 dest, 0, dest.length())).toString());
         assertEquals(destString, dest.toString());
 
+        source = "+a1-b2c3d";
+        assertEquals("+123", (digitsKeyListener.filter(source, 0, source.length(),
+                dest, 0, dest.length())).toString());
+        assertEquals(destString, dest.toString());
+
         source = "5-a1-b2c3d";
         assertEquals("5123", (digitsKeyListener.filter(source, 0, source.length(),
                 dest, 0, dest.length())).toString());
         assertEquals(destString, dest.toString());
 
+        source = "5-a1+b2c3d";
+        assertEquals("5123", (digitsKeyListener.filter(source, 0, source.length(),
+                dest, 0, dest.length())).toString());
+        assertEquals(destString, dest.toString());
+
+        source = "+5-a1+b2c3d";
+        assertEquals("+5123", (digitsKeyListener.filter(source, 0, source.length(),
+                dest, 0, dest.length())).toString());
+        assertEquals(destString, dest.toString());
+
+        source = "5-a1+b2c3d";
         Object what = new Object();
         Spannable spannableSource = new SpannableString(source);
         spannableSource.setSpan(what, 0, spannableSource.length(), Spanned.SPAN_POINT_POINT);
@@ -158,11 +191,23 @@
                 dest, 0, dest.length() - 1)).toString());
         assertEquals(endSign, dest.toString());
 
+        endSign = "789+";
+        dest = new SpannableString(endSign);
+        assertEquals("", (digitsKeyListener.filter(source, 0, source.length(),
+                dest, 0, dest.length() - 1)).toString());
+        assertEquals(endSign, dest.toString());
+
         String startSign = "-789";
         dest = new SpannableString(startSign);
         assertEquals("123456", (digitsKeyListener.filter(source, 0, source.length(),
                 dest, 1, dest.length())).toString());
         assertEquals(startSign, dest.toString());
+
+        source = "+123456";
+        dest = new SpannableString(startSign);
+        assertEquals("123456", (digitsKeyListener.filter(source, 0, source.length(),
+                dest, 1, dest.length())).toString());
+        assertEquals(startSign, dest.toString());
     }
 
     /**
@@ -170,12 +215,13 @@
      * Current accepted characters are '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.'.
      * 1. filter "123.456", return null
      * 2. filter "-a1.b2c3d", return "1.23"
-     * 3. filter "a1.b2c3d.", return "123."
-     * 4. filter "5.a1.b2c3d", return "51.23"
-     * 5. filter Spanned("5.a1.b2c3d"), return Spanned("51.23") and copy spans.
-     * 6. filter "", return null
-     * 7. filter "123.456" but dest has '.' after dend, return "123456"
-     * 8. filter "123.456" but dest has '.' before dstart, return "123456"
+     * 3. filter "+a1.b2c3d", return "1.23"
+     * 4. filter "a1.b2c3d.", return "123."
+     * 5. filter "5.a1.b2c3d", return "51.23"
+     * 6. filter Spanned("5.a1.b2c3d"), return Spanned("51.23") and copy spans.
+     * 7. filter "", return null
+     * 8. filter "123.456" but dest has '.' after dend, return "123456"
+     * 9. filter "123.456" but dest has '.' before dstart, return "123456"
      */
     public void testFilter3() {
         String source = "123.456";
@@ -192,6 +238,11 @@
                 dest, 0, dest.length())).toString());
         assertEquals(destString, dest.toString());
 
+        source = "+a1.b2c3d";
+        assertEquals("1.23", (digitsKeyListener.filter(source, 0, source.length(),
+                dest, 0, dest.length())).toString());
+        assertEquals(destString, dest.toString());
+
         source = "a1.b2c3d.";
         assertEquals("123.", (digitsKeyListener.filter(source, 0, source.length(),
                 dest, 0, dest.length())).toString());
@@ -231,17 +282,25 @@
 
     /**
      * Check point:
-     * Current accepted characters are '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '-'.
+     * Current accepted characters are '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '-',
+     * '+'.
      * 1. filter "-123.456", return null
-     * 2. filter "-a1.b2c3d", return "-1.23"
-     * 3. filter "a1.b-2c3d.", return "123."
-     * 4. filter "-5.a1.b2c3d", return "-51.23"
-     * 5. filter Spanned("-5.a1.b2c3d"), return Spanned("-51.23") and copy spans.
-     * 6. filter "", return null
-     * 7. filter "-123.456" but dest has '.' after dend, return "-123456"
-     * 8. filter "-123.456" but dest has '.' before dstart, return "123456"
-     * 9. filter "-123.456" but dest has '-' after dend, return ""
-     * 10. filter "-123.456" but dest has '-' before dstart, return "123.456"
+     * 2. filter "+123.456", return null
+     * 3. filter "-a1.b2c3d", return "-1.23"
+     * 4. filter "+a1.b2c3d", return "+1.23"
+     * 5. filter "a1.b-2c+3d.", return "123."
+     * 6. filter "-5.a1.b2c+3d", return "-51.23"
+     * 7. filter "+5.a1.b2c-3d", return "+51.23"
+     * 8. filter Spanned("-5.a1.b2c3d"), return Spanned("-51.23") and copy spans.
+     * 9. filter "", return null
+     * 10. filter "-123.456" but dest has '.' after dend, return "-123456"
+     * 11. filter "-123.456" but dest has '.' before dstart, return "123456"
+     * 12. filter "+123.456" but dest has '.' after dend, return "+123456"
+     * 13. filter "+123.456" but dest has '.' before dstart, return "123456"
+     * 14. filter "-123.456" but dest has '-' after dend, return ""
+     * 15. filter "-123.456" but dest has '+' after dend, return ""
+     * 16. filter "-123.456" but dest has '-' before dstart, return "123.456"
+     * 17. filter "+123.456" but dest has '-' before dstart, return "123.456"
      */
     public void testFilter4() {
         String source = "-123.456";
@@ -253,21 +312,32 @@
                 dest, 0, dest.length()));
         assertEquals(destString, dest.toString());
 
+        source = "+123.456";
+        assertNull(digitsKeyListener.filter(source, 0, source.length(),
+                dest, 0, dest.length()));
+        assertEquals(destString, dest.toString());
+
         source = "-a1.b2c3d";
         assertEquals("-1.23", (digitsKeyListener.filter(source, 0, source.length(),
                 dest, 0, dest.length())).toString());
         assertEquals(destString, dest.toString());
 
-        source = "a1.b-2c3d.";
+        source = "a1.b-2c+3d.";
         assertEquals("123.", (digitsKeyListener.filter(source, 0, source.length(),
                 dest, 0, dest.length())).toString());
         assertEquals(destString, dest.toString());
 
-        source = "-5.a1.b2c3d";
+        source = "-5.a1.b2c+3d";
         assertEquals("-51.23", (digitsKeyListener.filter(source, 0, source.length(),
                 dest, 0, dest.length())).toString());
         assertEquals(destString, dest.toString());
 
+        source = "+5.a1.b2c-3d";
+        assertEquals("+51.23", (digitsKeyListener.filter(source, 0, source.length(),
+                dest, 0, dest.length())).toString());
+        assertEquals(destString, dest.toString());
+
+        source = "-5.a1.b2c+3d";
         Object what = new Object();
         Spannable spannableSource = new SpannableString(source);
         spannableSource.setSpan(what, 0, spannableSource.length(), Spanned.SPAN_POINT_POINT);
@@ -294,17 +364,43 @@
                 dest, 1, dest.length())).toString());
         assertEquals(startDecimal, dest.toString());
 
+        source = "+123.456";
+        endDecimal = "789.";
+        dest = new SpannableString(endDecimal);
+        assertEquals("+123456", (digitsKeyListener.filter(source, 0, source.length(),
+                dest, 0, dest.length() - 1)).toString());
+        assertEquals(endDecimal, dest.toString());
+
+        startDecimal = ".789";
+        dest = new SpannableString(startDecimal);
+        assertEquals("123456", (digitsKeyListener.filter(source, 0, source.length(),
+                dest, 1, dest.length())).toString());
+        assertEquals(startDecimal, dest.toString());
+
+        source = "-123.456";
         String endSign = "789-";
         dest = new SpannableString(endSign);
         assertEquals("", (digitsKeyListener.filter(source, 0, source.length(),
                 dest, 0, dest.length() - 1)).toString());
         assertEquals(endSign, dest.toString());
 
+        endSign = "789+";
+        dest = new SpannableString(endSign);
+        assertEquals("", (digitsKeyListener.filter(source, 0, source.length(),
+                dest, 0, dest.length() - 1)).toString());
+        assertEquals(endSign, dest.toString());
+
         String startSign = "-789";
         dest = new SpannableString(startSign);
         assertEquals("123.456", (digitsKeyListener.filter(source, 0, source.length(),
                 dest, 1, dest.length())).toString());
         assertEquals(startSign, dest.toString());
+
+        source = "+123.456";
+        dest = new SpannableString(startSign);
+        assertEquals("123.456", (digitsKeyListener.filter(source, 0, source.length(),
+                dest, 1, dest.length())).toString());
+        assertEquals(startSign, dest.toString());
     }
 
     /**
@@ -346,12 +442,13 @@
 
     /**
      * Scenario description:
-     * Current accepted characters are '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-'.
+     * Current accepted characters are '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '+'.
      *  1. Press '-' key and check if the content of TextView becomes "-"
      *  2. Press '1' key and check if the content of TextView becomes "-1"
      *  3. Press '.' key and this key could not be accepted.
-     *  4. Press '2' key and check if the content of TextView becomes "-12"
-     *  5. Press '-' key and this key could not be accepted,
+     *  4. Press '+' key and this key could not be accepted.
+     *  5. Press '2' key and check if the content of TextView becomes "-12"
+     *  6. Press '-' key and this key could not be accepted,
      *     because text view accepts minus sign iff it at the beginning.
      */
     public void testDigitsKeyListener2() {
@@ -378,6 +475,10 @@
         sendKeys(KeyEvent.KEYCODE_PERIOD);
         assertEquals("-1", mTextView.getText().toString());
 
+        // press '+' key.
+        sendKeys(KeyEvent.KEYCODE_PLUS);
+        assertEquals("-1", mTextView.getText().toString());
+
         // press '2' key.
         sendKeys(KeyEvent.KEYCODE_2);
         assertEquals("-12", mTextView.getText().toString());
@@ -391,92 +492,98 @@
      * Scenario description:
      * Current accepted characters are '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.'.
      *  1. Press '-' key and check if the content of TextView becomes ""
-     *  2. Press '1' key and check if the content of TextView becomes "1"
-     *  3. Press '.' key and check if the content of TextView becomes "1."
-     *  4. Press '2' key and check if the content of TextView becomes "1.2"
-     *  5. Press '.' key and this key could not be accepted,
+     *  2. Press '+' key and check if the content of TextView becomes ""
+     *  3. Press '1' key and check if the content of TextView becomes "1"
+     *  4. Press '.' key and check if the content of TextView becomes "1."
+     *  5. Press '2' key and check if the content of TextView becomes "1.2"
+     *  6. Press '.' key and this key could not be accepted,
      *     because text view accepts only one decimal point per field.
      */
     public void testDigitsKeyListener3() {
-//        final DigitsKeyListener digitsKeyListener = DigitsKeyListener.getInstance(false, true);
-//
-//        mActivity.runOnUiThread(new Runnable() {
-//            public void run() {
-//                mTextView.setKeyListener(digitsKeyListener);
-//                mTextView.requestFocus();
-//            }
-//        });
-//        mInstrumentation.waitForIdleSync();
-//        assertEquals("", mTextView.getText().toString());
-//
-//        // press '-' key.
-//        sendKeys(KeyEvent.KEYCODE_MINUS);
-//        assertEquals("", mTextView.getText().toString());
-//
-//        // press '1' key.
-//        sendKeys(KeyEvent.KEYCODE_1);
-//        assertEquals("1", mTextView.getText().toString());
-//
-//        // press '.' key.
-//        sendKeys(KeyEvent.KEYCODE_PERIOD);
-//        assertEquals("1.", mTextView.getText().toString());
-//
-//        // press '2' key.
-//        sendKeys(KeyEvent.KEYCODE_2);
-//        assertEquals("1.2", mTextView.getText().toString());
-//
-//        // press '.' key.
-//        sendKeys(KeyEvent.KEYCODE_PERIOD);
-//        assertEquals("1.2", mTextView.getText().toString());
+        final DigitsKeyListener digitsKeyListener = DigitsKeyListener.getInstance(false, true);
+
+        mActivity.runOnUiThread(new Runnable() {
+            public void run() {
+                mTextView.setKeyListener(digitsKeyListener);
+                mTextView.requestFocus();
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+        assertEquals("", mTextView.getText().toString());
+
+        // press '-' key.
+        sendKeys(KeyEvent.KEYCODE_MINUS);
+        assertEquals("", mTextView.getText().toString());
+
+        // press '+' key.
+        sendKeys(KeyEvent.KEYCODE_PLUS);
+        assertEquals("", mTextView.getText().toString());
+
+        // press '1' key.
+        sendKeys(KeyEvent.KEYCODE_1);
+        assertEquals("1", mTextView.getText().toString());
+
+        // press '.' key.
+        sendKeys(KeyEvent.KEYCODE_PERIOD);
+        assertEquals("1.", mTextView.getText().toString());
+
+        // press '2' key.
+        sendKeys(KeyEvent.KEYCODE_2);
+        assertEquals("1.2", mTextView.getText().toString());
+
+        // press '.' key.
+        sendKeys(KeyEvent.KEYCODE_PERIOD);
+        assertEquals("1.2", mTextView.getText().toString());
     }
 
     /**
      * Scenario description:
-     * Current accepted characters are '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '.'.
-     *  1. Press '-' key and check if the content of TextView becomes "-"
-     *  2. Press '1' key and check if the content of TextView becomes "-1"
+     * Current accepted characters are '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '+',
+     * '.'.
+     *  1. Press '+' key and check if the content of TextView becomes "+"
+     *  2. Press '1' key and check if the content of TextView becomes "+1"
      *  3. Press '.' key and this key could not be accepted.
-     *  4. Press '2' key and check if the content of TextView becomes "-12"
+     *  4. Press '2' key and check if the content of TextView becomes "+12"
      *  5. Press '-' key and this key could not be accepted,
      *     because text view accepts minus sign iff it at the beginning.
      *  6. Press '.' key and this key could not be accepted,
      *     because text view accepts only one decimal point per field.
      */
     public void testDigitsKeyListener4() {
-//        final DigitsKeyListener digitsKeyListener = DigitsKeyListener.getInstance(true, true);
-//
-//        mActivity.runOnUiThread(new Runnable() {
-//            public void run() {
-//                mTextView.setKeyListener(digitsKeyListener);
-//                mTextView.requestFocus();
-//            }
-//        });
-//        mInstrumentation.waitForIdleSync();
-//        assertEquals("", mTextView.getText().toString());
-//
-//        // press '-' key.
-//        sendKeys(KeyEvent.KEYCODE_MINUS);
-//        assertEquals("-", mTextView.getText().toString());
-//
-//        // press '1' key.
-//        sendKeys(KeyEvent.KEYCODE_1);
-//        assertEquals("-1", mTextView.getText().toString());
-//
-//        // press '.' key.
-//        sendKeys(KeyEvent.KEYCODE_PERIOD);
-//        assertEquals("-1.", mTextView.getText().toString());
-//
-//        // press '2' key.
-//        sendKeys(KeyEvent.KEYCODE_2);
-//        assertEquals("-1.2", mTextView.getText().toString());
-//
-//        // press '-' key.
-//        sendKeys(KeyEvent.KEYCODE_MINUS);
-//        assertEquals("-1.2", mTextView.getText().toString());
-//
-//        // press '.' key.
-//        sendKeys(KeyEvent.KEYCODE_PERIOD);
-//        assertEquals("-1.2", mTextView.getText().toString());
+        final DigitsKeyListener digitsKeyListener = DigitsKeyListener.getInstance(true, true);
+
+        mActivity.runOnUiThread(new Runnable() {
+            public void run() {
+                mTextView.setKeyListener(digitsKeyListener);
+                mTextView.requestFocus();
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+        assertEquals("", mTextView.getText().toString());
+
+        // press '+' key.
+        sendKeys(KeyEvent.KEYCODE_PLUS);
+        assertEquals("+", mTextView.getText().toString());
+
+        // press '1' key.
+        sendKeys(KeyEvent.KEYCODE_1);
+        assertEquals("+1", mTextView.getText().toString());
+
+        // press '.' key.
+        sendKeys(KeyEvent.KEYCODE_PERIOD);
+        assertEquals("+1.", mTextView.getText().toString());
+
+        // press '2' key.
+        sendKeys(KeyEvent.KEYCODE_2);
+        assertEquals("+1.2", mTextView.getText().toString());
+
+        // press '-' key.
+        sendKeys(KeyEvent.KEYCODE_MINUS);
+        assertEquals("+1.2", mTextView.getText().toString());
+
+        // press '.' key.
+        sendKeys(KeyEvent.KEYCODE_PERIOD);
+        assertEquals("+1.2", mTextView.getText().toString());
     }
 
     /**
@@ -570,9 +677,9 @@
 
         final char[][] expected = new char[][] {
             new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' },
-            new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-' },
+            new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '+' },
             new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.' },
-            new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '.' },
+            new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '+', '.' },
         };
 
         TextMethodUtils.assertEquals(expected[0],
diff --git a/tests/tests/view/src/android/view/cts/DisplayTest.java b/tests/tests/view/src/android/view/cts/DisplayTest.java
deleted file mode 100644
index 489282a..0000000
--- a/tests/tests/view/src/android/view/cts/DisplayTest.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2009 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.view.cts;
-
-import android.content.Context;
-import android.test.AndroidTestCase;
-import android.util.DisplayMetrics;
-import android.view.Display;
-import android.view.WindowManager;
-
-public class DisplayTest extends AndroidTestCase {
-
-    /**
-     * Test the properties of Display, they are:
-     * 1 index of this display
-     * 2 height of this display in pixels
-     * 3 width of this display in pixels
-     * 4 orientation of this display
-     * 5 pixel format of this display
-     * 6 refresh rate of this display in frames per second
-     * 7 Initialize a DisplayMetrics object from this display's data
-     */
-    public void testGetDisplayAttrs() {
-        Context con = getContext();
-        WindowManager windowManager = (WindowManager) con.getSystemService(Context.WINDOW_SERVICE);
-        Display display = windowManager.getDefaultDisplay();
-
-        assertEquals(Display.DEFAULT_DISPLAY, display.getDisplayId());
-        assertTrue(0 < display.getHeight());
-        assertTrue(0 < display.getWidth());
-        display.getOrientation();
-        assertTrue(0 < display.getPixelFormat());
-        assertTrue(0 < display.getRefreshRate());
-
-        DisplayMetrics outMetrics = new DisplayMetrics();
-        outMetrics.setToDefaults();
-        display.getMetrics(outMetrics);
-        assertEquals(display.getHeight(), outMetrics.heightPixels);
-        assertEquals(display.getWidth(), outMetrics.widthPixels);
-
-        // The scale is in [0.1, 3], and density is the scale factor.
-        assertTrue(0.1f <= outMetrics.density && outMetrics.density <= 3.0f);
-        assertTrue(0.1f <= outMetrics.scaledDensity && outMetrics.density <= 3.0f);
-        assertTrue(0 < outMetrics.xdpi);
-        assertTrue(0 < outMetrics.ydpi);
-    }
-}
diff --git a/tests/tests/view/src/android/view/cts/ViewTreeObserverTest.java b/tests/tests/view/src/android/view/cts/ViewTreeObserverTest.java
index c5b56a4..1ee690b 100644
--- a/tests/tests/view/src/android/view/cts/ViewTreeObserverTest.java
+++ b/tests/tests/view/src/android/view/cts/ViewTreeObserverTest.java
@@ -35,7 +35,6 @@
 import android.view.ViewTreeObserver.OnTouchModeChangeListener;
 import android.widget.Button;
 import android.widget.LinearLayout;
-import android.widget.ListView;
 import android.widget.ScrollView;
 
 public class ViewTreeObserverTest extends ActivityInstrumentationTestCase2<MockActivity> {
@@ -69,8 +68,15 @@
 
     public void testAddOnGlobalFocusChangeListener() {
         final LinearLayout layout = (LinearLayout) mActivity.findViewById(R.id.linearlayout);
-        final ListView lv1 = (ListView) mActivity.findViewById(R.id.listview1);
-        final ListView lv2 = (ListView) mActivity.findViewById(R.id.listview2);
+        final View view1 = mActivity.findViewById(R.id.view1);
+        final View view2 = mActivity.findViewById(R.id.view2);
+
+        mInstrumentation.runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                view1.requestFocus();
+            }
+        });
 
         mViewTreeObserver = layout.getViewTreeObserver();
         final MockOnGlobalFocusChangeListener listener = new MockOnGlobalFocusChangeListener();
@@ -80,14 +86,15 @@
         mInstrumentation.runOnMainSync(new Runnable() {
             @Override
             public void run() {
-                layout.requestChildFocus(lv1, lv2);
+                view2.requestFocus();
             }
         });
         mInstrumentation.waitForIdleSync();
         new PollingCheck() {
             @Override
             protected boolean check() {
-                return listener.hasCalledOnGlobalFocusChanged();
+                return listener.hasCalledOnGlobalFocusChanged()
+                        && listener.oldFocus == view1 && listener.newFocus == view2;
             }
         }.run();
     }
@@ -144,16 +151,16 @@
     }
 
     public void testAddOnComputeInternalInsetsListener() {
-        final ListView lv1 = (ListView) mActivity.findViewById(R.id.listview1);
-        mViewTreeObserver = lv1.getViewTreeObserver();
+        final View view1 = mActivity.findViewById(R.id.view1);
+        mViewTreeObserver = view1.getViewTreeObserver();
 
         MockOnComputeInternalInsetsListener listener = new MockOnComputeInternalInsetsListener();
         mViewTreeObserver.addOnComputeInternalInsetsListener(listener);
     }
 
     public void testRemoveOnComputeInternalInsetsListener() {
-        final ListView lv1 = (ListView) mActivity.findViewById(R.id.listview1);
-        mViewTreeObserver = lv1.getViewTreeObserver();
+        final View view1 = mActivity.findViewById(R.id.view1);
+        mViewTreeObserver = view1.getViewTreeObserver();
 
         MockOnComputeInternalInsetsListener listener = new MockOnComputeInternalInsetsListener();
         mViewTreeObserver.removeOnComputeInternalInsetsListener(listener);
@@ -207,8 +214,15 @@
 
     public void testRemoveOnGlobalFocusChangeListener() {
         final LinearLayout layout = (LinearLayout) mActivity.findViewById(R.id.linearlayout);
-        final ListView lv1 = (ListView) mActivity.findViewById(R.id.listview1);
-        final ListView lv2 = (ListView) mActivity.findViewById(R.id.listview2);
+        final View view1 = mActivity.findViewById(R.id.view1);
+        final View view2 = mActivity.findViewById(R.id.view2);
+
+        mInstrumentation.runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                view1.requestFocus();
+            }
+        });
 
         mViewTreeObserver = layout.getViewTreeObserver();
         final MockOnGlobalFocusChangeListener listener = new MockOnGlobalFocusChangeListener();
@@ -217,14 +231,15 @@
         mInstrumentation.runOnMainSync(new Runnable() {
             @Override
             public void run() {
-                layout.requestChildFocus(lv1, lv2);
+                view2.requestFocus();
             }
         });
         mInstrumentation.waitForIdleSync();
         new PollingCheck() {
             @Override
             protected boolean check() {
-                return listener.hasCalledOnGlobalFocusChanged();
+                return listener.hasCalledOnGlobalFocusChanged()
+                        && listener.oldFocus == view1 && listener.newFocus == view2;
             }
         }.run();
 
@@ -234,7 +249,7 @@
         mInstrumentation.runOnMainSync(new Runnable() {
             @Override
             public void run() {
-                layout.requestChildFocus(lv1, lv2);
+                view1.requestFocus();
             }
         });
         mInstrumentation.waitForIdleSync();
@@ -345,10 +360,14 @@
 
     private class MockOnGlobalFocusChangeListener implements OnGlobalFocusChangeListener {
         private boolean mCalledOnGlobalFocusChanged = false;
+        View oldFocus;
+        View newFocus;
 
         @Override
         public void onGlobalFocusChanged(View oldFocus, View newFocus) {
             mCalledOnGlobalFocusChanged = true;
+            this.oldFocus = oldFocus;
+            this.newFocus = newFocus;
         }
 
         public boolean hasCalledOnGlobalFocusChanged() {
@@ -357,6 +376,8 @@
 
         public void reset() {
             mCalledOnGlobalFocusChanged = false;
+            oldFocus = null;
+            newFocus = null;
         }
     }
 
diff --git a/tests/tests/webkit/AndroidManifest.xml b/tests/tests/webkit/AndroidManifest.xml
index 9475451..493762e 100644
--- a/tests/tests/webkit/AndroidManifest.xml
+++ b/tests/tests/webkit/AndroidManifest.xml
@@ -19,6 +19,8 @@
     package="com.android.cts.webkit">
 
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"/>
+    <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/>
     <application>
         <uses-library android:name="android.test.runner" />
     </application>
diff --git a/tests/tests/webkit/src/android/webkit/cts/CacheManagerTest.java b/tests/tests/webkit/src/android/webkit/cts/CacheManagerTest.java
deleted file mode 100644
index 66b1e02..0000000
--- a/tests/tests/webkit/src/android/webkit/cts/CacheManagerTest.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2009 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.webkit.cts;
-
-import android.cts.util.PollingCheck;
-import android.test.ActivityInstrumentationTestCase2;
-import android.webkit.CacheManager;
-import android.webkit.CacheManager.CacheResult;
-
-
-import java.util.Map;
-
-public class CacheManagerTest extends ActivityInstrumentationTestCase2<WebViewStubActivity> {
-    private static final long CACHEMANAGER_INIT_TIMEOUT = 5000L;
-    private static final long NETWORK_OPERATION_TIMEOUT = 10000L;
-
-    private CtsTestServer mWebServer;
-    private WebViewOnUiThread mOnUiThread;
-
-    public CacheManagerTest() {
-        super("com.android.cts.stub", WebViewStubActivity.class);
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mOnUiThread = new WebViewOnUiThread(this, getActivity().getWebView());
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        mOnUiThread.cleanUp();
-        if (mWebServer != null) {
-            mWebServer.shutdown();
-        }
-        super.tearDown();
-    }
-
-    public void testGetCacheFileBaseDir() {
-        assertTrue(CacheManager.getCacheFileBaseDir().exists());
-    }
-
-    public void testCacheTransaction() {
-    }
-
-    public void testCacheFile() throws Exception {
-        mWebServer = new CtsTestServer(getActivity());
-        final String url = mWebServer.getAssetUrl(TestHtmlConstants.EMBEDDED_IMG_URL);
-
-        // Wait for CacheManager#init() finish.
-        new PollingCheck(CACHEMANAGER_INIT_TIMEOUT) {
-            @Override
-            protected boolean check() {
-                return CacheManager.getCacheFileBaseDir() != null;
-            }
-        }.run();
-
-        mOnUiThread.clearCache(true);
-        new PollingCheck(NETWORK_OPERATION_TIMEOUT) {
-            @Override
-            protected boolean check() {
-                CacheResult result = CacheManager.getCacheFile(url, null);
-                return result == null;
-            }
-        }.run();
-
-        mOnUiThread.loadUrlAndWaitForCompletion(url);
-        new PollingCheck(NETWORK_OPERATION_TIMEOUT) {
-            @Override
-            protected boolean check() {
-                CacheResult result = CacheManager.getCacheFile(url, null);
-                return result != null;
-            }
-        }.run();
-
-        // Can not test saveCacheFile(), because the output stream is null and
-        // saveCacheFile() will throw a NullPointerException.  There is no
-        // public API to set the output stream.
-    }
-
-    public void testCacheDisabled() {
-        // The cache should always be enabled.
-        assertFalse(CacheManager.cacheDisabled());
-    }
-}
diff --git a/tests/tests/webkit/src/android/webkit/cts/CacheManager_CacheResultTest.java b/tests/tests/webkit/src/android/webkit/cts/CacheManager_CacheResultTest.java
deleted file mode 100755
index 0c2a51a..0000000
--- a/tests/tests/webkit/src/android/webkit/cts/CacheManager_CacheResultTest.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2009 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.webkit.cts;
-
-import android.cts.util.PollingCheck;
-import android.test.ActivityInstrumentationTestCase2;
-import android.webkit.CacheManager;
-import android.webkit.CacheManager.CacheResult;
-import android.webkit.WebView;
-
-
-import org.apache.http.HttpStatus;
-import org.apache.http.impl.cookie.DateUtils;
-
-import java.io.File;
-import java.io.InputStream;
-import java.util.HashMap;
-import java.util.Map;
-
-public class CacheManager_CacheResultTest
-        extends ActivityInstrumentationTestCase2<WebViewStubActivity> {
-    private static final long NETWORK_OPERATION_TIMEOUT = 10000L;
-
-    private CtsTestServer mWebServer;
-    private WebViewOnUiThread mOnUiThread;
-
-    public CacheManager_CacheResultTest() {
-        super("com.android.cts.stub", WebViewStubActivity.class);
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mOnUiThread = new WebViewOnUiThread(this, getActivity().getWebView());
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        mOnUiThread.cleanUp();
-        if (mWebServer != null) {
-            mWebServer.shutdown();
-        }
-        super.tearDown();
-    }
-
-    public void testCacheResult() throws Exception {
-        final long validity = 5 * 60 * 1000; // 5 min
-        final long age = 30 * 60 * 1000; // 30 min
-        final long tolerance = 5 * 1000; // 5s
-
-        mWebServer = new CtsTestServer(getActivity());
-        final String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
-        mWebServer.setDocumentAge(age);
-        mWebServer.setDocumentValidity(validity);
-
-        mOnUiThread.clearCache(true);
-        new PollingCheck(NETWORK_OPERATION_TIMEOUT) {
-            @Override
-            protected boolean check() {
-                CacheResult result =
-                    CacheManager.getCacheFile(url, null);
-                return result == null;
-            }
-        }.run();
-        final long time = System.currentTimeMillis();
-        mOnUiThread.loadUrlAndWaitForCompletion(url);
-
-        Map<String, String> headers = new HashMap<String, String>();
-        CacheResult result = CacheManager.getCacheFile(url, headers);
-        assertTrue(headers.isEmpty());
-
-        assertNotNull(result);
-        assertNotNull(result.getInputStream());
-        assertTrue(result.getContentLength() > 0);
-        assertNull(result.getETag());
-        assertEquals((double)(time - age),
-                (double)DateUtils.parseDate(result.getLastModified()).getTime(),
-                (double)tolerance);
-        File file = new File(CacheManager.getCacheFileBaseDir().getPath(), result.getLocalPath());
-        assertTrue(file.exists());
-        assertNull(result.getLocation());
-        assertEquals("text/html", result.getMimeType());
-        assertNull(result.getOutputStream());
-        assertEquals((double)(time + validity), (double)result.getExpires(),
-                (double)tolerance);
-        assertEquals(HttpStatus.SC_OK, result.getHttpStatusCode());
-        assertNotNull(result.getEncoding());
-
-        result.setEncoding("iso-8859-1");
-        assertEquals("iso-8859-1", result.getEncoding());
-
-        result.setInputStream(null);
-        assertNull(result.getInputStream());
-    }
-}
diff --git a/tests/tests/webkit/src/android/webkit/cts/ChromeClient.java b/tests/tests/webkit/src/android/webkit/cts/ChromeClient.java
index 3df78a4..ca61b24 100644
--- a/tests/tests/webkit/src/android/webkit/cts/ChromeClient.java
+++ b/tests/tests/webkit/src/android/webkit/cts/ChromeClient.java
@@ -20,6 +20,7 @@
 
 // A chrome client for listening webview chrome events.
 class ChromeClient extends WaitForProgressClient {
+
     private boolean mIsMessageLevelAvailable;
     private ConsoleMessage.MessageLevel mMessageLevel;
 
@@ -32,7 +33,8 @@
         mMessageLevel = message.messageLevel();
         mIsMessageLevelAvailable = true;
         notify();
-        return true;
+        // return false for default handling; i.e. printing the message.
+        return false;
     }
 
     public synchronized ConsoleMessage.MessageLevel getMessageLevel(int timeout) {
@@ -45,4 +47,4 @@
         }
         return mMessageLevel;
     }
-}
\ No newline at end of file
+}
diff --git a/tests/tests/webkit/src/android/webkit/cts/CookieManagerTest.java b/tests/tests/webkit/src/android/webkit/cts/CookieManagerTest.java
index 51eae48..a0b4fa6 100755
--- a/tests/tests/webkit/src/android/webkit/cts/CookieManagerTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/CookieManagerTest.java
@@ -68,7 +68,7 @@
         CtsTestServer server = new CtsTestServer(getActivity(), false);
         String url = server.getCookieUrl("conquest.html");
         mOnUiThread.loadUrlAndWaitForCompletion(url);
-        assertEquals(null, mOnUiThread.getTitle()); // no cookies passed
+        assertEquals("0", mOnUiThread.getTitle()); // no cookies passed
         Thread.sleep(500);
         assertNull(mCookieManager.getCookie(url));
 
@@ -77,7 +77,7 @@
 
         url = server.getCookieUrl("war.html");
         mOnUiThread.loadUrlAndWaitForCompletion(url);
-        assertEquals(null, mOnUiThread.getTitle()); // no cookies passed
+        assertEquals("0", mOnUiThread.getTitle()); // no cookies passed
         waitForCookie(url);
         String cookie = mCookieManager.getCookie(url);
         assertNotNull(cookie);
@@ -89,7 +89,7 @@
 
         url = server.getCookieUrl("famine.html");
         mOnUiThread.loadUrlAndWaitForCompletion(url);
-        assertEquals("count=0", mOnUiThread.getTitle()); // outgoing cookie
+        assertEquals("1|count=0", mOnUiThread.getTitle()); // outgoing cookie
         waitForCookie(url);
         cookie = mCookieManager.getCookie(url);
         assertNotNull(cookie);
@@ -100,7 +100,7 @@
         url = server.getCookieUrl("death.html");
         mCookieManager.setCookie(url, "count=41");
         mOnUiThread.loadUrlAndWaitForCompletion(url);
-        assertEquals("count=41", mOnUiThread.getTitle()); // outgoing cookie
+        assertEquals("1|count=41", mOnUiThread.getTitle()); // outgoing cookie
         waitForCookie(url);
         cookie = mCookieManager.getCookie(url);
         assertNotNull(cookie);
diff --git a/tests/tests/webkit/src/android/webkit/cts/CookieTest.java b/tests/tests/webkit/src/android/webkit/cts/CookieTest.java
index 14a14d0..0c0396a 100644
--- a/tests/tests/webkit/src/android/webkit/cts/CookieTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/CookieTest.java
@@ -45,23 +45,6 @@
             }
         }
         assertFalse(mCookieManager.hasCookies());
-
-    }
-
-    public void testParse() {
-        String url = "http://www.foo.com";
-
-        // basic
-        mCookieManager.setCookie(url, "a=b");
-        String cookie = mCookieManager.getCookie(url);
-        assertTrue(cookie.equals("a=b"));
-
-        // quoted
-        mCookieManager.setCookie(url, "c=\"d;\"");
-        cookie = mCookieManager.getCookie(url);
-
-        assertTrue(cookie.contains("a=b"));
-        assertTrue(cookie.contains("c=\"d;\""));
     }
 
     public void testDomain() {
diff --git a/tests/tests/webkit/src/android/webkit/cts/GeolocationTest.java b/tests/tests/webkit/src/android/webkit/cts/GeolocationTest.java
new file mode 100644
index 0000000..1dafd3b
--- /dev/null
+++ b/tests/tests/webkit/src/android/webkit/cts/GeolocationTest.java
@@ -0,0 +1,514 @@
+/*
+ * Copyright (C) 2012 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.webkit.cts;
+
+import android.content.Context;
+import android.cts.util.PollingCheck;
+import android.graphics.Bitmap;
+import android.location.Criteria;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.location.LocationProvider;
+import android.os.Bundle;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.test.ActivityInstrumentationTestCase2;
+import android.webkit.CookieManager;
+import android.webkit.CookieSyncManager;
+import android.webkit.GeolocationPermissions;
+import android.webkit.JavascriptInterface;
+import android.webkit.WebChromeClient;
+import android.webkit.WebResourceResponse;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.webkit.cts.WebViewOnUiThread.WaitForLoadedClient;
+import android.webkit.cts.WebViewOnUiThread.WaitForProgressClient;
+
+import java.io.ByteArrayInputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.concurrent.Callable;
+import java.util.Date;
+import java.util.Random;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.Set;
+import java.util.TreeSet;
+
+import junit.framework.Assert;
+
+public class GeolocationTest extends ActivityInstrumentationTestCase2<WebViewStubActivity> {
+
+    // TODO Write additional tests to cover:
+    // - test that the errors are correct
+    // - test that use of gps and network location is correct
+
+    // The URLs does not matter since the tests will intercept the load, but it has to be a real
+    // url, and different domains.
+    private static final String URL_1 = "http://www.example.com";
+    private static final String URL_2 = "http://www.example.org";
+
+    private static final String JS_INTERFACE_NAME = "Android";
+    private static final int POLLING_TIMEOUT = 60 * 1000;
+    private static final String PROVIDER_NAME = "WebKitGeolocationTestLocationProvider";
+
+    // static HTML page always injected instead of the url loaded
+    private static final String RAW_HTML =
+            "<!DOCTYPE html>\n" +
+            "<html>\n" +
+            "  <head>\n" +
+            "    <title>Geolocation</title>\n" +
+            "    <script>\n" +
+            "      function gotPos(position) {\n" +
+            "        " + JS_INTERFACE_NAME + ".gotLocation();\n" +
+            "      }\n" +
+            "      function initiate_getCurrentPosition() {\n" +
+            "        navigator.geolocation.getCurrentPosition(\n" +
+            "            gotPos,\n" +
+            "            handle_errors,\n" +
+            "            {maximumAge:1000});\n" +
+            "      }\n" +
+            "      function handle_errors(error) {\n" +
+            "        switch(error.code) {\n" +
+            "          case error.PERMISSION_DENIED:\n" +
+            "            " + JS_INTERFACE_NAME + ".errorDenied(); break;\n" +
+            "          case error.POSITION_UNAVAILABLE:\n" +
+            "            " + JS_INTERFACE_NAME + ".errorUnavailable(); break;\n" +
+            "          case error.TIMEOUT:\n" +
+            "            " + JS_INTERFACE_NAME + ".errorTimeout(); break;\n" +
+            "          default: break;\n" +
+            "        }\n" +
+            "      }\n" +
+            "    </script>\n" +
+            "  </head>\n" +
+            "  <body onload=\"initiate_getCurrentPosition();\">\n" +
+            "  </body>\n" +
+            "</html>";
+
+    private JavascriptStatusReceiver mJavascriptStatusReceiver;
+    private LocationManager mLocationManager;
+    private WebViewOnUiThread mOnUiThread;
+
+    public GeolocationTest() throws Exception {
+        super("com.android.cts.stub", WebViewStubActivity.class);
+    }
+
+    // Both this test and WebViewOnUiThread need to override some of the methods on WebViewClient,
+    // so this test sublclasses the WebViewClient from WebViewOnUiThread
+    private static class InterceptClient extends WaitForLoadedClient {
+
+        public InterceptClient(WebViewOnUiThread webViewOnUiThread) throws Exception {
+            super(webViewOnUiThread);
+        }
+
+        @Override
+        public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
+            // Intercept all page loads with the same geolocation enabled page
+            try {
+                return new WebResourceResponse("text/html", "utf-8",
+                    new ByteArrayInputStream(RAW_HTML.getBytes("UTF-8")));
+            } catch(java.io.UnsupportedEncodingException e) {
+                return null;
+            }
+        }
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        // Set up a WebView with JavaScript and Geolocation enabled
+        final String GEO_DIR = "geo_test";
+        mOnUiThread = new WebViewOnUiThread(this, getActivity().getWebView());
+        mOnUiThread.getSettings().setJavaScriptEnabled(true);
+        mOnUiThread.getSettings().setGeolocationEnabled(true);
+        mOnUiThread.getSettings().setGeolocationDatabasePath(
+                getActivity().getApplicationContext().getDir(GEO_DIR, 0).getPath());
+
+        // Add a JsInterface to report back to the test when a location is received
+        mJavascriptStatusReceiver = new JavascriptStatusReceiver();
+        mOnUiThread.addJavascriptInterface(mJavascriptStatusReceiver, JS_INTERFACE_NAME);
+
+        // Always intercept all loads with the same geolocation test page
+        mOnUiThread.setWebViewClient(new InterceptClient(mOnUiThread));
+        // Clear all permissions before each test
+        GeolocationPermissions.getInstance().clearAll();
+        // Cache this mostly because the lookup is two lines of code
+        mLocationManager = (LocationManager)getActivity().getApplicationContext()
+                .getSystemService(Context.LOCATION_SERVICE);
+        // Add a test provider before each test to inject a location
+        addTestProvider(PROVIDER_NAME);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        // Remove the test provider after each test
+        try {
+          mLocationManager.removeTestProvider(PROVIDER_NAME);
+        } catch (IllegalArgumentException e) {} // Not much to do about this
+        mOnUiThread.cleanUp();
+        // This will null all member and static variables
+        super.tearDown();
+    }
+
+    // Update location with a fixed latitude and longtitude, sets the time to the current time.
+    private void updateLocation(final String providerName) {
+        Location location = new Location(providerName);
+        location.setLatitude(40);
+        location.setLongitude(40);
+        location.setAccuracy(1.0f);
+        location.setTime(java.lang.System.currentTimeMillis());
+        location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
+        mLocationManager.setTestProviderLocation(providerName, location);
+    }
+
+    // Need to set the location just after loading the url. Setting it after each load instead of
+    // using a maximum age.
+    private void loadUrlAndUpdateLocation(String url) {
+        mOnUiThread.loadUrlAndWaitForCompletion(url);
+        updateLocation(PROVIDER_NAME);
+    }
+
+    // WebChromeClient that accepts each location for one load. WebChromeClient is used in
+    // WebViewOnUiThread to detect when the page is loaded, so subclassing the one used there.
+    private static class TestSimpleGeolocationRequestWebChromeClient
+                extends WaitForProgressClient {
+        private boolean mReceivedRequest = false;
+        private final boolean mAccept;
+        private final boolean mRetain;
+
+        public TestSimpleGeolocationRequestWebChromeClient(
+                WebViewOnUiThread webViewOnUiThread, boolean accept, boolean retain) {
+            super(webViewOnUiThread);
+            this.mAccept = accept;
+            this.mRetain = retain;
+        }
+
+        @Override
+        public void onGeolocationPermissionsShowPrompt(
+                String origin, GeolocationPermissions.Callback callback) {
+            mReceivedRequest = true;
+            callback.invoke(origin, mAccept, mRetain);
+        }
+    }
+
+    private void addTestProvider(final String providerName) {
+        mLocationManager.addTestProvider(providerName,
+                true, //requiresNetwork,
+                false, // requiresSatellite,
+                true,  // requiresCell,
+                false, // hasMonetaryCost,
+                false, // supportsAltitude,
+                false, // supportsSpeed,
+                false, // supportsBearing,
+                Criteria.POWER_MEDIUM, // powerRequirement
+                Criteria.ACCURACY_FINE); // accuracy
+        mLocationManager.setTestProviderEnabled(providerName, true);
+    }
+
+    // Test loading a page and accepting the domain for one load
+    public void testSimpleGeolocationRequestAcceptOnce() throws Exception {
+        final TestSimpleGeolocationRequestWebChromeClient chromeClientAcceptOnce =
+                new TestSimpleGeolocationRequestWebChromeClient(mOnUiThread, true, false);
+        mOnUiThread.setWebChromeClient(chromeClientAcceptOnce);
+        loadUrlAndUpdateLocation(URL_1);
+        Callable<Boolean> receivedRequest = new Callable<Boolean>() {
+            @Override
+            public Boolean call() {
+                return chromeClientAcceptOnce.mReceivedRequest;
+            }
+        };
+        PollingCheck.check("Geolocation prompt not called", POLLING_TIMEOUT, receivedRequest);
+        Callable<Boolean> receivedLocation = new Callable<Boolean>() {
+            @Override
+            public Boolean call() {
+                return mJavascriptStatusReceiver.mHasPosition;
+            }
+        };
+        PollingCheck.check("JS didn't get position", POLLING_TIMEOUT, receivedLocation);
+        chromeClientAcceptOnce.mReceivedRequest = false;
+        // Load URL again, should receive callback again
+        loadUrlAndUpdateLocation(URL_1);
+        PollingCheck.check("Geolocation prompt not called", POLLING_TIMEOUT, receivedRequest);
+        PollingCheck.check("JS didn't get position", POLLING_TIMEOUT, receivedLocation);
+    }
+
+    private static class OriginCheck extends PollingCheck implements
+            android.webkit.ValueCallback<Set<String>> {
+
+        private boolean mReceived = false;
+        private final Set<String> mExpectedValue;
+        private Set<String> mReceivedValue = null;
+
+        public OriginCheck(Set<String> val) {
+            mExpectedValue = val;
+        }
+
+        @Override
+        protected boolean check() {
+            if (!mReceived) return false;
+            if (mExpectedValue.equals(mReceivedValue)) return true;
+            if (mExpectedValue.size() != mReceivedValue.size()) return false;
+            // Origins can have different strings even if they represent the same origin,
+            // for example http://www.example.com is the same origin as http://www.example.com/
+            // and they are both valid representations
+            for (String origin : mReceivedValue) {
+                if (mExpectedValue.contains(origin)) continue;
+                if (origin.endsWith("/")) {
+                    if (mExpectedValue.contains(origin.substring(0, origin.length() - 1))) {
+                        continue;
+                    }
+                } else {
+                    if (mExpectedValue.contains(origin + "/")) continue;
+                }
+                return false;
+            }
+            return true;
+        }
+        @Override
+        public void onReceiveValue(Set<String> value) {
+            mReceived = true;
+            mReceivedValue = value;
+        }
+    }
+
+    // Class that waits and checks for a particular value being received
+    private static class BooleanCheck extends PollingCheck implements
+            android.webkit.ValueCallback<Boolean> {
+
+        private boolean mReceived = false;
+        private final boolean mExpectedValue;
+        private boolean mReceivedValue;
+
+        public BooleanCheck(boolean val) {
+            mExpectedValue = val;
+        }
+
+        @Override
+        protected boolean check() {
+            return mReceived && mReceivedValue == mExpectedValue;
+        }
+
+        @Override
+        public void onReceiveValue(Boolean value) {
+            mReceived = true;
+            mReceivedValue = value;
+        }
+    }
+
+    // Test loading a page and retaining the domain forever
+    public void testSimpleGeolocationRequestAcceptAlways() throws Exception {
+        final TestSimpleGeolocationRequestWebChromeClient chromeClientAcceptAlways =
+                new TestSimpleGeolocationRequestWebChromeClient(mOnUiThread, true, true);
+        mOnUiThread.setWebChromeClient(chromeClientAcceptAlways);
+        // Load url once, and the callback should accept the domain for all future loads
+        loadUrlAndUpdateLocation(URL_1);
+        Callable<Boolean> receivedRequest = new Callable<Boolean>() {
+            @Override
+            public Boolean call() {
+                return chromeClientAcceptAlways.mReceivedRequest;
+            }
+        };
+        PollingCheck.check("Geolocation prompt not called", POLLING_TIMEOUT, receivedRequest);
+        Callable<Boolean> receivedLocation = new Callable<Boolean>() {
+            @Override
+            public Boolean call() {
+                return mJavascriptStatusReceiver.mHasPosition;
+            }
+        };
+        PollingCheck.check("JS didn't get position", POLLING_TIMEOUT, receivedLocation);
+        chromeClientAcceptAlways.mReceivedRequest = false;
+        mJavascriptStatusReceiver.clearState();
+        // Load the same URL again
+        loadUrlAndUpdateLocation(URL_1);
+        PollingCheck.check("JS didn't get position", POLLING_TIMEOUT, receivedLocation);
+        // Assert prompt for geolocation permission is not called the second time
+        assertFalse(chromeClientAcceptAlways.mReceivedRequest);
+        // Check that the permission is in GeolocationPermissions
+        BooleanCheck trueCheck = new BooleanCheck(true);
+        GeolocationPermissions.getInstance().getAllowed(URL_1, trueCheck);
+        trueCheck.run();
+        Set<String> acceptedOrigins = new TreeSet<String>();
+        acceptedOrigins.add(URL_1);
+        OriginCheck originCheck = new OriginCheck(acceptedOrigins);
+        GeolocationPermissions.getInstance().getOrigins(originCheck);
+        originCheck.run();
+
+        // URL_2 should get a prompt
+        chromeClientAcceptAlways.mReceivedRequest = false;
+        loadUrlAndUpdateLocation(URL_2);
+        // Checking the callback for geolocation permission prompt is called
+        PollingCheck.check("Geolocation prompt not called", POLLING_TIMEOUT, receivedRequest);
+        PollingCheck.check("JS didn't get position", POLLING_TIMEOUT, receivedLocation);
+        acceptedOrigins.add(URL_2);
+        originCheck = new OriginCheck(acceptedOrigins);
+        GeolocationPermissions.getInstance().getOrigins(originCheck);
+        originCheck.run();
+        // Remove a domain manually that was added by the callback
+        GeolocationPermissions.getInstance().clear(URL_1);
+        acceptedOrigins.remove(URL_1);
+        originCheck = new OriginCheck(acceptedOrigins);
+        GeolocationPermissions.getInstance().getOrigins(originCheck);
+        originCheck.run();
+    }
+
+    // Test the GeolocationPermissions API
+    public void testGeolocationPermissions() {
+        Set<String> acceptedOrigins = new TreeSet<String>();
+        BooleanCheck falseCheck = new BooleanCheck(false);
+        GeolocationPermissions.getInstance().getAllowed(URL_2, falseCheck);
+        falseCheck.run();
+        OriginCheck originCheck = new OriginCheck(acceptedOrigins);
+        GeolocationPermissions.getInstance().getOrigins(originCheck);
+        originCheck.run();
+
+        // Remove a domain that has not been allowed
+        GeolocationPermissions.getInstance().clear(URL_2);
+        acceptedOrigins.remove(URL_2);
+        originCheck = new OriginCheck(acceptedOrigins);
+        GeolocationPermissions.getInstance().getOrigins(originCheck);
+        originCheck.run();
+
+        // Add a domain
+        acceptedOrigins.add(URL_2);
+        GeolocationPermissions.getInstance().allow(URL_2);
+        originCheck = new OriginCheck(acceptedOrigins);
+        GeolocationPermissions.getInstance().getOrigins(originCheck);
+        originCheck.run();
+        BooleanCheck trueCheck = new BooleanCheck(true);
+        GeolocationPermissions.getInstance().getAllowed(URL_2, trueCheck);
+        trueCheck.run();
+
+        // Add a domain
+        acceptedOrigins.add(URL_1);
+        GeolocationPermissions.getInstance().allow(URL_1);
+        originCheck = new OriginCheck(acceptedOrigins);
+        GeolocationPermissions.getInstance().getOrigins(originCheck);
+        originCheck.run();
+
+        // Remove a domain that has been allowed
+        GeolocationPermissions.getInstance().clear(URL_2);
+        acceptedOrigins.remove(URL_2);
+        originCheck = new OriginCheck(acceptedOrigins);
+        GeolocationPermissions.getInstance().getOrigins(originCheck);
+        originCheck.run();
+        falseCheck = new BooleanCheck(false);
+        GeolocationPermissions.getInstance().getAllowed(URL_2, falseCheck);
+        falseCheck.run();
+
+        // Try to clear all domains
+        GeolocationPermissions.getInstance().clearAll();
+        acceptedOrigins.clear();
+        originCheck = new OriginCheck(acceptedOrigins);
+        GeolocationPermissions.getInstance().getOrigins(originCheck);
+        originCheck.run();
+
+        // Add a domain
+        acceptedOrigins.add(URL_1);
+        GeolocationPermissions.getInstance().allow(URL_1);
+        originCheck = new OriginCheck(acceptedOrigins);
+        GeolocationPermissions.getInstance().getOrigins(originCheck);
+        originCheck.run();
+    }
+
+    // Test loading pages and checks rejecting once and recjecting the domain forever
+    public void testSimpleGeolocationRequestReject() throws Exception {
+        final TestSimpleGeolocationRequestWebChromeClient chromeClientRejectOnce =
+                new TestSimpleGeolocationRequestWebChromeClient(mOnUiThread, false, false);
+        mOnUiThread.setWebChromeClient(chromeClientRejectOnce);
+        // Load url once, and the callback should accept the domain for all future loads
+        mOnUiThread.loadUrlAndWaitForCompletion(URL_1);
+        Callable<Boolean> receivedRequest = new Callable<Boolean>() {
+            @Override
+            public Boolean call() {
+                return chromeClientRejectOnce.mReceivedRequest;
+            }
+        };
+        PollingCheck.check("Geolocation prompt not called", POLLING_TIMEOUT, receivedRequest);
+        Callable<Boolean> locationDenied = new Callable<Boolean>() {
+            @Override
+            public Boolean call() {
+                return mJavascriptStatusReceiver.mDenied;
+            }
+        };
+        PollingCheck.check("JS didn't get position", POLLING_TIMEOUT, locationDenied);
+        // Same result should happen on next run
+        mOnUiThread.loadUrlAndWaitForCompletion(URL_1);
+        PollingCheck.check("Geolocation prompt not called", POLLING_TIMEOUT, receivedRequest);
+        PollingCheck.check("JS didn't get position", POLLING_TIMEOUT, locationDenied);
+
+        // Try to reject forever
+        final TestSimpleGeolocationRequestWebChromeClient chromeClientRejectAlways =
+            new TestSimpleGeolocationRequestWebChromeClient(mOnUiThread, false, true);
+        mOnUiThread.setWebChromeClient(chromeClientRejectAlways);
+        mOnUiThread.loadUrlAndWaitForCompletion(URL_2);
+        PollingCheck.check("Geolocation prompt not called", POLLING_TIMEOUT, receivedRequest);
+        PollingCheck.check("JS didn't get position", POLLING_TIMEOUT, locationDenied);
+        // second load should now not get a prompt
+        chromeClientRejectAlways.mReceivedRequest = false;
+        mOnUiThread.loadUrlAndWaitForCompletion(URL_2);
+        PollingCheck.check("JS didn't get position", POLLING_TIMEOUT, locationDenied);
+        PollingCheck.check("Geolocation prompt not called", POLLING_TIMEOUT, receivedRequest);
+
+        // Test if it gets added to origins
+        Set<String> acceptedOrigins = new TreeSet<String>();
+        acceptedOrigins.add(URL_2);
+        OriginCheck domainCheck = new OriginCheck(acceptedOrigins);
+        GeolocationPermissions.getInstance().getOrigins(domainCheck);
+        domainCheck.run();
+        // And now check that getAllowed returns false
+        BooleanCheck falseCheck = new BooleanCheck(false);
+        GeolocationPermissions.getInstance().getAllowed(URL_1, falseCheck);
+        falseCheck.run();
+    }
+
+    // Object added to the page via AddJavascriptInterface() that is used by the test Javascript to
+    // notify back to Java when a location or error is received.
+    public final static class JavascriptStatusReceiver {
+        public volatile boolean mHasPosition = false;
+        public volatile boolean mDenied = false;
+        public volatile boolean mUnavailable = false;
+        public volatile boolean mTimeout = false;
+
+        public void clearState() {
+            mHasPosition = false;
+            mDenied = false;
+            mUnavailable = false;
+            mTimeout = false;
+        }
+
+        @JavascriptInterface
+        public void errorDenied() {
+            mDenied = true;
+        }
+
+        @JavascriptInterface
+        public void errorUnavailable() {
+            mUnavailable = true;
+        }
+
+        @JavascriptInterface
+        public void errorTimeout() {
+            mTimeout = true;
+        }
+
+        @JavascriptInterface
+        public void gotLocation() {
+            mHasPosition = true;
+        }
+    }
+}
diff --git a/tests/tests/webkit/src/android/webkit/cts/HttpAuthHandlerTest.java b/tests/tests/webkit/src/android/webkit/cts/HttpAuthHandlerTest.java
index 2fcf53c..52e9278 100644
--- a/tests/tests/webkit/src/android/webkit/cts/HttpAuthHandlerTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/HttpAuthHandlerTest.java
@@ -28,6 +28,11 @@
 
     private static final long TIMEOUT = 10000;
 
+    private static final String WRONG_USERNAME = "wrong_user";
+    private static final String WRONG_PASSWORD = "wrong_password";
+    private static final String CORRECT_USERNAME = CtsTestServer.AUTH_USER;
+    private static final String CORRECT_PASSWORD = CtsTestServer.AUTH_PASS;
+
     private CtsTestServer mWebServer;
     private WebViewOnUiThread mOnUiThread;
 
@@ -50,64 +55,18 @@
         super.tearDown();
     }
 
-    public void testProceed() throws Exception {
-        mWebServer = new CtsTestServer(getActivity());
-        String url = mWebServer.getAuthAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
-
-        // wrong credentials
-        MyWebViewClient client = new MyWebViewClient(true, "FakeUser", "FakePass");
-        mOnUiThread.setWebViewClient(client);
-
-        mOnUiThread.loadUrlAndWaitForCompletion(url);
-        assertEquals(CtsTestServer.AUTH_REALM, client.realm);
-        assertEquals(CtsTestServer.getReasonString(HttpStatus.SC_UNAUTHORIZED), mOnUiThread.getTitle());
-        assertTrue(client.useHttpAuthUsernamePassword);
-
-        // missing credentials
-        client = new MyWebViewClient(true, null, null);
-        mOnUiThread.setWebViewClient(client);
-
-        mOnUiThread.loadUrlAndWaitForCompletion(url);
-        assertEquals(CtsTestServer.AUTH_REALM, client.realm);
-        assertEquals(
-                CtsTestServer.getReasonString(HttpStatus.SC_UNAUTHORIZED), mOnUiThread.getTitle());
-        assertTrue(client.useHttpAuthUsernamePassword);
-
-        // correct credentials
-        client = new MyWebViewClient(true, CtsTestServer.AUTH_USER, CtsTestServer.AUTH_PASS);
-        mOnUiThread.setWebViewClient(client);
-
-        mOnUiThread.loadUrlAndWaitForCompletion(url);
-        assertEquals(CtsTestServer.AUTH_REALM, client.realm);
-        assertEquals(TestHtmlConstants.HELLO_WORLD_TITLE, mOnUiThread.getTitle());
-        assertTrue(client.useHttpAuthUsernamePassword);
-    }
-
-    public void testCancel() throws Exception {
-        mWebServer = new CtsTestServer(getActivity());
-
-        String url = mWebServer.getAuthAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
-        MyWebViewClient client = new MyWebViewClient(false, null, null);
-        mOnUiThread.setWebViewClient(client);
-
-        mOnUiThread.loadUrlAndWaitForCompletion(url);
-        assertEquals(CtsTestServer.AUTH_REALM, client.realm);
-        assertEquals(
-                CtsTestServer.getReasonString(HttpStatus.SC_UNAUTHORIZED), mOnUiThread.getTitle());
-    }
-
-    private class MyWebViewClient extends WaitForLoadedClient {
+    private class ProceedHttpAuthClient extends WaitForLoadedClient {
         String realm;
         boolean useHttpAuthUsernamePassword;
 
-        private boolean mProceed;
+        private int mMaxAuthAttempts;
         private String mUser;
         private String mPassword;
         private int mAuthCount;
 
-        MyWebViewClient(boolean proceed, String user, String password) {
+        ProceedHttpAuthClient(int maxAuthAttempts, String user, String password) {
             super(mOnUiThread);
-            mProceed = proceed;
+            mMaxAuthAttempts = maxAuthAttempts;
             mUser = user;
             mPassword = password;
         }
@@ -115,18 +74,113 @@
         @Override
         public void onReceivedHttpAuthRequest(WebView view,
                 HttpAuthHandler handler, String host, String realm) {
-            ++mAuthCount;
-            if (mAuthCount > 1) {
+            if (++mAuthCount > mMaxAuthAttempts) {
                 handler.cancel();
                 return;
             }
+
             this.realm = realm;
             this.useHttpAuthUsernamePassword = handler.useHttpAuthUsernamePassword();
-            if (mProceed) {
-                handler.proceed(mUser, mPassword);
-            } else {
-                handler.cancel();
-            }
+
+            handler.proceed(mUser, mPassword);
         }
     }
+
+    private class CancelHttpAuthClient extends WaitForLoadedClient {
+        String realm;
+
+        CancelHttpAuthClient() {
+            super(mOnUiThread);
+        }
+
+        @Override
+        public void onReceivedHttpAuthRequest(WebView view,
+                HttpAuthHandler handler, String host, String realm) {
+            this.realm = realm;
+            handler.cancel();
+        }
+    }
+
+    private void incorrectCredentialsAccessDenied(String url) throws Throwable {
+        ProceedHttpAuthClient client = new ProceedHttpAuthClient(1, WRONG_USERNAME, WRONG_PASSWORD);
+        mOnUiThread.setWebViewClient(client);
+
+        // As we're providing incorrect credentials, the page should complete but at
+        // an access denied page.
+        mOnUiThread.loadUrlAndWaitForCompletion(url);
+
+        assertEquals(CtsTestServer.AUTH_REALM, client.realm);
+        assertEquals(CtsTestServer.getReasonString(HttpStatus.SC_UNAUTHORIZED), mOnUiThread.getTitle());
+    }
+
+    private void missingCredentialsAccessDenied(String url) throws Throwable {
+        ProceedHttpAuthClient client = new ProceedHttpAuthClient(1, null, null);
+        mOnUiThread.setWebViewClient(client);
+
+        // As we're providing no credentials, the page should complete but at
+        // an access denied page.
+        mOnUiThread.loadUrlAndWaitForCompletion(url);
+
+        assertEquals(CtsTestServer.AUTH_REALM, client.realm);
+        assertEquals(CtsTestServer.getReasonString(HttpStatus.SC_UNAUTHORIZED), mOnUiThread.getTitle());
+    }
+
+    private void correctCredentialsAccessGranted(String url) throws Throwable {
+        ProceedHttpAuthClient client = new ProceedHttpAuthClient(1, CORRECT_USERNAME, CORRECT_PASSWORD);
+        mOnUiThread.setWebViewClient(client);
+
+        // As we're providing valid credentials, the page should complete and
+        // at the page we requested.
+        mOnUiThread.loadUrlAndWaitForCompletion(url);
+
+        assertEquals(CtsTestServer.AUTH_REALM, client.realm);
+        assertEquals(TestHtmlConstants.HELLO_WORLD_TITLE, mOnUiThread.getTitle());
+    }
+
+    public void testProceed() throws Throwable {
+        mWebServer = new CtsTestServer(getActivity());
+        String url = mWebServer.getAuthAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
+
+        incorrectCredentialsAccessDenied(url);
+        missingCredentialsAccessDenied(url);
+        correctCredentialsAccessGranted(url);
+    }
+
+    public void testCancel() throws Throwable {
+        mWebServer = new CtsTestServer(getActivity());
+        String url = mWebServer.getAuthAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
+
+        CancelHttpAuthClient client = new CancelHttpAuthClient();
+        mOnUiThread.setWebViewClient(client);
+
+        mOnUiThread.loadUrlAndWaitForCompletion(url);
+        assertEquals(CtsTestServer.AUTH_REALM, client.realm);
+        assertEquals(CtsTestServer.getReasonString(HttpStatus.SC_UNAUTHORIZED), mOnUiThread.getTitle());
+    }
+
+    public void testUseHttpAuthUsernamePassword() throws Throwable {
+        mWebServer = new CtsTestServer(getActivity());
+        String url = mWebServer.getAuthAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
+
+        // Try to login once with incorrect credentials. This should cause
+        // useHttpAuthUsernamePassword to be true in the callback, as at that point
+        // we don't yet know that the credentials we will use are invalid.
+        ProceedHttpAuthClient client = new ProceedHttpAuthClient(1, WRONG_USERNAME, WRONG_PASSWORD);
+        mOnUiThread.setWebViewClient(client);
+        mOnUiThread.loadUrlAndWaitForCompletion(url);
+        assertEquals(CtsTestServer.AUTH_REALM, client.realm);
+        assertEquals(CtsTestServer.getReasonString(HttpStatus.SC_UNAUTHORIZED), mOnUiThread.getTitle());
+        assertTrue(client.useHttpAuthUsernamePassword);
+
+        // Try to login twice with invalid credentials. This should cause
+        // useHttpAuthUsernamePassword to return false, as the credentials
+        // we would have stored on the first auth request
+        // are not suitable for use the second time.
+        client = new ProceedHttpAuthClient(2, WRONG_USERNAME, WRONG_PASSWORD);
+        mOnUiThread.setWebViewClient(client);
+        mOnUiThread.loadUrlAndWaitForCompletion(url);
+        assertEquals(CtsTestServer.AUTH_REALM, client.realm);
+        assertEquals(CtsTestServer.getReasonString(HttpStatus.SC_UNAUTHORIZED), mOnUiThread.getTitle());
+        assertFalse(client.useHttpAuthUsernamePassword);
+    }
 }
diff --git a/tests/tests/webkit/src/android/webkit/cts/TestHtmlConstants.java b/tests/tests/webkit/src/android/webkit/cts/TestHtmlConstants.java
index 63354d4..47ec475 100644
--- a/tests/tests/webkit/src/android/webkit/cts/TestHtmlConstants.java
+++ b/tests/tests/webkit/src/android/webkit/cts/TestHtmlConstants.java
@@ -62,6 +62,8 @@
     public static final String ANCHOR_ASSET_URL = "webkit/test_anchor.html";
     public static final String IMAGE_ACCESS_URL = "webkit/test_imageaccess.html";
     public static final String IFRAME_ACCESS_URL = "webkit/test_iframeaccess.html";
+    public static final String DATABASE_ACCESS_URL = "webkit/test_databaseaccess.html";
+    public static final String STOP_LOADING_URL = "webkit/test_stop_loading.html";
 
     // Must match the title of the page at
     // android/frameworks/base/core/res/res/raw/loaderror.html
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebChromeClientTest.java b/tests/tests/webkit/src/android/webkit/cts/WebChromeClientTest.java
index 4a536b8..2a5044e 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebChromeClientTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebChromeClientTest.java
@@ -20,6 +20,7 @@
 import android.graphics.Bitmap;
 import android.os.Message;
 import android.test.ActivityInstrumentationTestCase2;
+import android.view.ViewGroup;
 import android.webkit.JsPromptResult;
 import android.webkit.JsResult;
 import android.webkit.WebIconDatabase;
@@ -27,7 +28,6 @@
 import android.webkit.WebView;
 import android.webkit.cts.WebViewOnUiThread.WaitForProgressClient;
 
-
 public class WebChromeClientTest extends ActivityInstrumentationTestCase2<WebViewStubActivity> {
     private static final long TEST_TIMEOUT = 5000L;
 
@@ -64,7 +64,8 @@
         mOnUiThread.setWebChromeClient(webChromeClient);
 
         assertFalse(webChromeClient.hadOnProgressChanged());
-        mOnUiThread.loadUrlAndWaitForCompletion(TestHtmlConstants.HELLO_WORLD_URL);
+        String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
+        mOnUiThread.loadUrlAndWaitForCompletion(url);
 
         new PollingCheck(TEST_TIMEOUT) {
             @Override
@@ -132,8 +133,8 @@
 
         assertFalse(webChromeClient.hadOnCreateWindow());
 
-        // load a page that opens a child window, requests focus for the child and sets a timeout
-        // after which the child will be closed
+        // Load a page that opens a child window and sets a timeout after which the child
+        // will be closed.
         mOnUiThread.loadUrlAndWaitForCompletion(mWebServer.
                 getAssetUrl(TestHtmlConstants.JS_WINDOW_URL));
 
@@ -143,7 +144,6 @@
                 return webChromeClient.hadOnCreateWindow();
             }
         }.run();
-        assertFalse(webChromeClient.hadOnRequestFocus());
         new PollingCheck(TEST_TIMEOUT) {
             @Override
             protected boolean check() {
@@ -388,6 +388,8 @@
             transport.setWebView(childView);
             resultMsg.sendToTarget();
             mHadOnCreateWindow = true;
+            getActivity().addContentView(childView, new ViewGroup.LayoutParams(
+                    ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
             return true;
         }
 
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebHistoryItemTest.java b/tests/tests/webkit/src/android/webkit/cts/WebHistoryItemTest.java
index 4f2113b..2eb81fc 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebHistoryItemTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebHistoryItemTest.java
@@ -55,7 +55,6 @@
         assertEquals(1, list.getSize());
         WebHistoryItem item = list.getCurrentItem();
         assertNotNull(item);
-        int firstId = item.getId();
         assertEquals(url, item.getUrl());
         assertEquals(url, item.getOriginalUrl());
         assertEquals(TestHtmlConstants.HELLO_WORLD_TITLE, item.getTitle());
@@ -69,7 +68,5 @@
         item = list.getCurrentItem();
         assertNotNull(item);
         assertEquals(TestHtmlConstants.BR_TAG_TITLE, item.getTitle());
-        int secondId = item.getId();
-        assertTrue(firstId != secondId);
     }
 }
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java b/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java
index 054217e..b9c338b 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java
@@ -25,6 +25,7 @@
 import android.webkit.WebIconDatabase;
 import android.webkit.WebSettings;
 import android.webkit.WebSettings.TextSize;
+import android.webkit.WebStorage;
 import android.webkit.WebView;
 import android.webkit.cts.WebViewOnUiThread.WaitForProgressClient;
 import java.io.FileOutputStream;
@@ -155,47 +156,6 @@
         assertEquals(customUserAgent, mOnUiThread.getTitle());
     }
 
-    @SuppressWarnings("deprecation")
-    public void testAccessUserAgent() throws Exception {
-        startWebServer();
-        String url = mWebServer.getUserAgentUrl();
-
-        mSettings.setUserAgent(1);
-        assertEquals(1, mSettings.getUserAgent());
-        mOnUiThread.loadUrlAndWaitForCompletion(url);
-        String userAgent1 = mOnUiThread.getTitle();
-        assertNotNull(userAgent1);
-
-        mSettings.setUserAgent(3);
-        assertEquals(1, mSettings.getUserAgent());
-        mOnUiThread.loadUrlAndWaitForCompletion(url);
-        assertEquals(userAgent1, mOnUiThread.getTitle());
-
-        mSettings.setUserAgent(2);
-        assertEquals(2, mSettings.getUserAgent());
-        mOnUiThread.loadUrlAndWaitForCompletion(url);
-        String userAgent2 = mOnUiThread.getTitle();
-        assertNotNull(userAgent2);
-
-        mSettings.setUserAgent(3);
-        assertEquals(2, mSettings.getUserAgent());
-        mOnUiThread.loadUrlAndWaitForCompletion(url);
-        assertEquals(userAgent2, mOnUiThread.getTitle());
-
-        mSettings.setUserAgent(0);
-        assertEquals(0, mSettings.getUserAgent());
-        mOnUiThread.loadUrlAndWaitForCompletion(url);
-        String userAgent0 = mOnUiThread.getTitle();
-        assertNotNull(userAgent0);
-
-        final String customUserAgent = "Cts/Test";
-        mSettings.setUserAgentString(customUserAgent);
-        assertEquals(-1, mSettings.getUserAgent());
-        mOnUiThread.loadUrlAndWaitForCompletion(url);
-        assertEquals(customUserAgent, mOnUiThread.getTitle());
-    }
-
-
     public void testAccessAllowFileAccess() {
         // This test is not compatible with 4.0.3
         if ("4.0.3".equals(Build.VERSION.RELEASE)) {
@@ -387,11 +347,9 @@
         new PollingCheck(WEBVIEW_TIMEOUT) {
             @Override
             protected boolean check() {
-                String title = mOnUiThread.getTitle();
-                return title != null && title.length() > 0;
+                return "Popup blocked".equals(mOnUiThread.getTitle());
             }
         }.run();
-        assertEquals("Popup blocked", mOnUiThread.getTitle());
 
         mSettings.setJavaScriptCanOpenWindowsAutomatically(true);
         assertTrue(mSettings.getJavaScriptCanOpenWindowsAutomatically());
@@ -399,15 +357,9 @@
         new PollingCheck(WEBVIEW_TIMEOUT) {
             @Override
             protected boolean check() {
-                String title = mOnUiThread.getTitle();
-                // The title may not change immediately after loading, so
-                // we have to discount the initial "Popup blocked" from the
-                // previous load.
-                return title != null && title.length() > 0
-                        && !title.equals("Popup blocked");
+                return "Popup allowed".equals(mOnUiThread.getTitle());
             }
         }.run();
-        assertEquals("Popup allowed", mOnUiThread.getTitle());
     }
 
     public void testAccessJavaScriptEnabled() throws Exception {
@@ -417,10 +369,9 @@
         new PollingCheck(WEBVIEW_TIMEOUT) {
             @Override
             protected boolean check() {
-                return mOnUiThread.getTitle() != null;
+                return "javascript on".equals(mOnUiThread.getTitle());
             }
         }.run();
-        assertEquals("javascript on", mOnUiThread.getTitle());
 
         mSettings.setJavaScriptEnabled(false);
         assertFalse(mSettings.getJavaScriptEnabled());
@@ -428,10 +379,10 @@
         new PollingCheck(WEBVIEW_TIMEOUT) {
             @Override
             protected boolean check() {
-                return mOnUiThread.getTitle() != null;
+                return "javascript off".equals(mOnUiThread.getTitle());
             }
         }.run();
-        assertEquals("javascript off", mOnUiThread.getTitle());
+
     }
 
     public void testAccessLayoutAlgorithm() {
@@ -444,13 +395,6 @@
         assertEquals(WebSettings.LayoutAlgorithm.SINGLE_COLUMN, mSettings.getLayoutAlgorithm());
     }
 
-    public void testAccessLightTouchEnabled() {
-        assertFalse(mSettings.getLightTouchEnabled());
-
-        mSettings.setLightTouchEnabled(true);
-        assertTrue(mSettings.getLightTouchEnabled());
-    }
-
     public void testAccessMinimumFontSize() {
         assertEquals(8, mSettings.getMinimumFontSize());
 
@@ -477,13 +421,6 @@
         assertEquals(10, mSettings.getMinimumLogicalFontSize());
     }
 
-    public void testAccessNavDump() {
-        assertFalse(mSettings.getNavDump());
-
-        mSettings.setNavDump(true);
-        assertTrue(mSettings.getNavDump());
-    }
-
     public void testAccessPluginsEnabled() {
         assertFalse(mSettings.getPluginsEnabled());
 
@@ -507,13 +444,6 @@
         assertFalse(mSettings.getSaveFormData());
     }
 
-    public void testAccessSavePassword() {
-        assertTrue(mSettings.getSavePassword());
-
-        mSettings.setSavePassword(false);
-        assertFalse(mSettings.getSavePassword());
-    }
-
     public void testAccessTextSize() {
         assertEquals(TextSize.NORMAL, mSettings.getTextSize());
 
@@ -646,6 +576,32 @@
         }.run();
     }
 
+    // Ideally, we need a test case for the enabled case. However, it seems that
+    // enabling the database should happen prior to navigating the first url due to
+    // some internal limitations of webview. For this reason, we only provide a
+    // test case for "disabled" behavior.
+    // Also loading as data rather than using URL should work, but it causes a
+    // security exception in JS, most likely due to cross domain access. So we load
+    // using a URL. Finally, it looks like enabling database requires creating a
+    // webChromeClient and listening to Quota callbacks, which is not documented.
+    public void testDatabaseDisabled() throws Throwable {
+        // Verify that websql database does not work when disabled.
+        startWebServer();
+
+        mOnUiThread.setWebChromeClient(new ChromeClient(mOnUiThread) {
+            @Override
+            public void onExceededDatabaseQuota(String url, String databaseId, long quota,
+                long estimatedSize, long total, WebStorage.QuotaUpdater updater) {
+                updater.updateQuota(estimatedSize);
+            }
+        });
+        mSettings.setJavaScriptEnabled(true);
+        mSettings.setDatabaseEnabled(false);
+        final String url = mWebServer.getAssetUrl(TestHtmlConstants.DATABASE_ACCESS_URL);
+        mOnUiThread.loadUrlAndWaitForCompletion(url);
+        assertEquals("No database", mOnUiThread.getTitle());
+    }
+
     public void testLoadsImagesAutomatically() throws Throwable {
         assertTrue(mSettings.getLoadsImagesAutomatically());
 
@@ -727,7 +683,7 @@
         mOnUiThread.clearCache(true);
         mOnUiThread.loadUrlAndWaitForCompletion(
             mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL));
-        assertEquals(TestHtmlConstants.WEBPAGE_NOT_AVAILABLE_TITLE, mOnUiThread.getTitle());
+        assertFalse(TestHtmlConstants.HELLO_WORLD_TITLE.equals(mOnUiThread.getTitle()));
         mOnUiThread.loadDataAndWaitForCompletion(getNetworkImageHtml(), "text/html", null);
         assertEquals(EMPTY_IMAGE_HEIGHT, mOnUiThread.getTitle());
         mOnUiThread.loadDataAndWaitForCompletion(DATA_URL_IMAGE_HTML, "text/html", null);
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
index 6c66f52..44e95ac 100755
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
@@ -33,6 +33,8 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.StrictMode;
+import android.os.StrictMode.ThreadPolicy;
 import android.os.SystemClock;
 import android.test.ActivityInstrumentationTestCase2;
 import android.test.UiThreadTest;
@@ -41,8 +43,6 @@
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
-import android.webkit.CacheManager;
-import android.webkit.CacheManager.CacheResult;
 import android.webkit.ConsoleMessage;
 import android.webkit.DownloadListener;
 import android.webkit.JavascriptInterface;
@@ -124,6 +124,18 @@
         mWebServer = new CtsTestServer(getActivity(), secure);
     }
 
+    private void stopWebServer() throws Exception {
+        assertNotNull(mWebServer);
+        ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
+        ThreadPolicy tmpPolicy = new ThreadPolicy.Builder(oldPolicy)
+                .permitNetwork()
+                .build();
+        StrictMode.setThreadPolicy(tmpPolicy);
+        mWebServer.shutdown();
+        mWebServer = null;
+        StrictMode.setThreadPolicy(oldPolicy);
+    }
+
     @UiThreadTest
     public void testConstructor() {
         new WebView(getActivity());
@@ -138,23 +150,11 @@
          * http://en.wikipedia.org/wiki/Postal_address#United_States
          * http://www.usps.com/
          */
-        // full address, invalid zip code
-        assertNull(WebView.findAddress("455 LARKSPUR DRIVE CALIFORNIA SPRINGS CALIFORNIA 92926"));
         // full address
         assertEquals("455 LARKSPUR DRIVE CALIFORNIA SPRINGS CALIFORNIA 92826",
                 WebView.findAddress("455 LARKSPUR DRIVE CALIFORNIA SPRINGS CALIFORNIA 92826"));
-        // full address ( with abbreviated street type and state)
-        assertEquals("455 LARKSPUR DR CALIFORNIA SPRINGS CA 92826",
-                WebView.findAddress("455 LARKSPUR DR CALIFORNIA SPRINGS CA 92826"));
-        // misspell the state ( CALIFORNIA -> CALIFONIA )
-        assertNull(WebView.findAddress("455 LARKSPUR DRIVE CALIFORNIA SPRINGS CALIFONIA 92826"));
-        // without optional zip code
-        assertEquals("455 LARKSPUR DR CALIFORNIA SPRINGS CA",
-                WebView.findAddress("455 LARKSPUR DR CALIFORNIA SPRINGS CA"));
-        // house number, street name and street type are missing
-        assertNull(WebView.findAddress("CALIFORNIA SPRINGS CA"));
-        // city & state are missing
-        assertNull(WebView.findAddress("455 LARKSPUR DR"));
+        // not an address
+        assertNull(WebView.findAddress("This is not an address: no town, no state, no zip."));
     }
 
     @SuppressWarnings("deprecation")
@@ -320,22 +320,38 @@
         assertEquals(redirectUrl, mWebView.getOriginalUrl());
     }
 
-    @UiThreadTest
     public void testStopLoading() throws Exception {
-        assertNull(mWebView.getUrl());
-        assertEquals(INITIAL_PROGRESS, mWebView.getProgress());
+        assertEquals(INITIAL_PROGRESS, mOnUiThread.getProgress());
 
         startWebServer(false);
-        String url = mWebServer.getDelayedAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
-        mWebView.loadUrl(url);
-        mWebView.stopLoading();
-        new PollingCheck() {
-            @Override
-            protected boolean check() {
-                return 100 == mWebView.getProgress();
+        String url = mWebServer.getDelayedAssetUrl(TestHtmlConstants.STOP_LOADING_URL);
+
+        class JsInterface {
+            private boolean mPageLoaded;
+
+            @JavascriptInterface
+            public synchronized void pageLoaded() {
+                mPageLoaded = true;
+                notify();
             }
-        }.run();
-        assertNull(mWebView.getUrl());
+            public synchronized boolean getPageLoaded() {
+                return mPageLoaded;
+            }
+        }
+
+        JsInterface jsInterface = new JsInterface();
+
+        mOnUiThread.getSettings().setJavaScriptEnabled(true);
+        mOnUiThread.addJavascriptInterface(jsInterface, "javabridge");
+        mOnUiThread.loadUrl(url);
+        mOnUiThread.stopLoading();
+
+        // We wait to see that the onload callback in the HTML is not fired.
+        synchronized (jsInterface) {
+            jsInterface.wait(3000);
+        }
+
+        assertFalse(jsInterface.getPageLoaded());
     }
 
     @UiThreadTest
@@ -606,7 +622,7 @@
             }
         }.run();
         assertEquals(mWebView, listener.webView);
-        assertNotNull(listener.picture);
+        assertNull(listener.picture);
 
         final int oldCallCount = listener.callCount;
         final String newUrl = mWebServer.getAssetUrl(TestHtmlConstants.SMALL_IMG_URL);
@@ -619,66 +635,6 @@
         }.run();
     }
 
-    public void testSaveAndRestorePicture() throws Throwable {
-        mWebView.setBackgroundColor(Color.CYAN);
-        startWebServer(false);
-        final String url = mWebServer.getAssetUrl(TestHtmlConstants.BLANK_PAGE_URL);
-        mOnUiThread.loadUrlAndWaitForCompletion(url);
-        getInstrumentation().waitForIdleSync();
-
-        final Bundle bundle = new Bundle();
-        final File f = getActivity().getFileStreamPath("snapshot");
-        if (f.exists()) {
-            f.delete();
-        }
-
-        try {
-            assertTrue(bundle.isEmpty());
-            assertEquals(0, f.length());
-            assertTrue(mOnUiThread.savePicture(bundle, f));
-
-            // File saving is done in a separate thread.
-            new PollingCheck() {
-                @Override
-                protected boolean check() {
-                    return f.length() > 0;
-                }
-            }.run();
-
-            assertFalse(bundle.isEmpty());
-
-            Picture p = Picture.createFromStream(new FileInputStream(f));
-            Bitmap b = Bitmap.createBitmap(p.getWidth(), p.getHeight(), Config.ARGB_8888);
-            p.draw(new Canvas(b));
-            assertBitmapFillWithColor(b, Color.CYAN);
-
-            mOnUiThread.setBackgroundColor(Color.WHITE);
-            mOnUiThread.reloadAndWaitForCompletion();
-            getInstrumentation().waitForIdleSync();
-
-            runTestOnUiThread(new Runnable() {
-                @Override
-                public void run() {
-                    Bitmap b = Bitmap.createBitmap(mWebView.getWidth(), mWebView.getHeight(),
-                            Config.ARGB_8888);
-                    mWebView.draw(new Canvas(b));
-                    assertBitmapFillWithColor(b, Color.WHITE);
-
-                    // restorePicture is only supported in software rendering
-                    mWebView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
-                    assertTrue(mWebView.restorePicture(bundle, f));
-                }
-            });
-            getInstrumentation().waitForIdleSync();
-            // Cannot test whether the picture has been restored successfully.
-            // Drawing the webview into a canvas will draw white, but on the display it is cyan
-        } finally {
-            if (f.exists()) {
-                f.delete();
-            }
-        }
-    }
-
     @UiThreadTest
     public void testAccessHttpAuthUsernamePassword() {
         try {
@@ -741,28 +697,10 @@
         }
     }
 
-    @UiThreadTest
-    public void testSavePassword() {
-        WebViewDatabase db = WebViewDatabase.getInstance(getActivity());
-        try {
-            db.clearUsernamePassword();
-
-            String host = "http://localhost:8080";
-            String userName = "user";
-            String password = "password";
-            assertFalse(db.hasUsernamePassword());
-            mWebView.savePassword(host, userName, password);
-            assertTrue(db.hasUsernamePassword());
-        } finally {
-            db.clearUsernamePassword();
-        }
-    }
-
     public void testLoadData() throws Throwable {
         final String HTML_CONTENT =
                 "<html><head><title>Hello,World!</title></head><body></body>" +
                 "</html>";
-        assertNull(mOnUiThread.getTitle());
         mOnUiThread.loadDataAndWaitForCompletion(HTML_CONTENT,
                 "text/html", null);
         assertEquals("Hello,World!", mOnUiThread.getTitle());
@@ -788,14 +726,13 @@
 
     @UiThreadTest
     public void testLoadDataWithBaseUrl() throws Throwable {
-        assertNull(mWebView.getTitle());
         assertNull(mWebView.getUrl());
         String imgUrl = TestHtmlConstants.SMALL_IMG_URL; // relative
 
         // Check that we can access relative URLs and that reported URL is supplied history URL.
         startWebServer(false);
         String baseUrl = mWebServer.getAssetUrl("foo.html");
-        String historyUrl = "random";
+        String historyUrl = "http://www.example.com/";
         String dbPath = getActivity().getFilesDir().toString() + "/icons";
         mIconDb = WebIconDatabase.getInstance();
         mIconDb.open(dbPath);
@@ -824,7 +761,7 @@
         mOnUiThread.loadDataWithBaseURLAndWaitForCompletion(baseUrl,
                 "<html><body><img src=\"" + imgUrl + "\"/></body></html>",
                 "text/html", "UTF-8", null);
-        assertTrue(mWebServer.getLastRequestUrl().endsWith(imgUrl));
+        assertTrue("last request is " + mWebServer.getLastRequestUrl(), mWebServer.getLastRequestUrl().endsWith(imgUrl));
         assertEquals("about:blank", mWebView.getUrl());
 
         // Test that JavaScript can access content from the same origin as the base URL.
@@ -842,8 +779,8 @@
         mOnUiThread.loadDataWithBaseURLAndWaitForCompletion("data:foo",
                 "<html><body>bar</body></html>", "text/html", "UTF-8",
                 historyUrl);
-        assertTrue(mWebView.getUrl().indexOf("data:text/html,") == 0);
-        assertTrue(mWebView.getUrl().indexOf("bar") > 0);
+        assertTrue("URL: " + mWebView.getUrl(), mWebView.getUrl().indexOf("data:text/html") == 0);
+        assertTrue("URL: " + mWebView.getUrl(), mWebView.getUrl().indexOf("bar") > 0);
     }
 
     @UiThreadTest
@@ -1024,40 +961,6 @@
     }
 
     @UiThreadTest
-    public void testClearCache() throws Exception {
-        final File cacheFileBaseDir = CacheManager.getCacheFileBaseDir();
-        mWebView.clearCache(true);
-        assertEquals(0, cacheFileBaseDir.list().length);
-
-        startWebServer(false);
-        final String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
-        mOnUiThread.loadUrlAndWaitForCompletion(url);
-        new PollingCheck(TEST_TIMEOUT) {
-            @Override
-            protected boolean check() {
-                CacheResult result = CacheManager.getCacheFile(url, null);
-                return result != null;
-            }
-        }.run();
-        int cacheFileCount = cacheFileBaseDir.list().length;
-        assertTrue(cacheFileCount > 0);
-
-        mWebView.clearCache(false);
-        // the cache files are still there
-        // can not check other effects of the method
-        assertEquals(cacheFileCount, cacheFileBaseDir.list().length);
-
-        mWebView.clearCache(true);
-        // check the files are deleted
-        new PollingCheck(TEST_TIMEOUT) {
-            @Override
-            protected boolean check() {
-                return cacheFileBaseDir.list().length == 0;
-            }
-        }.run();
-    }
-
-    @UiThreadTest
     public void testPlatformNotifications() {
         WebView.enablePlatformNotifications();
         WebView.disablePlatformNotifications();
@@ -1123,7 +1026,7 @@
 
         // focus on first link
         handler.reset();
-        getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_DOWN);
+        getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_TAB);
         mOnUiThread.requestFocusNodeHref(hrefMsg);
         new PollingCheck() {
             @Override
@@ -1148,7 +1051,7 @@
         handler.reset();
         final Message hrefMsg2 = new Message();
         hrefMsg2.setTarget(handler);
-        getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_DOWN);
+        getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_TAB);
         mOnUiThread.requestFocusNodeHref(hrefMsg2);
         new PollingCheck() {
             @Override
@@ -1475,21 +1378,30 @@
     }
 
     @UiThreadTest
-    public void testSetAndGetCertificate() {
-        assertNull(mWebView.getCertificate());
-        SslCertificate certificate = new SslCertificate("foo", "bar", new Date(42), new Date(43));
-        mWebView.setCertificate(certificate);
-        assertEquals(certificate, mWebView.getCertificate());
-    }
-
-    @UiThreadTest
     public void testInsecureSiteClearsCertificate() throws Throwable {
-        final SslCertificate certificate =
-                new SslCertificate("foo", "bar", new Date(42), new Date(43));
+        final class MockWebViewClient extends WaitForLoadedClient {
+            public MockWebViewClient() {
+                super(mOnUiThread);
+            }
+            @Override
+            public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
+                handler.proceed();
+            }
+        }
+
+        startWebServer(true);
+        mOnUiThread.setWebViewClient(new MockWebViewClient());
+        mOnUiThread.loadUrlAndWaitForCompletion(
+                mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL));
+        SslCertificate cert = mWebView.getCertificate();
+        assertNotNull(cert);
+        assertEquals("Android", cert.getIssuedTo().getUName());
+
+        stopWebServer();
+
         startWebServer(false);
-        final String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
-        mWebView.setCertificate(certificate);
-        mOnUiThread.loadUrlAndWaitForCompletion(url);
+        mOnUiThread.loadUrlAndWaitForCompletion(
+                mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL));
         assertNull(mWebView.getCertificate());
     }
 
@@ -1505,20 +1417,48 @@
             }
         }
 
+        startWebServer(false);
+        mOnUiThread.loadUrlAndWaitForCompletion(
+                mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL));
+        assertNull(mWebView.getCertificate());
+
+        stopWebServer();
+
         startWebServer(true);
-        final String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
         mOnUiThread.setWebViewClient(new MockWebViewClient());
-        mWebView.setCertificate(null);
-        mOnUiThread.loadUrlAndWaitForCompletion(url);
+        mOnUiThread.loadUrlAndWaitForCompletion(
+                mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL));
         SslCertificate cert = mWebView.getCertificate();
         assertNotNull(cert);
         assertEquals("Android", cert.getIssuedTo().getUName());
     }
 
     @UiThreadTest
-    public void testClearSslPreferences() {
-        // FIXME: Implement this. See http://b/5378046.
-        mWebView.clearSslPreferences();
+    public void testClearSslPreferences() throws Throwable {
+        // Load the first page. We expect a call to
+        // WebViewClient.onReceivedSslError().
+        final SslErrorWebViewClient webViewClient = new SslErrorWebViewClient();
+        startWebServer(true);
+        final String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
+        mOnUiThread.setWebViewClient(webViewClient);
+        mOnUiThread.clearSslPreferences();
+        mOnUiThread.loadUrlAndWaitForCompletion(url);
+        assertTrue(webViewClient.wasOnReceivedSslErrorCalled());
+
+        // Load the page again. We expect another call to
+        // WebViewClient.onReceivedSslError() since we cleared sslpreferences.
+        mOnUiThread.clearSslPreferences();
+        webViewClient.resetWasOnReceivedSslErrorCalled();
+        mOnUiThread.loadUrlAndWaitForCompletion(url);
+        assertTrue(webViewClient.wasOnReceivedSslErrorCalled());
+        assertEquals(TestHtmlConstants.HELLO_WORLD_TITLE, mOnUiThread.getTitle());
+
+        // Load the page once again, without clearing the sslpreferences.
+        // Make sure we do not get the callback.
+        webViewClient.resetWasOnReceivedSslErrorCalled();
+        mOnUiThread.loadUrlAndWaitForCompletion(url);
+        assertFalse(webViewClient.wasOnReceivedSslErrorCalled());
+        assertEquals(TestHtmlConstants.HELLO_WORLD_TITLE, mOnUiThread.getTitle());
     }
 
     public void testOnReceivedSslError() throws Throwable {
@@ -1685,15 +1625,13 @@
         // WebViewClient.shouldOverrideUrlLoading() returns false, so
         // the WebView will load the new URL.
         mOnUiThread.setDownloadListener(listener);
+        mWebView.getSettings().setJavaScriptEnabled(true);
         mOnUiThread.loadDataAndWaitForCompletion(
-                "<html><body><a href=\"" + url
-                + "\">link</a></body></html>",
+                "<html><body onload=\"window.location = \'" + url + "\'\"></body></html>",
                 "text/html", null);
         // Wait for layout to complete before setting focus.
         getInstrumentation().waitForIdleSync();
-        assertTrue(mOnUiThread.requestFocus(View.FOCUS_DOWN, null));
-        getInstrumentation().waitForIdleSync();
-        getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER);
+
         new PollingCheck(TEST_TIMEOUT) {
             @Override
             protected boolean check() {
@@ -1719,22 +1657,34 @@
         mWebView.setMapTrackballToArrowKeys(true);
     }
 
-    @UiThreadTest
     public void testSetNetworkAvailable() throws Exception {
-        WebSettings settings = mWebView.getSettings();
+        WebSettings settings = mOnUiThread.getSettings();
         settings.setJavaScriptEnabled(true);
         startWebServer(false);
+
         String url = mWebServer.getAssetUrl(TestHtmlConstants.NETWORK_STATE_URL);
         mOnUiThread.loadUrlAndWaitForCompletion(url);
-        assertEquals("ONLINE", mWebView.getTitle());
+        assertEquals("ONLINE", mOnUiThread.getTitle());
 
         mWebView.setNetworkAvailable(false);
-        mOnUiThread.reloadAndWaitForCompletion();
-        assertEquals("OFFLINE", mWebView.getTitle());
+
+        // Wait for the DOM to receive notification of the network state change.
+        new PollingCheck(TEST_TIMEOUT) {
+            @Override
+            protected boolean check() {
+                return mOnUiThread.getTitle().equals("OFFLINE");
+            }
+        }.run();
 
         mWebView.setNetworkAvailable(true);
-        mOnUiThread.reloadAndWaitForCompletion();
-        assertEquals("ONLINE", mWebView.getTitle());
+
+        // Wait for the DOM to receive notification of the network state change.
+        new PollingCheck(TEST_TIMEOUT) {
+            @Override
+            protected boolean check() {
+                return mOnUiThread.getTitle().equals("ONLINE");
+            }
+        }.run();
     }
 
     public void testSetWebChromeClient() throws Throwable {
@@ -1887,7 +1837,7 @@
 
     private void moveFocusDown() throws Throwable {
         // send down key and wait for idle
-        sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+        getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_TAB);
         // waiting for idle isn't always sufficient for the key to be fully processed
         Thread.sleep(500);
     }
@@ -2012,6 +1962,7 @@
     // Note that this class is not thread-safe.
     final class SslErrorWebViewClient extends WaitForLoadedClient {
         private boolean mWasOnReceivedSslErrorCalled;
+        private String mErrorUrl;
 
         public SslErrorWebViewClient() {
             super(mOnUiThread);
@@ -2019,6 +1970,7 @@
         @Override
         public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
             mWasOnReceivedSslErrorCalled = true;
+            mErrorUrl = error.getUrl();
             handler.proceed();
         }
         public void resetWasOnReceivedSslErrorCalled() {
@@ -2027,5 +1979,8 @@
         public boolean wasOnReceivedSslErrorCalled() {
             return mWasOnReceivedSslErrorCalled;
         }
+        public String errorUrl() {
+            return mErrorUrl;
+        }
     }
 }
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebView_WebViewTransportTest.java b/tests/tests/webkit/src/android/webkit/cts/WebView_WebViewTransportTest.java
index eaa5e69..dd784be 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebView_WebViewTransportTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebView_WebViewTransportTest.java
@@ -16,16 +16,22 @@
 
 package android.webkit.cts;
 
-import android.test.AndroidTestCase;
+import android.test.ActivityInstrumentationTestCase2;
 import android.test.UiThreadTest;
 import android.webkit.WebView;
 import android.webkit.WebView.WebViewTransport;
 
 
-public class WebView_WebViewTransportTest extends AndroidTestCase {
+public class WebView_WebViewTransportTest
+        extends ActivityInstrumentationTestCase2<WebViewStubActivity> {
+
+    public WebView_WebViewTransportTest() {
+        super("com.android.cts.stub", WebViewStubActivity.class);
+    }
+
     @UiThreadTest
     public void testAccessWebView() {
-        WebView webView = new WebView(mContext);
+        WebView webView = getActivity().getWebView();
         WebViewTransport transport = webView.new WebViewTransport();
 
         assertNull(transport.getWebView());
diff --git a/tests/tests/widget/src/android/widget/cts/GridLayoutTest.java b/tests/tests/widget/src/android/widget/cts/GridLayoutTest.java
new file mode 100644
index 0000000..638fe96
--- /dev/null
+++ b/tests/tests/widget/src/android/widget/cts/GridLayoutTest.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2012 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.widget.cts;
+
+import android.content.Context;
+import android.test.ActivityInstrumentationTestCase;
+import android.util.AttributeSet;
+import android.util.Xml;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsoluteLayout;
+import android.widget.Button;
+import android.widget.GridLayout;
+import android.widget.TextView;
+import com.android.cts.stub.R;
+import org.xmlpull.v1.XmlPullParser;
+
+import static android.view.ViewGroup.LAYOUT_MODE_OPTICAL_BOUNDS;
+import static android.widget.GridLayout.spec;
+
+/**
+ * Test {@link android.widget.GridLayout}.
+ */
+public class GridLayoutTest extends ActivityInstrumentationTestCase<GridLayoutStubActivity> {
+
+    // The size of the off-screen test container in which we we will testing layout.
+    public static final int MAX_X = 2000;
+    public static final int MAX_Y = 2000;
+
+    private static abstract class Alignment {
+        String name;
+        int gravity;
+
+        abstract int getValue(View v);
+
+        protected Alignment(String name, int gravity) {
+            this.name = name;
+            this.gravity = gravity;
+        }
+    }
+
+    private static final Alignment[] HORIZONTAL_ALIGNMENTS = {
+            new Alignment("LEFT", Gravity.LEFT) {
+                @Override
+                int getValue(View v) {
+                    return v.getLeft();
+                }
+            },
+            new Alignment("CENTER", Gravity.CENTER_HORIZONTAL) {
+                @Override
+                int getValue(View v) {
+                    return (v.getLeft() + v.getRight()) / 2;
+                }
+            },
+            new Alignment("RIGHT", Gravity.RIGHT) {
+                @Override
+                int getValue(View v) {
+                    return v.getRight();
+                }
+            },
+            new Alignment("FILL", Gravity.FILL_HORIZONTAL) {
+                @Override
+                int getValue(View v) {
+                    return v.getWidth();
+                }
+            }
+    };
+
+    private static final Alignment[] VERTICAL_ALIGNMENTS = {
+            new Alignment("TOP", Gravity.TOP) {
+                @Override
+                int getValue(View v) {
+                    return v.getTop();
+                }
+            },
+            new Alignment("CENTER", Gravity.CENTER_VERTICAL) {
+                @Override
+                int getValue(View v) {
+                    return (v.getTop() + v.getBottom()) / 2;
+                }
+            },
+            new Alignment("BASELINE", Gravity.NO_GRAVITY) {
+                @Override
+                int getValue(View v) {
+                    return v.getTop() + v.getBaseline();
+                }
+            },
+            new Alignment("BOTTOM", Gravity.BOTTOM) {
+                @Override
+                int getValue(View v) {
+                    return v.getBottom();
+                }
+            },
+            new Alignment("FILL", Gravity.FILL_VERTICAL) {
+                @Override
+                int getValue(View v) {
+                    return v.getHeight();
+                }
+            }
+    };
+
+    private Context mContext;
+
+    public GridLayoutTest() {
+        super("com.android.cts.stub", GridLayoutStubActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mContext = getInstrumentation().getTargetContext();
+    }
+
+    public void testConstructor() {
+        new GridLayout(mContext);
+
+        new GridLayout(mContext, null);
+
+        XmlPullParser parser = mContext.getResources().getXml(R.layout.gridlayout_layout);
+        AttributeSet attrs = Xml.asAttributeSet(parser);
+        new GridLayout(mContext, attrs);
+
+        try {
+            new GridLayout(null, null);
+            fail("should throw NullPointerException.");
+        } catch (NullPointerException e) {
+        }
+    }
+
+    public void testCheckLayoutParams() {
+        GridLayout gridLayout = new GridLayout(mContext);
+
+        gridLayout.addView(new TextView(mContext), new AbsoluteLayout.LayoutParams(0, 0, 0, 0));
+
+        gridLayout.addView(new TextView(mContext), new GridLayout.LayoutParams(
+                GridLayout.spec(0),
+                GridLayout.spec(0)));
+
+    }
+
+    public void testGenerateDefaultLayoutParams() {
+        GridLayout gridLayout = new GridLayout(mContext);
+        ViewGroup.LayoutParams lp = gridLayout.generateLayoutParams(null);
+        assertNotNull(lp);
+        assertTrue(lp instanceof GridLayout.LayoutParams);
+        assertEquals(ViewGroup.LayoutParams.WRAP_CONTENT, lp.width);
+        assertEquals(ViewGroup.LayoutParams.WRAP_CONTENT, lp.height);
+    }
+
+    private View[][] populate(GridLayout container) {
+        Context context = container.getContext();
+        int N = VERTICAL_ALIGNMENTS.length;
+        int M = HORIZONTAL_ALIGNMENTS.length;
+
+        View[][] table = new View[N + 1][M + 1];
+
+        {
+            TextView v = new TextView(context);
+            GridLayout.LayoutParams lp = new GridLayout.LayoutParams(spec(0), spec(0));
+            lp.setGravity(Gravity.CENTER);
+            v.setText("*");
+            container.addView(v, lp);
+        }
+        for (int i = 0; i < N; i++) {
+            Alignment va = VERTICAL_ALIGNMENTS[i];
+            int row = i + 1;
+            GridLayout.LayoutParams lp = new GridLayout.LayoutParams(spec(row), spec(0));
+            lp.setGravity(va.gravity | Gravity.CENTER_HORIZONTAL);
+            TextView v = new TextView(context);
+            v.setGravity(Gravity.CENTER);
+            v.setText(va.name);
+            container.addView(v, lp);
+            table[row][0] = v;
+        }
+        for (int j = 0; j < M; j++) {
+            Alignment ha = HORIZONTAL_ALIGNMENTS[j];
+            int col = j + 1;
+            GridLayout.LayoutParams lp = new GridLayout.LayoutParams(spec(0), spec(col));
+            lp.setGravity(Gravity.CENTER_VERTICAL | ha.gravity);
+            TextView v = new TextView(context);
+            v.setGravity(Gravity.CENTER);
+            v.setText(ha.name);
+            container.addView(v, lp);
+            table[0][col] = v;
+        }
+        for (int i = 0; i < N; i++) {
+            for (int j = 0; j < M; j++) {
+                Alignment ha = HORIZONTAL_ALIGNMENTS[j];
+                Alignment va = VERTICAL_ALIGNMENTS[i];
+                int row = i + 1;
+                int col = j + 1;
+                GridLayout.LayoutParams lp = new GridLayout.LayoutParams(spec(row), spec(col));
+                lp.setGravity(va.gravity | ha.gravity);
+                TextView v = new Button(context);
+                v.setText("X");
+                v.setTextSize(10 + 5 * row * col);
+                container.addView(v, lp);
+                table[row][col] = v;
+            }
+        }
+        return table;
+    }
+
+    private void testAlignment(int row, int col, Alignment a, View v0, View v1, String group) {
+        int a0 = a.getValue(v0);
+        int a1 = a.getValue(v1);
+        assertEquals("View at row " + row + ", column " + col + " was not " + a.name +
+                " aligned with its title " + group + ";",
+                a0, a1);
+    }
+
+    private void test(GridLayout p, View[][] table) {
+        p.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
+        p.layout(0, 0, MAX_X, MAX_Y);
+
+        int N = VERTICAL_ALIGNMENTS.length;
+        int M = HORIZONTAL_ALIGNMENTS.length;
+
+        // test all horizontal alignments in each column
+        for (int j = 0; j < M; j++) {
+            int col = j + 1;
+            View v0 = table[0][col];
+            Alignment alignment = HORIZONTAL_ALIGNMENTS[j];
+            for (int i = 0; i < N; i++) {
+                int row = i + 1;
+                testAlignment(row, col, alignment, v0, table[row][col], "column");
+            }
+        }
+
+        // test all vertical alignments in each row
+        for (int i = 0; i < N; i++) {
+            int row = i + 1;
+            View v0 = table[row][0];
+            Alignment alignment = VERTICAL_ALIGNMENTS[i];
+            for (int j = 0; j < M; j++) {
+                int col = j + 1;
+                testAlignment(row, col, alignment, v0, table[row][col], "row");
+            }
+        }
+    }
+    public void testAlignment() {
+        GridLayout p = new GridLayout(mContext);
+        View[][] table = populate(p);
+        test(p, table);
+        //p.setLayoutMode(ViewGroup.LAYOUT_MODE_OPTICAL_BOUNDS);
+        //test(p, table);
+    }
+}
diff --git a/tests/tests/widget/src/android/widget/cts/ViewFlipperTest.java b/tests/tests/widget/src/android/widget/cts/ViewFlipperTest.java
index bcb417a..f223466 100644
--- a/tests/tests/widget/src/android/widget/cts/ViewFlipperTest.java
+++ b/tests/tests/widget/src/android/widget/cts/ViewFlipperTest.java
@@ -93,6 +93,7 @@
 
         // wait for a longer time to make sure the view flipping is completed.
         waitForViewFlipping(FLIP_INTERVAL + 200);
+        getInstrumentation().waitForIdleSync();
         runTestOnUiThread(new Runnable() {
             public void run() {
                 ViewFlipper viewFlipper =
@@ -108,6 +109,7 @@
         });
 
         waitForViewFlipping(FLIP_INTERVAL + 200);
+        getInstrumentation().waitForIdleSync();
         runTestOnUiThread(new Runnable() {
             public void run() {
                 ViewFlipper viewFlipper =
diff --git a/tests/uiautomator/Android.mk b/tests/uiautomator/Android.mk
index 68b1dc2..d0d4b8e 100644
--- a/tests/uiautomator/Android.mk
+++ b/tests/uiautomator/Android.mk
@@ -22,7 +22,7 @@
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_MODULE := CtsUiAutomatorTests
-LOCAL_JAVA_LIBRARIES := uiautomator_sdk_v17
+LOCAL_JAVA_LIBRARIES := uiautomator.core
 LOCAL_PROGUARD_ENABLED := disabled
 LOCAL_CTS_TEST_APK := CtsUiAutomatorApp
 LOCAL_CTS_TEST_APP_PACKAGE := com.android.cts.uiautomator
diff --git a/tests/uiautomator/src/com/android/cts/uiautomatortest/CtsUiAutomatorTest.java b/tests/uiautomator/src/com/android/cts/uiautomatortest/CtsUiAutomatorTest.java
index 5de9805..ce6c02d 100644
--- a/tests/uiautomator/src/com/android/cts/uiautomatortest/CtsUiAutomatorTest.java
+++ b/tests/uiautomator/src/com/android/cts/uiautomatortest/CtsUiAutomatorTest.java
@@ -15,6 +15,7 @@
  */
 package com.android.cts.uiautomatortest;
 
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -54,6 +55,7 @@
     protected void setUp() throws Exception {
         super.setUp();
         // Make sure the test app is always running
+        UiDevice.getInstance().waitForIdle();
         if (!new UiObject(new UiSelector().packageName(PKG_NAME)).exists())
             runShellCommand(LAUNCH_APP);
     }
@@ -195,14 +197,17 @@
     }
 
     /**
-     * Test if a the content changed due to an action can be verified
+     * Test when a node's state is changed due to an action, it is updated in the accessibility
+     * hierarchy.
      *
      * @throws UiObjectNotFoundException
      */
     public void testSelectAfterContentChanged() throws UiObjectNotFoundException {
         openTest("Test 2");
-        getObjectByText("Before").click();
-        getObjectByText("After").click();
+        UiObject dynaButton = getObjectByText("Before");
+        dynaButton.click();
+        assertTrue("Button state change is not refreshed in accessibility hierarchy",
+                getObjectByText("After").exists());
     }
 
     /**
@@ -288,12 +293,12 @@
         int totalDelay = Integer.parseInt(timeDiff);
 
         // Cumulative waits in this test should add up to at minimum 30 seconds
-        assertFalse("Timeout for wait-for-idle is too short. Expecting minimum 10 seconds",
+        assertFalse("Timeout for wait-for-idle is too short. Expecting minimum 30 seconds",
                 totalDelay < 30 * 1000);
 
         // allow for tolerance in time measurements due to differences between
         // device speeds
-        assertFalse("Timeout for wait-for-idle is too long. Expecting maximum 15 seconds",
+        assertFalse("Timeout for wait-for-idle is too long. Expecting maximum 60 seconds",
                 totalDelay > 60 * 1000);
     }
 
@@ -324,26 +329,28 @@
         UiObject textView = new UiObject(new UiSelector().textContains("["));
 
         textView.swipeLeft(10);
-        assertTrue("UiObject swipe left", "[ 2 ]".equals(textView.getText()));
+        assertTrue("UiObject swipe left 1->2", "[ 2 ]".equals(textView.getText()));
 
         textView.swipeLeft(10);
-        assertTrue("UiObject swipe left", "[ 3 ]".equals(textView.getText()));
+        assertTrue("UiObject swipe left 2->3", "[ 3 ]".equals(textView.getText()));
 
         textView.swipeLeft(10);
-        assertTrue("UiObject swipe left", "[ 4 ]".equals(textView.getText()));
+        assertTrue("UiObject swipe left 3->4", "[ 4 ]".equals(textView.getText()));
 
         textView.swipeRight(10);
-        assertTrue("UiObject swipe right", "[ 3 ]".equals(textView.getText()));
+        assertTrue("UiObject swipe right 3<-4", "[ 3 ]".equals(textView.getText()));
 
         textView.swipeRight(10);
-        assertTrue("UiObject swipe right", "[ 2 ]".equals(textView.getText()));
+        assertTrue("UiObject swipe right 2<-3", "[ 2 ]".equals(textView.getText()));
 
         textView.swipeRight(10);
-        assertTrue("UiObject swipe right", "[ 1 ]".equals(textView.getText()));
+        assertTrue("UiObject swipe right 1<-2", "[ 1 ]".equals(textView.getText()));
 
         Rect tb = textView.getBounds();
         UiDevice.getInstance().swipe(tb.right - 20, tb.centerY(), tb.left + 20, tb.centerY(), 50);
-        assertTrue("UiDevice swipe", "[ 2 ]".equals(textView.getText()));
+
+        SystemClock.sleep(100);
+        assertTrue("UiDevice raw swipe 1->2", "[ 2 ]".equals(textView.getText()));
     }
 
     /**
@@ -370,45 +377,10 @@
     }
 
     /**
-     * The view contains a WebView with static content. This test uses the text
-     * traversal feature of pressing down arrows to read the view's contents
-     *
+     * Test when an object does not exist, an exception is thrown
      * @throws UiObjectNotFoundException
      */
-    /*// broken in MR1
-    public void testWebViewTextTraversal() throws UiObjectNotFoundException {
-        openTest("Test 6");
-        UiObject webView = new UiObject(new UiSelector().className(android.webkit.WebView.class
-                .getName()));
-        webView.clickTopLeft();
-        UiDevice device = UiDevice.getInstance();
-        device.clearLastTraversedText();
-
-        device.pressDPadDown();
-        String text = device.getLastTraversedText();
-        assertTrue("Read regular text", text.contains("This is test <b>6</b>"));
-
-        device.pressDPadDown();
-        text = device.getLastTraversedText();
-        assertTrue("Anchor text", text.contains("<a"));
-
-        device.pressDPadDown();
-        text = device.getLastTraversedText();
-        assertTrue("h5 text", text.contains("h5"));
-
-        device.pressDPadDown();
-        text = device.getLastTraversedText();
-        assertTrue("Anchor text", text.contains("<a"));
-
-        device.pressDPadDown();
-        text = device.getLastTraversedText();
-        assertTrue("h4 text", text.contains("h4"));
-    }*/
-
-    /**
-     * Test when an object does not exist, an exception is thrown
-     */
-    public void testExceptionObjectNotFound() {
+    public void testExceptionObjectNotFound() throws UiObjectNotFoundException {
         UiSelector selector = new UiSelector().text("Nothing should be found");
         UiSelector child = new UiSelector().className("Nothing");
         UiObject obj = new UiObject(selector.childSelector(child));
@@ -760,6 +732,7 @@
      * @since API Level 17
      */
     public void testSelectorLongClickableProperty() throws UiObjectNotFoundException {
+        openTest("Test 2");
         UiObject button3 = new UiObject(new UiSelector().className(
                 android.widget.Button.class).longClickable(true).instance(2));
         button3.longClick();
@@ -781,6 +754,236 @@
     }
 
     /**
+     * Verifies the 'Resource-Id' property of UiSelector
+     *
+     * @throws UiObjectNotFoundException
+     * @since API Level 18
+     */
+    public void testSelectorResourceId() throws UiObjectNotFoundException {
+        openTest("Test 5");
+        UiSelector toggleSelector =
+                new UiSelector().resourceId("com.android.cts.uiautomator:id/test_5_toggleButton");
+        UiObject toggleButton = new UiObject(toggleSelector);
+        assertTrue("Object with selector resource-id not found", toggleButton.exists());
+        assertTrue("Incorrect object for selector resource-id returned",
+                "OFF".equals(toggleButton.getText()) || "ON".equals(toggleButton.getText()));
+    }
+
+    /**
+     * Performs a pinch out from the center of a view to its edges and listens to
+     * the motion events to make sure the starting and ending points of both pointers
+     * are correct.
+     *
+     * @throws UiObjectNotFoundException
+     * @since API Level 18
+     */
+    public void testPinchOut() throws UiObjectNotFoundException {
+        openTest("Test 12");
+
+        UiObject screen = new UiObject(
+                new UiSelector().description("Details View"));
+
+        // get the current view dimensions
+        Rect screenRect = screen.getBounds();
+
+        // perform the pinch for 100% of the view dimensions starting form
+        // the center out to the edges.
+        screen.pinchOut(100, 30);
+
+        // dialog with the detected pointers motion coordinates is displayed.
+        UiObject results = new UiObject(new UiSelector().className(
+                android.widget.ScrollView.class).childSelector(new UiSelector().className(
+                        android.widget.TextView.class)));
+        String allPointers = results.getText();
+        new UiObject(new UiSelector().text("OK")).click(); // dismiss dialog
+
+        // parse pointer 1
+        Point p1s = parsePointerCoordinates(allPointers, 0, 0); // start
+        Point p1e = parsePointerCoordinates(allPointers, 0, 1); // end
+        // parse pointer 2
+        Point p2s = parsePointerCoordinates(allPointers, 1, 0); // start
+        Point p2e = parsePointerCoordinates(allPointers, 1, 1); // end
+
+        assertTrue("All Y axis coordinates for pointer 1 must be the same", p1s.y == p1e.y);
+        assertTrue("All Y axis coordinates for pointer 2 must be the same", p2s.y == p2e.y);
+        assertTrue("All Y axis coordinates for both pointers must be the same", p1s.y == p2s.y);
+        assertTrue("Pinch must be in center of target view", p2s.y == screenRect.centerY());
+
+        assertTrue("Touch-down X coordinate for pointer 1 is invalid",
+                withinMarginOfError(0.1f, screenRect.centerX(), p1s.x));
+
+        assertTrue("Touch-down X coordinate for pointer 2 is invalid",
+                withinMarginOfError(0.1f, screenRect.centerX(), p2s.x));
+
+        assertTrue("Touch-up X coordinate for pointer 1 is invalid",
+                withinMarginOfError(0.1f, screenRect.centerX() - screenRect.left,
+                        screenRect.centerX() - p1e.x));
+
+        assertTrue("Touch-up X coordinate for pointer 2 is invalid",
+                withinMarginOfError(0.1f, screenRect.right, p2e.x));
+    }
+
+    /**
+     * Performs a pinch in from the edges of a view to its center and listens to
+     * the motion events to make sure the starting and ending points of both pointers
+     * are correct.
+     *
+     * @throws UiObjectNotFoundException
+     * @since API Level 18
+     */
+    public void testPinchIn() throws UiObjectNotFoundException {
+        openTest("Test 12");
+
+        UiObject screen = new UiObject(
+                new UiSelector().description("Details View"));
+
+        // get the current view dimensions
+        Rect screenRect = screen.getBounds();
+
+        // perform the pinch for 100% of the view dimensions starting form
+        // the edges in towards the center.
+        screen.pinchIn(100, 30);
+
+        // dialog with the detected pointers motion coordinates is displayed.
+        UiObject results = new UiObject(new UiSelector().className(
+                android.widget.ScrollView.class).childSelector(new UiSelector().className(
+                        android.widget.TextView.class)));
+        String allPointers = results.getText();
+        new UiObject(new UiSelector().text("OK")).click(); // dismiss dialog
+
+        // parse pointer 1
+        Point p1s = parsePointerCoordinates(allPointers, 0, 0); // start
+        Point p1e = parsePointerCoordinates(allPointers, 0, 1); // end
+        // parse pointer 2
+        Point p2s = parsePointerCoordinates(allPointers, 1, 0); // start
+        Point p2e = parsePointerCoordinates(allPointers, 1, 1); // end
+
+        assertTrue("All Y axis coordinates for pointer 1 must be the same", p1s.y == p1e.y);
+        assertTrue("All Y axis coordinates for pointer 2 must be the same", p2s.y == p2e.y);
+        assertTrue("All Y axis coordinates for both pointers must be the same", p1s.y == p2s.y);
+        assertTrue("Pinch must be in center of target view", p2s.y == screenRect.centerY());
+
+        assertTrue("Touch-down X coordinate for pointer 1 is invalid",
+                withinMarginOfError(0.1f, screenRect.centerX() - screenRect.left,
+                        screenRect.centerX() -  p1s.x));
+
+        assertTrue("Touch-down X coordinate for pointer 2 is invalid",
+                withinMarginOfError(0.1f, screenRect.right, p2s.x));
+
+        assertTrue("Touch-up X coordinate for pointer 1 is invalid",
+                withinMarginOfError(0.1f, screenRect.centerX(), p1e.x));
+
+        assertTrue("Touch-up X coordinate for pointer 2 is invalid",
+                withinMarginOfError(0.1f, screenRect.centerX(), p2e.x));
+    }
+
+    /**
+     * Performs a drag and drop operation from one UiObject to another UiObject
+     *
+     * @throws UiObjectNotFoundException
+     * @since API Level 18
+     */
+    public void testDragToObject() throws UiObjectNotFoundException {
+        openTest("Test 5");
+
+        UiObject imageButton = new UiObject(new UiSelector().description("Image button"));
+        UiObject starsBar = new UiObject(new UiSelector().className(android.widget.RatingBar.class));
+
+        Rect starsBarRect = starsBar.getBounds();
+        Rect imageButtonRect = imageButton.getBounds();
+        imageButton.dragTo(starsBar, 30);
+
+        // dialog with the detected pointers motion coordinates is displayed.
+        UiObject results = new UiObject(new UiSelector().className(
+                android.widget.ScrollView.class).childSelector(new UiSelector().className(
+                        android.widget.TextView.class)));
+        String allPointers = results.getText();
+        new UiObject(new UiSelector().text("OK")).click(); // dismiss dialog
+
+        // parse pointer 1
+        Point p1s = parsePointerCoordinates(allPointers, 0, 0); // start
+        Point p1e = parsePointerCoordinates(allPointers, 0, 1); // end
+
+        assertTrue("Invalid touch starting.X reported",
+                withinMarginOfError(0.05f, imageButtonRect.centerX(), p1s.x));
+        assertTrue("Invalid touch starting.Y reported",
+                withinMarginOfError(0.05f, imageButtonRect.centerY(), p1s.y));
+        assertTrue("Invalid touch ending.X reported",
+                withinMarginOfError(0.05f, starsBarRect.centerX(), p1e.x));
+        assertTrue("Invalid touch ending.Y reported",
+                withinMarginOfError(0.05f, starsBarRect.centerY(), p1e.y));
+    }
+
+    /**
+     * Performs a drag and drop operation from one UiObject to a specified coordinates
+     *
+     * @throws UiObjectNotFoundException
+     * @since API Level 18
+     */
+   public void testDragToCoordinates() throws UiObjectNotFoundException {
+       openTest("Test 5");
+
+       UiObject imageButton = new UiObject(new UiSelector().description("Image button"));
+       UiObject starsBar = new UiObject(new UiSelector().className(android.widget.RatingBar.class));
+
+       Rect starsBarRect = starsBar.getBounds();
+       Rect imageButtonRect = imageButton.getBounds();
+       imageButton.dragTo(starsBarRect.centerX(), starsBarRect.centerY(), 30);
+
+       // dialog with the detected pointers motion coordinates is displayed.
+       UiObject results = new UiObject(new UiSelector().className(
+               android.widget.ScrollView.class).childSelector(new UiSelector().className(
+                       android.widget.TextView.class)));
+       String allPointers = results.getText();
+       new UiObject(new UiSelector().text("OK")).click(); // dismiss dialog
+
+       // parse pointer 1
+       Point p1s = parsePointerCoordinates(allPointers, 0, 0); // start
+       Point p1e = parsePointerCoordinates(allPointers, 0, 1); // end
+
+       assertTrue("Invalid touch starting.X reported",
+               withinMarginOfError(0.05f, imageButtonRect.centerX(), p1s.x));
+       assertTrue("Invalid touch starting.Y reported",
+               withinMarginOfError(0.05f, imageButtonRect.centerY(), p1s.y));
+       assertTrue("Invalid touch ending.X reported",
+               withinMarginOfError(0.05f, starsBarRect.centerX(), p1e.x));
+       assertTrue("Invalid touch ending.Y reported",
+               withinMarginOfError(0.05f, starsBarRect.centerY(), p1e.y));
+   }
+
+   /**
+    * Detect if actual value is within the allowable margin of error of the expected value.
+    *
+    * Used essentially with actual values that may vary from the expected values such in the
+    * cases of touch and pinch and touch and swipe where the starting or ending points may
+    * not exactly match the expected value.
+    *
+    * @param marginPrecent is values between 0 and 1
+    * @param expected
+    * @param actual
+    * @return true if actual is within the allowed range from expected
+    */
+   private boolean withinMarginOfError(float marginPrecent, int expected, int actual) {
+       int m = (int) (marginPrecent * expected);
+       return actual >= expected - m && actual <= expected + m;
+   }
+
+   /**
+     * Parses a string containing starting to ending coordinates of one or more pointers.
+     *
+     * @param allPointers is a raw string with coordinates from all detected pointers
+     * @param pointerNumber is the desired pointer to be parsed
+     * @param edge is the 0 for the start or 1 for the end of the swipe
+     * @return Point containing the start or end coordinates of the specified pointer number
+     */
+    private Point parsePointerCoordinates(String allPointers, int pointerNumber, int edge) {
+        String pointers[] = allPointers.split("\n");
+        String coordinates = pointers[pointerNumber].split(":")[edge];
+        String xy[] = coordinates.split(",");
+        return new Point(Integer.parseInt(xy[0]), Integer.parseInt(xy[1]));
+    }
+
+    /**
      * Private helper to open test views. Also covers UiScrollable tests
      *
      * @param name
diff --git a/suite/pts/tools/Android.mk b/tests/uiautomator/test-apps/Android.mk
similarity index 98%
rename from suite/pts/tools/Android.mk
rename to tests/uiautomator/test-apps/Android.mk
index c141484..e790e1e 100644
--- a/suite/pts/tools/Android.mk
+++ b/tests/uiautomator/test-apps/Android.mk
@@ -1,4 +1,3 @@
-#
 # Copyright (C) 2012 The Android Open Source Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,6 +11,5 @@
 # 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.
-#
 
 include $(call all-subdir-makefiles)
diff --git a/tests/uiautomator/test-apps/CtsUiAutomatorApp/Android.mk b/tests/uiautomator/test-apps/CtsUiAutomatorApp/Android.mk
index e30816a..fe4342a 100644
--- a/tests/uiautomator/test-apps/CtsUiAutomatorApp/Android.mk
+++ b/tests/uiautomator/test-apps/CtsUiAutomatorApp/Android.mk
@@ -1,4 +1,3 @@
-#
 # Copyright (C) 2012 The Android Open Source Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,7 +11,6 @@
 # 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.
-#
 
 LOCAL_PATH:= $(call my-dir)
 
@@ -31,4 +29,3 @@
 LOCAL_PROGUARD_ENABLED := disabled
 
 include $(BUILD_PACKAGE)
-
diff --git a/tests/uiautomator/test-apps/CtsUiAutomatorApp/AndroidManifest.xml b/tests/uiautomator/test-apps/CtsUiAutomatorApp/AndroidManifest.xml
index 9b3d8b3..87c2d82 100644
--- a/tests/uiautomator/test-apps/CtsUiAutomatorApp/AndroidManifest.xml
+++ b/tests/uiautomator/test-apps/CtsUiAutomatorApp/AndroidManifest.xml
@@ -1,5 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-
 <!--
  * Copyright (C) 2011 The Android Open Source Project
  *
@@ -44,6 +43,6 @@
                 android:name="android.support.PARENT_ACTIVITY"
                 android:value="FragmentActivity" />
         </activity>
-        </application>
+     </application>
 
 </manifest>
diff --git a/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/layout/activity_main.xml b/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/layout/activity_main.xml
index f0e3bc9..72b68f3 100644
--- a/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/layout/activity_main.xml
+++ b/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/layout/activity_main.xml
@@ -28,4 +28,4 @@
         android:layout_centerVertical="true"
         android:text="@string/hello_world" />
 
-</RelativeLayout>
\ No newline at end of file
+</RelativeLayout>
diff --git a/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/layout/test_results_detail_fragment.xml b/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/layout/test_results_detail_fragment.xml
index 28ed6dd..e8b8c05 100644
--- a/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/layout/test_results_detail_fragment.xml
+++ b/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/layout/test_results_detail_fragment.xml
@@ -19,6 +19,7 @@
     android:id="@+id/test_results_detail_container"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
+    android:contentDescription="@string/title_test_view_detail"
     android:orientation="vertical" >
 
     <TextView
diff --git a/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/menu/activity_main.xml b/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/menu/activity_main.xml
index 73c4db8..c54c287 100644
--- a/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/menu/activity_main.xml
+++ b/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/menu/activity_main.xml
@@ -23,4 +23,4 @@
         android:showAsAction="never"
         android:title="@string/menu_settings"/>
 
-</menu>
\ No newline at end of file
+</menu>
diff --git a/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/values/strings.xml b/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/values/strings.xml
index 78b829f..04f0df4 100644
--- a/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/values/strings.xml
+++ b/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/values/strings.xml
@@ -1,5 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-
 <!--
  * Copyright (C) 2011 The Android Open Source Project
  *
@@ -58,5 +57,7 @@
     <string name="title_activity_test5_detail">Test5DetailActivity</string>
     <string name="title_activity_test6_detail">Test6DetailActivity</string>
     <string name="test_5_Widgets_collection">Widgets Collection</string>
-
+    <string name="generic_item_touch_dialog_title">Multi-touch test dialog</string>
+    <string name="drag_item_touch_dialog_title">Drag and drop test dialog</string>
+    <string name="title_test_view_detail">Details View</string>
 </resources>
diff --git a/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/values/styles.xml b/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/values/styles.xml
index 119c259..bce88ae 100644
--- a/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/values/styles.xml
+++ b/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/values/styles.xml
@@ -19,4 +19,4 @@
 
     <style name="AppTheme" parent="android:Theme.Light" />
 
-</resources>
\ No newline at end of file
+</resources>
diff --git a/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/MainActivity.java b/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/MainActivity.java
index 8d4f144..81833b7 100644
--- a/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/MainActivity.java
+++ b/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/MainActivity.java
@@ -13,7 +13,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package com.android.cts.uiautomator;
 
 import android.content.Intent;
diff --git a/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/Test2DetailFragment.java b/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/Test2DetailFragment.java
index 4eade4b..4fb322f 100644
--- a/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/Test2DetailFragment.java
+++ b/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/Test2DetailFragment.java
@@ -30,7 +30,6 @@
 public class Test2DetailFragment extends Fragment {
     public static final String ARG_ITEM_ID = "item_id";
     private Button mButton1, mButton2, mButton3, mDynaButton;
-    private boolean mDynaButtonAfter = false;
 
     public Test2DetailFragment() {
     }
@@ -144,32 +143,11 @@
         mDynaButton.setOnClickListener(new Button.OnClickListener() {
             @Override
             public void onClick(View v) {
-                if (getActivity().getString(R.string.buttonBefore).equals(mDynaButton.getText())) {
-                    mDynaButton.setText(R.string.buttonAfter);
-                    mDynaButton
-                            .setContentDescription(getActivity().getString(R.string.buttonAfter));
-                    mDynaButtonAfter = true;
-                } else {
-                    mDynaButton.setText(R.string.buttonBefore);
-                    mDynaButton.setContentDescription(getActivity()
-                            .getString(R.string.buttonBefore));
-                    mDynaButtonAfter = false;
-                }
+                mDynaButton.setText(R.string.buttonAfter);
+                mDynaButton.setContentDescription(getString(R.string.buttonAfter));
             }
         });
 
-        if (savedState != null && savedState.getBoolean("DynaButtonAfter")) {
-            mDynaButton.setText(R.string.buttonAfter);
-            mDynaButton.setContentDescription(getActivity().getString(R.string.buttonAfter));
-            mDynaButtonAfter = true;
-        }
         return rootView;
     }
-
-    @Override
-    public void onSaveInstanceState(Bundle savedInstanceState) {
-        super.onSaveInstanceState(savedInstanceState);
-        // Save UI state changes to the savedInstanceState.
-        savedInstanceState.putBoolean("DynaButtonAfter", mDynaButtonAfter);
-    }
 }
diff --git a/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/Test5DetailFragment.java b/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/Test5DetailFragment.java
index 0f88d3c..e2dd156 100644
--- a/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/Test5DetailFragment.java
+++ b/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/Test5DetailFragment.java
@@ -16,9 +16,11 @@
 
 package com.android.cts.uiautomator;
 
+import android.app.AlertDialog;
 import android.os.Bundle;
 import android.support.v4.app.Fragment;
 import android.view.LayoutInflater;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.Button;
@@ -31,6 +33,15 @@
 
     public static final String ARG_ITEM_ID = "item_id";
 
+    class PointerEvent {
+        int startX;
+        int startY;
+        int endX;
+        int endY;
+    }
+
+    private final PointerEvent mPointerEvent = new PointerEvent();
+
     public Test5DetailFragment() {
     }
 
@@ -66,6 +77,75 @@
             }
         });
 
+        imageButton.setOnTouchListener(new ImageButton.OnTouchListener() {
+
+            @Override
+            public boolean onTouch(View v, MotionEvent event) {
+                if (event.getAction() == MotionEvent.ACTION_DOWN) {
+                    resetTouchResults();
+                    collectStartAction(event, v);
+                } else if (event.getAction() == MotionEvent.ACTION_UP) {
+                    collectEndAction(event, v);
+                    displayTouchResults();
+                }
+                return false;
+            }
+        });
+
         return rootView;
     }
+
+    private void displayTouchResults() {
+        StringBuilder output = new StringBuilder();
+
+        output.append(String.format("%d,%d:%d,%d\n",
+                mPointerEvent.startX, mPointerEvent.startY, mPointerEvent.endX,
+                mPointerEvent.endY));
+
+        // display the submitted text
+        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+        builder.setTitle(R.string.drag_item_touch_dialog_title);
+        builder.setPositiveButton(R.string.OK, null);
+        builder.setMessage(output.toString());
+        AlertDialog diag = builder.create();
+        diag.show();
+    }
+
+    /**
+     * Clears all collected pointer results
+     */
+    private void resetTouchResults() {
+         mPointerEvent.startX = mPointerEvent.startY =
+                    mPointerEvent.endX = mPointerEvent.endY = -1;
+    }
+
+    /**
+     * Collects pointer touch information converting from relative to absolute before
+     * storing it as starting touch coordinates.
+     *
+     * @param event
+     * @param view
+     * @param pointerIndex
+     */
+    private void collectStartAction(MotionEvent event, View view) {
+        int offsetInScreen[] = new int[2];
+        view.getLocationOnScreen(offsetInScreen);
+        mPointerEvent.startX = (int)(event.getX() + offsetInScreen[0]);
+        mPointerEvent.startY = (int)(event.getY() + offsetInScreen[1]);
+    }
+
+    /**
+     * Collects pointer touch information converting from relative to absolute before
+     * storing it as ending touch coordinates.
+     *
+     * @param event
+     * @param view
+     * @param pointerIndex
+     */
+    private void collectEndAction(MotionEvent event, View view) {
+        int offsetInScreen[] = new int[2];
+        view.getLocationOnScreen(offsetInScreen);
+        mPointerEvent.endX = (int)(event.getX() + offsetInScreen[0]);
+        mPointerEvent.endY = (int)(event.getY() + offsetInScreen[1]);
+    }
 }
diff --git a/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/TestGenericDetailFragment.java b/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/TestGenericDetailFragment.java
index ab36d04..a7215c3 100644
--- a/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/TestGenericDetailFragment.java
+++ b/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/TestGenericDetailFragment.java
@@ -16,9 +16,11 @@
 
 package com.android.cts.uiautomator;
 
+import android.app.AlertDialog;
 import android.os.Bundle;
 import android.support.v4.app.Fragment;
 import android.view.LayoutInflater;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.TextView;
@@ -27,6 +29,15 @@
     public static final String ARG_ITEM_ID = "item_id";
     TestItems.TestItem mItem;
 
+    private class PointerEvent {
+        int startX;
+        int startY;
+        int endX;
+        int endY;
+    }
+
+    private final PointerEvent[] mPointerEvents = new PointerEvent[10];
+
     public TestGenericDetailFragment() {
     }
 
@@ -44,6 +55,122 @@
         if (mItem != null) {
             ((TextView) rootView.findViewById(R.id.testResultsTextView)).setText(mItem.mName);
         }
+
+        // listen to touch events to verify the multiPointerGesture APIs
+        // Since API Level 18
+        rootView.setOnTouchListener(new View.OnTouchListener() {
+            @Override
+            public boolean onTouch(View v, MotionEvent event) {
+
+                switch(event.getAction() & MotionEvent.ACTION_MASK) {
+                    case MotionEvent.ACTION_DOWN:
+                        // Reset any collected touch coordinate results on the primary touch down
+                        resetTouchResults();
+                        // collect this event
+                        collectStartAction(event, v, 0);
+                        break;
+
+                    case MotionEvent.ACTION_POINTER_DOWN:
+                        // collect this event
+                        collectStartAction(event, v, getPointerIndex(event));
+                        break;
+
+                    case MotionEvent.ACTION_POINTER_UP:
+                        // collect this event
+                        collectEndAction(event, v, getPointerIndex(event));
+                        break;
+
+                    case MotionEvent.ACTION_UP:
+                        // collect this event
+                        collectEndAction(event, v, 0);
+                        // on the primary touch up display results collected for all pointers
+                        displayTouchResults();
+                        break;
+                }
+                return true;
+            }
+        });
+
         return rootView;
     }
+
+    /**
+     * Displays collected results from all pointers into a dialog view in the following
+     * format: "startX,startY:endX,endY" where each line represent data for a pointer if
+     * multiple pointers (fingers) were detected.
+     */
+    private void displayTouchResults() {
+        StringBuilder output = new StringBuilder();
+        for (int x = 0; x < mPointerEvents.length; x++) {
+            if (mPointerEvents[x].startX == -1)
+                break;
+
+            output.append(String.format("%d,%d:%d,%d\n",
+                    mPointerEvents[x].startX, mPointerEvents[x].startY, mPointerEvents[x].endX,
+                    mPointerEvents[x].endY));
+        }
+
+        // display the submitted text
+        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+        builder.setTitle(R.string.generic_item_touch_dialog_title);
+        builder.setPositiveButton(R.string.OK, null);
+        builder.setMessage(output.toString());
+        AlertDialog diag = builder.create();
+        diag.show();
+    }
+
+    /**
+     * Clears all collected pointer results
+     */
+    private void resetTouchResults() {
+        for (int x = 0; x < mPointerEvents.length; x++) {
+            if (mPointerEvents[x] == null)
+                mPointerEvents[x] = new PointerEvent();
+            mPointerEvents[x].startX = mPointerEvents[x].startY =
+                    mPointerEvents[x].endX = mPointerEvents[x].endY = -1;
+        }
+    }
+
+    /**
+     * Collects pointer touch information converting from relative to absolute before
+     * storing it as starting touch coordinates.
+     *
+     * @param event
+     * @param view
+     * @param pointerIndex
+     */
+    private void collectStartAction(MotionEvent event, View view, int pointerIndex) {
+        int offsetInScreen[] = new int[2];
+        view.getLocationOnScreen(offsetInScreen);
+        mPointerEvents[getPointerId(event)].startX =
+                (int)(event.getX(pointerIndex) + offsetInScreen[0]);
+        mPointerEvents[getPointerId(event)].startY =
+                (int)(event.getY(pointerIndex) + offsetInScreen[1]);
+    }
+
+    /**
+     * Collects pointer touch information converting from relative to absolute before
+     * storing it as ending touch coordinates.
+     *
+     * @param event
+     * @param view
+     * @param pointerIndex
+     */
+    private void collectEndAction(MotionEvent event, View view, int pointerIndex) {
+        int offsetInScreen[] = new int[2];
+        view.getLocationOnScreen(offsetInScreen);
+        mPointerEvents[getPointerId(event)].endX =
+                (int)(event.getX(pointerIndex) + offsetInScreen[0]);
+        mPointerEvents[getPointerId(event)].endY =
+                (int)(event.getY(pointerIndex) + offsetInScreen[1]);
+    }
+
+    private int getPointerId(MotionEvent event) {
+        return event.getPointerId(getPointerIndex(event));
+    }
+
+    private int getPointerIndex(MotionEvent event) {
+        return ((event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
+                >> MotionEvent.ACTION_POINTER_INDEX_SHIFT);
+    }
 }
diff --git a/tools/cts-java-scanner/src/com/android/cts/javascanner/DocletRunner.java b/tools/cts-java-scanner/src/com/android/cts/javascanner/DocletRunner.java
index ba87156..2afc4fa 100644
--- a/tools/cts-java-scanner/src/com/android/cts/javascanner/DocletRunner.java
+++ b/tools/cts-java-scanner/src/com/android/cts/javascanner/DocletRunner.java
@@ -72,6 +72,7 @@
         // PTS adds PtsAndroidTestCase
         sourcePath.add("./cts/suite/pts/deviceTests/ptsutil/src");
         sourcePath.add("./cts/libs/util/src");
+        sourcePath.add("./frameworks/testing/uiautomator/library/testrunner-src");
         sourcePath.add(sourceDir.toString());
         return join(sourcePath, ":");
     }
diff --git a/tools/cts-native-scanner/Android.mk b/tools/cts-native-scanner/Android.mk
index f8b1629..8bcff1c 100644
--- a/tools/cts-native-scanner/Android.mk
+++ b/tools/cts-native-scanner/Android.mk
@@ -34,10 +34,5 @@
 	$(copy-file-to-new-target)
 	$(hide) chmod 755 $@
 
-# the other stuff
-# ============================================================
-subdirs := $(addprefix $(LOCAL_PATH)/,$(addsuffix /Android.mk, \
-		src \
-	))
-
-include $(subdirs)
+# Build all sub-directories
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tools/cts-native-scanner/src/com/android/cts/nativescanner/TestScanner.java b/tools/cts-native-scanner/src/com/android/cts/nativescanner/TestScanner.java
index 9411566..0a1b207 100644
--- a/tools/cts-native-scanner/src/com/android/cts/nativescanner/TestScanner.java
+++ b/tools/cts-native-scanner/src/com/android/cts/nativescanner/TestScanner.java
@@ -30,12 +30,14 @@
  * Scanner of C++ gTest source files.
  *
  * It looks for test declarations and outputs a file following this format:
- *
- * class:TestClass1
- * method:testMethod1
- * method:testMethod2
- * class:TestClass2
- * method:testMethod1
+ * suite:TestSuite
+ * case:TestCase1
+ * test:Test1
+ * test:Test2
+ * suite:TestSuite
+ * case:TestCase2
+ * test:Test1
+ * test:Test2
  *
  */
 class TestScanner {
@@ -71,38 +73,36 @@
             if (file.isDirectory()) {
                 scanDir(file, testNames);
             } else {
-                scanFile(file, testNames);
+                scanFile(new Scanner(file), testNames);
             }
         }
     }
-    // We want to find lines like class SLObjectCreationTest : public ::testing::Test { ...
-    // and extract the "SLObjectCreationTest" as group #1
-    private static final Pattern CLASS_REGEX =
-            Pattern.compile("\\s*class\\s+(\\w+).*");
 
     // We want to find lines like TEST_F(SLObjectCreationTest, testAudioPlayerFromFdCreation) { ...
-    // and extract the "testAudioPlayerFromFdCreation" as group #1
+    // and extract the "SLObjectCreationTest" as group #1,
+    // "testAudioPlayerFromFdCreation" as group #2
     private static final Pattern METHOD_REGEX =
-            Pattern.compile("\\s*TEST_F\\(\\w+,\\s*(\\w+)\\).*");
+            Pattern.compile("\\s*TEST_F\\((\\w+),\\s*(\\w+)\\).*");
 
-    private void scanFile(File file, List<String> testNames) throws FileNotFoundException {
-        Scanner scanner = null;
+    public void scanFile(Scanner scanner, List<String> testNames) {
         try {
-            scanner = new Scanner(file);
+            String lastCase = "";
             while (scanner.hasNextLine()) {
                 String line = scanner.nextLine();
-                Matcher matcher = CLASS_REGEX.matcher(line);
-                if (matcher.matches()) {
-                    testNames.add("suite:" + mTestSuite);
-                    testNames.add("case:" + matcher.group(1));
+
+                Matcher matcher = METHOD_REGEX.matcher(line);
+
+                if (!matcher.matches()) {
                     continue;
                 }
 
-                matcher = METHOD_REGEX.matcher(line);
-                if (matcher.matches()) {
-                    testNames.add("test:" + matcher.group(1));
-                    continue;
+                if (!lastCase.equals(matcher.group(1))) {
+                    testNames.add("suite:" + mTestSuite);
+                    testNames.add("case:" + matcher.group(1));
+                    lastCase = matcher.group(1);
                 }
+
+                testNames.add("test:" + matcher.group(2));
             }
         } finally {
             if (scanner != null) {
diff --git a/suite/pts/hostTests/ptshostutil/Android.mk b/tools/cts-native-scanner/tests/Android.mk
similarity index 71%
copy from suite/pts/hostTests/ptshostutil/Android.mk
copy to tools/cts-native-scanner/tests/Android.mk
index 14e786a..6044b0d 100644
--- a/suite/pts/hostTests/ptshostutil/Android.mk
+++ b/tools/cts-native-scanner/tests/Android.mk
@@ -1,4 +1,4 @@
-# Copyright (C) 2012 The Android Open Source Project
+# Copyright 2012 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.
@@ -16,14 +16,13 @@
 
 include $(CLEAR_VARS)
 
-LOCAL_SRC_FILES := \
-    $(call all-java-files-under, src)
+# Only compile source java files in this lib
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed-prebuilt ddmlib-prebuilt junit
-
+LOCAL_MODULE := cts-native-scanner-tests
 LOCAL_MODULE_TAGS := optional
-
-LOCAL_MODULE := ptshostutil
+LOCAL_JAVA_LIBRARIES := ddmlib-prebuilt tradefed-prebuilt cts-native-scanner
 
 include $(BUILD_HOST_JAVA_LIBRARY)
 
+
diff --git a/tools/cts-native-scanner/tests/run_unit_tests.sh b/tools/cts-native-scanner/tests/run_unit_tests.sh
new file mode 100755
index 0000000..a42e42b
--- /dev/null
+++ b/tools/cts-native-scanner/tests/run_unit_tests.sh
@@ -0,0 +1,36 @@
+#!/bin/bash
+
+# Copyright 2012 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.
+
+# helper script for running the cts-tradefed unit tests
+
+checkFile() {
+    if [ ! -f "$1" ]; then
+        echo "Unable to locate $1"
+        exit
+    fi;
+}
+
+JAR_DIR=${ANDROID_HOST_OUT}/framework
+JARS="ddmlib-prebuilt.jar tradefed-prebuilt.jar hosttestlib.jar cts-native-scanner.jar cts-native-scanner-tests.jar"
+
+for JAR in $JARS; do
+    checkFile ${JAR_DIR}/${JAR}
+    JAR_PATH=${JAR_PATH}:${JAR_DIR}/${JAR}
+done
+
+java $RDBG_FLAG \
+  -cp ${JAR_PATH} com.android.tradefed.command.Console run singleCommand host -n --class com.android.cts.nativescanner.UnitTests "$@"
+
diff --git a/tools/cts-native-scanner/tests/src/com/android/cts/nativescanner/TestScannerTest.java b/tools/cts-native-scanner/tests/src/com/android/cts/nativescanner/TestScannerTest.java
new file mode 100644
index 0000000..18732fd
--- /dev/null
+++ b/tools/cts-native-scanner/tests/src/com/android/cts/nativescanner/TestScannerTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2012 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.nativescanner;
+
+import com.android.cts.nativescanner.TestScanner;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.io.StringReader;
+import java.lang.StringBuilder;
+import java.util.Scanner;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+/**
+ * Unit tests for {@link TestScanner}.
+ */
+public class TestScannerTest extends TestCase {
+
+    public void testScanFile() {
+        TestScanner testScanner = new TestScanner(new File("unused"), "TestSuite");
+
+        String newLine = System.getProperty("line.separator");
+        StringBuilder sb = new StringBuilder();
+        sb.append("foobar" + newLine);  // ignored
+        sb.append("TEST_F(TestCase1, TestName1)" + newLine);  // valid
+        sb.append("TEST_F(TestCase1, TestName2)" + newLine);  // valid
+        sb.append("TEST_F(TestCase2, TestName1) foo" + newLine);  // valid
+        sb.append("TEST_F(TestCase2, TestName1 foo)" + newLine);  // ignored
+        sb.append("foo TEST_F(TestCase2, TestName1)" + newLine);  // ignored
+
+        List<String> names = new ArrayList<String>();
+        Scanner scanner = new Scanner(new StringReader(sb.toString()));
+        testScanner.scanFile(scanner, names);
+        Iterator it = names.iterator();
+
+        assertEquals("suite:TestSuite", it.next());
+        assertEquals("case:TestCase1", it.next());
+        assertEquals("test:TestName1", it.next());
+        assertEquals("test:TestName2", it.next());
+        assertEquals("suite:TestSuite", it.next());
+        assertEquals("case:TestCase2", it.next());
+        assertEquals("test:TestName1", it.next());
+        assertFalse(it.hasNext());
+        scanner.close();
+    }
+}
diff --git a/tools/cts-native-scanner/tests/src/com/android/cts/nativescanner/UnitTests.java b/tools/cts-native-scanner/tests/src/com/android/cts/nativescanner/UnitTests.java
new file mode 100644
index 0000000..13d4e5b
--- /dev/null
+++ b/tools/cts-native-scanner/tests/src/com/android/cts/nativescanner/UnitTests.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2012 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.nativescanner;
+
+import com.android.cts.nativescanner.TestScannerTest;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * A test suite for all cts-native-scanner unit tests.
+ * <p/>
+ * All tests listed here should be self-contained, and do not require any external dependencies
+ */
+public class UnitTests extends TestSuite {
+
+    public UnitTests() {
+        super();
+
+        // result package
+        addTestSuite(TestScannerTest.class);
+    }
+
+    public static Test suite() {
+        return new UnitTests();
+    }
+}
diff --git a/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/CtsXmlGenerator.java b/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/CtsXmlGenerator.java
index ce4fdd7..c5b253a 100644
--- a/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/CtsXmlGenerator.java
+++ b/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/CtsXmlGenerator.java
@@ -49,6 +49,8 @@
         String instrumentation = null;
         String testType = null;
         String jarPath = null;
+        String appNameSpace = null;
+        String targetNameSpace = null;
 
         for (int i = 0; i < args.length; i++) {
             if ("-p".equals(args[i])) {
@@ -68,15 +70,17 @@
                         "Missing value for expectation store")));
             } else if ("-o".equals(args[i])) {
                 outputPath = getArg(args, ++i, "Missing value for output file");
+            } else if ("-a".equals(args[i])) {
+                appNameSpace =  getArg(args, ++i, "Missing value for app name space");
+            } else if ("-r".equals(args[i])) {
+                targetNameSpace =  getArg(args, ++i, "Missing value for target name space");
             } else {
                 System.err.println("Unsupported flag: " + args[i]);
                 usage(args);
             }
         }
 
-        String appNameSpace = null;
         String runner = null;
-        String targetNameSpace = null;
 
         if (manifestFile != null) {
             Document manifest = DocumentBuilderFactory.newInstance().newDocumentBuilder()
diff --git a/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/Test.java b/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/Test.java
index a9dd5e0..93b838b 100644
--- a/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/Test.java
+++ b/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/Test.java
@@ -16,7 +16,7 @@
 
 package com.android.cts.xmlgenerator;
 
-public class Test {
+public class Test implements Comparable<Test> {
     private String mName;
     private int mTimeout;
 
@@ -32,4 +32,9 @@
     public int getTimeout() {
         return mTimeout;
     }
+
+    @Override
+    public int compareTo(Test another) {
+        return getName().compareTo(another.getName());
+    }
 }
diff --git a/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/TestCase.java b/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/TestCase.java
index a9ea6e1..ed09b8e 100644
--- a/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/TestCase.java
+++ b/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/TestCase.java
@@ -20,7 +20,7 @@
 import java.util.Collections;
 import java.util.List;
 
-class TestCase {
+class TestCase implements Comparable<TestCase> {
 
     private final String mName;
 
@@ -41,4 +41,9 @@
     public Collection<Test> getTests() {
         return Collections.unmodifiableCollection(mTests);
     }
+
+    @Override
+    public int compareTo(TestCase another) {
+        return getName().compareTo(another.getName());
+    }
 }
diff --git a/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/TestSuite.java b/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/TestSuite.java
index 3c29603..466f8d7 100644
--- a/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/TestSuite.java
+++ b/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/TestSuite.java
@@ -22,7 +22,7 @@
 import java.util.List;
 import java.util.Map;
 
-class TestSuite {
+class TestSuite implements Comparable<TestSuite> {
 
     private final String mName;
 
@@ -61,4 +61,9 @@
     public Collection<TestCase> getCases() {
         return Collections.unmodifiableCollection(mCases);
     }
+
+    @Override
+    public int compareTo(TestSuite another) {
+        return getName().compareTo(another.getName());
+    }
 }
diff --git a/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/XmlGenerator.java b/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/XmlGenerator.java
index 8a49cf3..7b1996d 100644
--- a/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/XmlGenerator.java
+++ b/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/XmlGenerator.java
@@ -24,7 +24,10 @@
 import java.io.IOException;
 import java.io.OutputStream;
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
 
 /**
  * Generator of TestPackage XML files for native tests.
@@ -141,7 +144,8 @@
 
     private void writeTestSuites(PrintWriter writer, Collection<TestSuite> suites,
             StringBuilder nameCollector) {
-        for (TestSuite suite : suites) {
+        Collection<TestSuite> sorted = sortCollection(suites);
+        for (TestSuite suite : sorted) {
             writer.append("<TestSuite name=\"").append(suite.getName()).println("\">");
 
             String namePart = suite.getName();
@@ -161,7 +165,8 @@
 
     private void writeTestCases(PrintWriter writer, Collection<TestCase> cases,
             StringBuilder nameCollector) {
-        for (TestCase testCase : cases) {
+        Collection<TestCase> sorted = sortCollection(cases);
+        for (TestCase testCase : sorted) {
             String name = testCase.getName();
             writer.append("<TestCase name=\"").append(name).println("\">");
             nameCollector.append('.').append(name);
@@ -176,7 +181,8 @@
 
     private void writeTests(PrintWriter writer, Collection<Test> tests,
             StringBuilder nameCollector) {
-        for (Test test : tests) {
+        Collection<Test> sorted = sortCollection(tests);
+        for (Test test : sorted) {
             nameCollector.append('#').append(test.getName());
             writer.append("<Test name=\"").append(test.getName()).append("\"");
             if (isKnownFailure(mExpectations, nameCollector.toString())) {
@@ -192,6 +198,12 @@
         }
     }
 
+    private <E extends Comparable<E>> Collection<E> sortCollection(Collection<E> col) {
+        List<E> list = new ArrayList<E>(col);
+        Collections.sort(list);
+        return list;
+    }
+
     public static boolean isKnownFailure(ExpectationStore expectationStore, String testName) {
         return expectationStore != null && expectationStore.get(testName) != Expectation.SUCCESS;
     }
diff --git a/tools/tradefed-host/.classpath b/tools/tradefed-host/.classpath
index 275ecf9..0b1866d 100644
--- a/tools/tradefed-host/.classpath
+++ b/tools/tradefed-host/.classpath
@@ -3,10 +3,9 @@
 	<classpathentry kind="src" path="src"/>
 	<classpathentry kind="src" path="res"/>
 	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3"/>
-	<classpathentry combineaccessrules="false" kind="src" path="/ddmlib"/>
-	<classpathentry combineaccessrules="false" kind="src" path="/tradefederation"/>
 	<classpathentry exported="true" kind="var" path="CTS_SRC_ROOT/out/host/common/obj/JAVA_LIBRARIES/ctsdeviceinfolib_intermediates/javalib.jar"/>
 	<classpathentry exported="true" kind="var" path="CTS_SRC_ROOT/out/host/common/obj/JAVA_LIBRARIES/hosttestlib_intermediates/javalib.jar"/>
+	<classpathentry kind="var" path="CTS_SRC_ROOT/out/host/common/obj/JAVA_LIBRARIES/ddmlib-prebuilt_intermediates/ddmlib-prebuilt.jar" sourcepath="/SDK_SRC_ROOT"/>
+	<classpathentry kind="var" path="CTS_SRC_ROOT/out/host/common/obj/JAVA_LIBRARIES/tradefed-prebuilt_intermediates/tradefed-prebuilt.jar" sourcepath="/TRADEFED_ROOT/tools/tradefederation/src"/>
 	<classpathentry kind="output" path="bin"/>
 </classpath>
diff --git a/tools/tradefed-host/.gitignore b/tools/tradefed-host/.gitignore
new file mode 100644
index 0000000..ba077a4
--- /dev/null
+++ b/tools/tradefed-host/.gitignore
@@ -0,0 +1 @@
+bin
diff --git a/tools/tradefed-host/Android.mk b/tools/tradefed-host/Android.mk
index 7af7993..e8965ab 100644
--- a/tools/tradefed-host/Android.mk
+++ b/tools/tradefed-host/Android.mk
@@ -17,13 +17,16 @@
 include $(CLEAR_VARS)
 
 # Only compile source java files in this lib.
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES := \
+	$(call all-java-files-under, src) \
+	$(call all-java-files-under, ../../suite/pts/hostTests/ptshostutil/src)
+
 LOCAL_JAVA_RESOURCE_DIRS := res
 
 LOCAL_MODULE := cts-tradefed
 LOCAL_MODULE_TAGS := optional
 LOCAL_JAVA_LIBRARIES := ddmlib-prebuilt tradefed-prebuilt hosttestlib
-LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceinfolib
+LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceinfolib ptscommonutilhost
 
 include $(BUILD_HOST_JAVA_LIBRARY)
 
diff --git a/tools/tradefed-host/res/report/cts_result.xsd b/tools/tradefed-host/res/report/cts_result.xsd
index 5e471d5..3f7f384 100644
--- a/tools/tradefed-host/res/report/cts_result.xsd
+++ b/tools/tradefed-host/res/report/cts_result.xsd
@@ -16,8 +16,8 @@
  -->
 
 <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
-           targetNamespace="http://compatibility.android.com/cts_result/1.13"
-           xmlns="http://compatibility.android.com/cts_result/1.13"
+           targetNamespace="http://compatibility.android.com/cts_result/1.15"
+           xmlns="http://compatibility.android.com/cts_result/1.15"
            elementFormDefault="qualified">
 
 <xs:element name="TestResult">
@@ -32,6 +32,7 @@
     <xs:attribute name="endtime" type="xs:string"/>
     <xs:attribute name="testPlan" type="xs:string"/>
     <xs:attribute name="version" type="xs:string"/>
+    <xs:attribute name="suite" type="xs:string"/>
   </xs:complexType>
 </xs:element>
 
@@ -195,6 +196,29 @@
   <xs:attribute name="priority" type="xs:string"/>
 </xs:complexType>
 
+<xs:simpleType name="unitType">
+  <xs:restriction base="xs:string">
+    <xs:enumeration value="none"/>
+    <xs:enumeration value="ms"/>
+    <xs:enumeration value="fps"/>
+    <xs:enumeration value="ops"/>
+    <xs:enumeration value="kbps"/>
+    <xs:enumeration value="mbps"/>
+    <xs:enumeration value="byte"/>
+    <xs:enumeration value="count"/>
+    <xs:enumeration value="score"/>
+  </xs:restriction>
+</xs:simpleType>
+
+<xs:simpleType name="scoreTypeType">
+  <xs:restriction base="xs:string">
+    <xs:enumeration value="higherBetter"/>
+    <xs:enumeration value="lowerBetter"/>
+    <xs:enumeration value="neutral"/>
+    <xs:enumeration value="warning"/>
+  </xs:restriction>
+</xs:simpleType>
+
 <xs:complexType name="testType">
   <xs:sequence>
     <xs:element name="FailedScene" minOccurs="0" maxOccurs="1">
@@ -205,6 +229,35 @@
         <xs:attribute name="message" type="xs:string"/>
       </xs:complexType>
     </xs:element>
+    <xs:element name="Summary" minOccurs="0" maxOccurs="1">
+      <xs:complexType>
+        <xs:simpleContent>
+          <xs:extension base="xs:decimal">
+            <xs:attribute name="message" type="xs:string" use="required" />
+            <xs:attribute name="scoreType" type="scoreTypeType" use="required" />
+            <xs:attribute name="unit" type="unitType" use="required" />
+            <xs:attribute name="target" type="xs:decimal" />
+          </xs:extension>
+        </xs:simpleContent>
+      </xs:complexType>
+    </xs:element>
+    <xs:element name="Details" minOccurs="0" maxOccurs="1">
+      <xs:complexType>
+        <xs:sequence>
+          <xs:element name="ValueArray" minOccurs="0" maxOccurs="unbounded">
+            <xs:complexType>
+              <xs:sequence>
+                <xs:element name="Value" type="xs:decimal" minOccurs="0" maxOccurs="unbounded" />
+              </xs:sequence>
+              <xs:attribute name="source" type="xs:string" use="required" />
+              <xs:attribute name="message" type="xs:string" use="required" />
+              <xs:attribute name="scoreType" type="scoreTypeType" use="required" />
+              <xs:attribute name="unit" type="unitType" use="required" />
+            </xs:complexType>
+          </xs:element>
+        </xs:sequence>
+      </xs:complexType>
+    </xs:element>
   </xs:sequence>
   <xs:attribute name="name" type="xs:string" use="required"/>
   <xs:attribute name="result" type="resultType" use="required"/>
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/build/CtsBuildHelper.java b/tools/tradefed-host/src/com/android/cts/tradefed/build/CtsBuildHelper.java
index 95ec69f..61b4b43 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/build/CtsBuildHelper.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/build/CtsBuildHelper.java
@@ -30,18 +30,12 @@
 public class CtsBuildHelper {
 
     static final String CTS_DIR_NAME = "android-cts";
-    static final String PTS_DIR_NAME = "android-pts";
-    static private boolean mCtsMode = true;
-    private final String mSuiteName;
+    private final String mSuiteName = "CTS";
     /** The root location of the extracted CTS package */
     private final File mRootDir;
     /** the {@link CTS_DIR_NAME} directory */
     private final File mCtsDir;
 
-    public static void changeToPtsMode() {
-        mCtsMode = false;
-    }
-
     /**
      * Creates a {@link CtsBuildHelper}.
      *
@@ -50,8 +44,7 @@
      */
     public CtsBuildHelper(File rootDir) {
         mRootDir = rootDir;
-        mSuiteName = mCtsMode? "CTS" : "PTS";
-        mCtsDir = new File(mRootDir, mCtsMode ? CTS_DIR_NAME : PTS_DIR_NAME);
+        mCtsDir = new File(mRootDir, CTS_DIR_NAME);
     }
 
     /**
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/command/CtsConsole.java b/tools/tradefed-host/src/com/android/cts/tradefed/command/CtsConsole.java
index f014135..40145dd 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/command/CtsConsole.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/command/CtsConsole.java
@@ -245,12 +245,7 @@
         return mCtsBuild;
     }
 
-    public static void main(String[] args) throws InterruptedException {
-        // change to PTS mode before anything else
-        String ptsMode = System.getProperty("PTS");
-        if ((ptsMode != null) && ptsMode.equals("1")) {
-            CtsBuildHelper.changeToPtsMode();
-        }
+    public static void main(String[] args) throws InterruptedException, ConfigurationException {
         Console console = new CtsConsole();
         Console.startConsole(console, args);
     }
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsXmlResultReporter.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsXmlResultReporter.java
index a2a31e0..2f62d71 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsXmlResultReporter.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsXmlResultReporter.java
@@ -58,7 +58,7 @@
     private static final String LOG_TAG = "CtsXmlResultReporter";
 
     static final String TEST_RESULT_FILE_NAME = "testResult.xml";
-    private static final String CTS_RESULT_FILE_VERSION = "1.13";
+    private static final String CTS_RESULT_FILE_VERSION = "1.14";
     private static final String[] CTS_RESULT_RESOURCES = {"cts_result.xsl", "cts_result.css",
         "logo.gif", "newrule-green.png"};
 
@@ -97,6 +97,7 @@
     private boolean mIsDeviceInfoRun = false;
     private ResultReporter mReporter;
     private File mLogDir;
+    private String mSuiteName;
 
     private static final String PTS_PERFORMANCE_EXCEPTION = "com.android.pts.util.PtsException";
     private static final Pattern mPtsLogPattern = Pattern.compile(
@@ -141,8 +142,8 @@
             mStartTime = getTimestamp();
             logResult("Created result dir %s", mReportDir.getName());
         }
-
-        mReporter = new ResultReporter(mResultServer, ctsBuildHelper.getSuiteName());
+        mSuiteName = ctsBuildHelper.getSuiteName();
+        mReporter = new ResultReporter(mResultServer, mSuiteName);
 
         // TODO: allow customization of log dir
         // create a unique directory for saving logs, with same name as result dir
@@ -358,6 +359,7 @@
         serializer.attribute(ns, STARTTIME_ATTR, startTime);
         serializer.attribute(ns, "endtime", endTime);
         serializer.attribute(ns, "version", CTS_RESULT_FILE_VERSION);
+        serializer.attribute(ns, "suite", mSuiteName);
 
         mResults.serialize(serializer);
         // TODO: not sure why, but the serializer doesn't like this statement
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/Test.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/Test.java
index dd4f31f..72e2e27 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/result/Test.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/Test.java
@@ -15,6 +15,7 @@
  */
 package com.android.cts.tradefed.result;
 
+import com.android.ddmlib.Log;
 import com.android.tradefed.result.TestResult;
 
 import org.kxml2.io.KXmlSerializer;
@@ -27,7 +28,6 @@
  * Data structure that represents a "Test" result XML element.
  */
 class Test extends AbstractXmlPullParser {
-
     static final String TAG = "Test";
     private static final String NAME_ATTR = "name";
     private static final String MESSAGE_ATTR = "message";
@@ -36,7 +36,17 @@
     private static final String RESULT_ATTR = "result";
     private static final String SCENE_TAG = "FailedScene";
     private static final String STACK_TAG = "StackTrace";
+    private static final String SUMMARY_TAG = "Summary";
     private static final String DETAILS_TAG = "Details";
+    private static final String VALUEARRAY_TAG = "ValueArray";
+    private static final String VALUE_TAG = "Value";
+    private static final String TARGET_ATTR = "target";
+    private static final String SCORETYPE_ATTR = "scoreType";
+    private static final String UNIT_ATTR = "unit";
+    private static final String SOURCE_ATTR = "source";
+    // separators for the message from PTS
+    private static final String LOG_SEPARATOR = "\\+\\+\\+";
+    private static final String LOG_ELEM_SEPARATOR = "\\|";
 
     private String mName;
     private CtsTestStatus mResult;
@@ -44,7 +54,8 @@
     private String mEndTime;
     private String mMessage;
     private String mStackTrace;
-    // details passed from pts
+    // summary and details passed from pts
+    private String mSummary;
     private String mDetails;
 
     /**
@@ -109,6 +120,14 @@
         mMessage = getFailureMessageFromStackTrace(mStackTrace);
     }
 
+    public String getSummary() {
+        return mSummary;
+    }
+
+    public void setSummary(String summary) {
+        mSummary = summary;
+    }
+
     public String getDetails() {
         return mDetails;
     }
@@ -147,17 +166,115 @@
                 serializer.text(mStackTrace);
                 serializer.endTag(CtsXmlResultReporter.ns, STACK_TAG);
             }
-            if (mDetails != null) {
-                serializer.startTag(CtsXmlResultReporter.ns, DETAILS_TAG);
-                serializer.text(mDetails);
-                serializer.endTag(CtsXmlResultReporter.ns, DETAILS_TAG);
-            }
             serializer.endTag(CtsXmlResultReporter.ns, SCENE_TAG);
         }
+        if (mSummary != null) {
+            // <Summary message = "screen copies per sec" scoretype="higherBetter" unit="fps">
+            // 23938.82978723404</Summary>
+            PerfResultSummary summary = parseSummary(mSummary);
+            if (summary != null) {
+                serializer.startTag(CtsXmlResultReporter.ns, SUMMARY_TAG);
+                serializer.attribute(CtsXmlResultReporter.ns, MESSAGE_ATTR, summary.mMessage);
+                if (summary.mTarget.length() != 0 && !summary.mTarget.equals(" ")) {
+                    serializer.attribute(CtsXmlResultReporter.ns, TARGET_ATTR, summary.mTarget);
+                }
+                serializer.attribute(CtsXmlResultReporter.ns, SCORETYPE_ATTR, summary.mType);
+                serializer.attribute(CtsXmlResultReporter.ns, UNIT_ATTR, summary.mUnit);
+                serializer.text(summary.mValue);
+                serializer.endTag(CtsXmlResultReporter.ns, SUMMARY_TAG);
+                // add details only if summary is present
+                // <Details>
+                //   <ValueArray source=”com.android.pts.dram.BandwidthTest#doRunMemcpy:98”
+                //                    message=”measure1” unit="ms" scoretype="higherBetter">
+                //     <Value>0.0</Value>
+                //     <Value>0.1</Value>
+                //   </ValueArray>
+                // </Details>
+                if (mDetails != null) {
+                    PerfResultDetail[] ds = parseDetails(mDetails);
+                    serializer.startTag(CtsXmlResultReporter.ns, DETAILS_TAG);
+                        for (PerfResultDetail d : ds) {
+                            if (d == null) {
+                                continue;
+                            }
+                            serializer.startTag(CtsXmlResultReporter.ns, VALUEARRAY_TAG);
+                            serializer.attribute(CtsXmlResultReporter.ns, SOURCE_ATTR, d.mSource);
+                            serializer.attribute(CtsXmlResultReporter.ns, MESSAGE_ATTR,
+                                    d.mMessage);
+                            serializer.attribute(CtsXmlResultReporter.ns, SCORETYPE_ATTR, d.mType);
+                            serializer.attribute(CtsXmlResultReporter.ns, UNIT_ATTR, d.mUnit);
+                            for (String v : d.mValues) {
+                                if (v == null) {
+                                    continue;
+                                }
+                                serializer.startTag(CtsXmlResultReporter.ns, VALUE_TAG);
+                                serializer.text(v);
+                                serializer.endTag(CtsXmlResultReporter.ns, VALUE_TAG);
+                            }
+                            serializer.endTag(CtsXmlResultReporter.ns, VALUEARRAY_TAG);
+                        }
+                    serializer.endTag(CtsXmlResultReporter.ns, DETAILS_TAG);
+                }
+            }
+        }
         serializer.endTag(CtsXmlResultReporter.ns, TAG);
     }
 
     /**
+     *  class containing performance result.
+     */
+    public static class PerfResultCommon {
+        public String mMessage;
+        public String mType;
+        public String mUnit;
+    }
+
+    private class PerfResultSummary extends PerfResultCommon {
+        public String mTarget;
+        public String mValue;
+    }
+
+    private class PerfResultDetail extends PerfResultCommon {
+        public String mSource;
+        public String[] mValues;
+    }
+
+    private PerfResultSummary parseSummary(String summary) {
+        String[] elems = summary.split(LOG_ELEM_SEPARATOR);
+        PerfResultSummary r = new PerfResultSummary();
+        if (elems.length < 5) {
+            Log.w(TAG, "wrong message " + summary);
+            return null;
+        }
+        r.mMessage = elems[0];
+        r.mTarget = elems[1];
+        r.mType = elems[2];
+        r.mUnit = elems[3];
+        r.mValue = elems[4];
+        return r;
+    }
+
+    private PerfResultDetail[] parseDetails(String details) {
+        String[] arrays = details.split(LOG_SEPARATOR);
+        PerfResultDetail[] rs = new PerfResultDetail[arrays.length];
+        for (int i = 0; i < arrays.length; i++) {
+            String[] elems = arrays[i].split(LOG_ELEM_SEPARATOR);
+            if (elems.length < 5) {
+                Log.w(TAG, "wrong message " + arrays[i]);
+                continue;
+            }
+            PerfResultDetail r = new PerfResultDetail();
+            r.mSource = elems[0];
+            r.mMessage = elems[1];
+            r.mType = elems[2];
+            r.mUnit = elems[3];
+            r.mValues = elems[4].split(" ");
+            rs[i] = r;
+        }
+        return rs;
+    }
+
+    /**
      * Strip out any invalid XML characters that might cause the report to be unviewable.
      * http://www.w3.org/TR/REC-xml/#dt-character
      */
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/TestPackageResult.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/TestPackageResult.java
index ce1c2f3..015a7ae 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/result/TestPackageResult.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/TestPackageResult.java
@@ -283,7 +283,7 @@
     public void reportPerformanceResult(TestIdentifier test, CtsTestStatus status, String summary, String details) {
         Test result = findTest(test);
         result.setResultStatus(status);
-        result.setMessage(summary);
+        result.setSummary(summary);
         result.setDetails(details);
     }
 
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/targetprep/CtsRootDeviceSetup.java b/tools/tradefed-host/src/com/android/cts/tradefed/targetprep/CtsRootDeviceSetup.java
index 09129ca..0e768e2 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/targetprep/CtsRootDeviceSetup.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/targetprep/CtsRootDeviceSetup.java
@@ -66,7 +66,10 @@
             // perform CTS setup steps that only work if adb is root
             SettingsToggler.setSecureInt(device, "mock_location", 1);
             enableDeviceAdmin(device, buildHelper);
-
+            // This is chrome specific setting to disable the first screen.
+            // For other browser, it will not do anything.
+            device.executeShellCommand(
+                    "echo \"chrome --disable-fre\" > /data/local/chrome-command-line");
             // end root setup steps
         } catch (FileNotFoundException e) {
             throw new TargetSetupError("Invalid CTS installation", e);
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/targetprep/SettingsToggler.java b/tools/tradefed-host/src/com/android/cts/tradefed/targetprep/SettingsToggler.java
index 69d4c8a..ff6c4f4 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/targetprep/SettingsToggler.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/targetprep/SettingsToggler.java
@@ -22,52 +22,98 @@
  * {@link SettingsToggler} sets settings by using the "adb shell content" command.
  */
 public class SettingsToggler {
+    private static final String GROUP_SECURE = "secure";
+    private static final String GROUP_GLOBAL = "global";
+
+    /** Sets a setting by deleting and then inserting the string value. */
+    public static void setString(ITestDevice device, String group, String name, String value)
+            throws DeviceNotAvailableException {
+        deleteSetting(device, group, name);
+        device.executeShellCommand(
+                "content insert"
+                + " --uri content://settings/" + group
+                + " --bind name:s:" + name
+                + " --bind value:s:" + value);
+    }
 
     /** Sets a secure setting by deleting and then inserting the string value. */
     public static void setSecureString(ITestDevice device, String name, String value)
             throws DeviceNotAvailableException {
-        deleteSecure(device, name);
+        setString(device, GROUP_SECURE, name, value);
+    }
+
+    /** Sets a global setting by deleting and then inserting the string value. */
+    public static void setGlobalString(ITestDevice device, String name, String value)
+            throws DeviceNotAvailableException {
+        setString(device, GROUP_GLOBAL, name, value);
+    }
+
+    /** Sets a setting by deleting and then inserting the int value. */
+    public static void setInt(ITestDevice device, String group, String name, int value)
+            throws DeviceNotAvailableException {
+        deleteSetting(device, group, name);
         device.executeShellCommand(
                 "content insert"
-                + " --uri content://settings/secure"
+                + " --uri content://settings/" + group
                 + " --bind name:s:" + name
-                + " --bind value:s:" + value);
+                + " --bind value:i:" + value);
     }
 
     /** Sets a secure setting by deleting and then inserting the int value. */
     public static void setSecureInt(ITestDevice device, String name, int value)
             throws DeviceNotAvailableException {
-        deleteSecure(device, name);
+        setInt(device, GROUP_SECURE, name, value);
+    }
+
+    /** Sets a global setting by deleting and then inserting the int value. */
+    public static void setGlobalInt(ITestDevice device, String name, int value)
+            throws DeviceNotAvailableException {
+        setInt(device, GROUP_GLOBAL, name, value);
+    }
+
+    public static void updateString(ITestDevice device, String group, String name, String value)
+            throws DeviceNotAvailableException {
         device.executeShellCommand(
-                "content insert"
-                + " --uri content://settings/secure"
-                + " --bind name:s:" + name
-                + " --bind value:i:" + value);
+                "content update"
+                + " --uri content://settings/" + group
+                + " --bind value:s:" + value
+                + " --where \"name='" + name + "'\"");
     }
 
     public static void updateSecureString(ITestDevice device, String name, String value)
             throws DeviceNotAvailableException {
+        updateString(device, GROUP_SECURE, name, value);
+    }
+
+    public static void updateGlobalString(ITestDevice device, String name, String value)
+            throws DeviceNotAvailableException {
+        updateString(device, GROUP_GLOBAL, name, value);
+    }
+
+    public static void updateInt(ITestDevice device, String group, String name, int value)
+            throws DeviceNotAvailableException {
         device.executeShellCommand(
                 "content update"
-                + " --uri content://settings/secure"
-                + " --bind value:s:" + value
+                + " --uri content://settings/" + group
+                + " --bind value:i:" + value
                 + " --where \"name='" + name + "'\"");
     }
 
     public static void updateSecureInt(ITestDevice device, String name, int value)
             throws DeviceNotAvailableException {
-        device.executeShellCommand(
-                "content update"
-                + " --uri content://settings/secure"
-                + " --bind value:i:" + value
-                + " --where \"name='" + name + "'\"");
+        updateInt(device, GROUP_SECURE, name, value);
     }
 
-    private static void deleteSecure(ITestDevice device, String name)
+    public static void updateGlobalInt(ITestDevice device, String name, int value)
+            throws DeviceNotAvailableException {
+        updateInt(device, GROUP_GLOBAL, name, value);
+    }
+
+    private static void deleteSetting(ITestDevice device, String group, String name)
             throws DeviceNotAvailableException {
         device.executeShellCommand(
                 "content delete"
-                + " --uri content://settings/secure"
+                + " --uri content://settings/" + group
                 + " --where \"name='" + name + "'\"");
     }
 }
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/AccessibilityServiceTestRunner.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/AccessibilityServiceTestRunner.java
index 7490f18..d333943 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/AccessibilityServiceTestRunner.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/AccessibilityServiceTestRunner.java
@@ -67,7 +67,7 @@
     }
 
     private void afterTest() throws DeviceNotAvailableException {
-        AccessibilityTestRunner.disableAccessibilityAndServicesAndTouchExploration(getDevice());
+        AccessibilityTestRunner.disableAccessibilityAndServices(getDevice());
         uninstallAndAssert(DELEGATING_ACCESSIBLITY_SERVICE_PACKAGE_NAME);
     }
 
@@ -85,7 +85,7 @@
     private void enableAccessibilityAndDelegatingService() throws DeviceNotAvailableException {
         String componentName = DELEGATING_ACCESSIBLITY_SERVICE_PACKAGE_NAME + "/"
             + DELEGATING_ACCESSIBLITY_SERVICE_NAME;
-        AccessibilityTestRunner.enableAccessibilityAndServicesAndTouchExploration(getDevice(),
+        AccessibilityTestRunner.enableAccessibilityAndServices(getDevice(),
                 componentName);
     }
 }
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/AccessibilityTestRunner.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/AccessibilityTestRunner.java
index ca4f9e1..2a4239a 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/AccessibilityTestRunner.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/AccessibilityTestRunner.java
@@ -68,11 +68,11 @@
 
     private void beforeTest() throws DeviceNotAvailableException {
         installApkAndAssert(SOME_ACCESSIBLITY_SERVICES_APK);
-        enableAccessibilityAndServicesAndTouchExploration();
+        enableAccessibilityAndServices();
     }
 
     private void afterTest() throws DeviceNotAvailableException {
-        disableAccessibilityAndServicesAndTouchExploration(getDevice());
+        disableAccessibilityAndServices(getDevice());
         uninstallAndAssert(SOME_ACCESSIBLITY_SERVICES_PACKAGE_NAME);
     }
 
@@ -87,26 +87,27 @@
         TestCase.assertNull("Error uninstalling: " + packageName, errorMessage);
     }
 
-    private void enableAccessibilityAndServicesAndTouchExploration()
-            throws DeviceNotAvailableException {
+    private void enableAccessibilityAndServices() throws DeviceNotAvailableException {
         String enabledServicesValue =
               SOME_ACCESSIBLITY_SERVICES_PACKAGE_NAME + "/" + SPEAKING_ACCESSIBLITY_SERVICE_NAME
             + ":"
             + SOME_ACCESSIBLITY_SERVICES_PACKAGE_NAME + "/" + VIBRATING_ACCESSIBLITY_SERVICE_NAME;
-        enableAccessibilityAndServicesAndTouchExploration(getDevice(), enabledServicesValue);
+        enableAccessibilityAndServices(getDevice(), enabledServicesValue);
     }
 
-    static void enableAccessibilityAndServicesAndTouchExploration(ITestDevice device, String value)
+    static void enableAccessibilityAndServices(ITestDevice device, String value)
             throws DeviceNotAvailableException {
         SettingsToggler.setSecureString(device, "enabled_accessibility_services", value);
+        SettingsToggler.setSecureString(device,
+                "touch_exploration_granted_accessibility_services", value);
         SettingsToggler.setSecureInt(device, "accessibility_enabled", 1);
-        SettingsToggler.setSecureInt(device, "touch_exploration_enabled", 1);
     }
 
-    static void disableAccessibilityAndServicesAndTouchExploration(ITestDevice device)
+    static void disableAccessibilityAndServices(ITestDevice device)
             throws DeviceNotAvailableException {
         SettingsToggler.updateSecureString(device, "enabled_accessibility_services", "");
+        SettingsToggler.updateSecureString(device,
+                "touch_exploration_granted_accessibility_services", "");
         SettingsToggler.updateSecureInt(device, "accessibility_enabled", 0);
-        SettingsToggler.updateSecureInt(device, "touch_exploration_enabled", 0);
     }
 }
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/CtsTest.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/CtsTest.java
index 19ee3b7..9188dca 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/CtsTest.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/CtsTest.java
@@ -40,6 +40,7 @@
 import com.android.tradefed.testtype.IRemoteTest;
 import com.android.tradefed.testtype.IResumableTest;
 import com.android.tradefed.testtype.IShardableTest;
+import com.android.tradefed.util.RunUtil;
 import com.android.tradefed.util.xml.AbstractXmlParser.ParseException;
 
 import java.io.BufferedInputStream;
@@ -149,6 +150,20 @@
             "Interval between each reboot in min.")
     private int mRebootIntervalMin = 30;
 
+    @Option(name = "screenshot-on-ui-failure", description =
+            "take a screenshot if a test fails with a message indicating a UI obstruction.")
+    private boolean mScreenshotOnUiFailures = false;
+
+    @Option(name = "logcat-on-failure", description =
+            "take a logcat snapshot on every test failure. Unlike --bugreport, this can capture" +
+            "logs even if connection with device has been lost, as well as being much more " +
+            "performant.")
+    private boolean mLogcatOnFailures = false;
+
+    @Option(name = "logcat-on-failure-size", description =
+            "The max number of logcat data in bytes to capture when --logcat-on-failure is on. " +
+            "Should be an amount that can comfortably fit in memory.")
+    private int mMaxLogcatBytes = 500 * 1024; // 500K
 
     private long mPrevRebootTime; // last reboot time
 
@@ -200,12 +215,72 @@
         public void testFailed(TestFailure status, TestIdentifier test, String trace) {
             super.testFailed(status, test, trace);
             InputStreamSource bugSource = mDevice.getBugreport();
-            super.testLog(String.format("bug-%s", test.toString()), LogDataType.TEXT,
-                    bugSource);
+            super.testLog(String.format("bug-%s_%s", test.getClassName(), test.getTestName()),
+                    LogDataType.TEXT, bugSource);
             bugSource.cancel();
         }
     }
 
+    /**
+     * A {@link ResultForwarder} that will forward a logcat snapshot on each failed test.
+     */
+    private static class FailedTestLogcatGenerator extends ResultForwarder {
+        private ITestDevice mDevice;
+        private int mNumLogcatBytes;
+
+        public FailedTestLogcatGenerator(ITestInvocationListener listener, ITestDevice device,
+                int maxLogcatBytes) {
+            super(listener);
+            mDevice = device;
+            mNumLogcatBytes = maxLogcatBytes;
+        }
+
+        @Override
+        public void testFailed(TestFailure status, TestIdentifier test, String trace) {
+            super.testFailed(status, test, trace);
+            // sleep a small amount of time to ensure test failure stack trace makes it into logcat
+            // capture
+            RunUtil.getDefault().sleep(10);
+            InputStreamSource logSource = mDevice.getLogcat(mNumLogcatBytes);
+            super.testLog(String.format("logcat-%s_%s", test.getClassName(), test.getTestName()),
+                    LogDataType.TEXT, logSource);
+            logSource.cancel();
+        }
+    }
+
+    /**
+     * A {@link ResultForwarder} that will forward a screenshot when it detects a failed test due
+     * to a UI obstruction.
+     */
+    private static class FailedUiTestScreenshotGenerator extends ResultForwarder {
+        private ITestDevice mDevice;
+
+        public FailedUiTestScreenshotGenerator(ITestInvocationListener listener,
+                ITestDevice device) {
+            super(listener);
+            mDevice = device;
+        }
+
+        @Override
+        public void testFailed(TestFailure status, TestIdentifier test, String trace) {
+            super.testFailed(status, test, trace);
+
+            if (trace.contains(
+                    "Injecting to another application requires INJECT_EVENTS permission")) {
+                try {
+                    InputStreamSource screenSource = mDevice.getScreenshot();
+                    super.testLog(String.format("screenshot-%s_%s", test.getClassName(),
+                            test.getTestName()), LogDataType.PNG, screenSource);
+                    screenSource.cancel();
+                } catch (DeviceNotAvailableException e) {
+                    // TODO: rethrow this somehow
+                    CLog.e("Device %s became unavailable while capturing screenshot, %s",
+                            mDevice.getSerialNumber(), e.toString());
+                }
+            }
+        }
+    }
+
     /** list of remaining tests to execute */
     private List<TestPackage> mRemainingTestPkgs = null;
 
@@ -337,6 +412,16 @@
                     getDevice());
             listener = bugListener;
         }
+        if (mScreenshotOnUiFailures) {
+            FailedUiTestScreenshotGenerator screenListener = new FailedUiTestScreenshotGenerator(
+                    listener, getDevice());
+            listener = screenListener;
+        }
+        if (mLogcatOnFailures) {
+            FailedTestLogcatGenerator logcatListener = new FailedTestLogcatGenerator(
+                    listener, getDevice(), mMaxLogcatBytes);
+            listener = logcatListener;
+        }
 
         // collect and install the prerequisiteApks first, to save time when multiple test
         // packages are using the same prerequisite apk (I'm looking at you, CtsTestStubs!)
@@ -424,7 +509,7 @@
     }
 
     private void rebootDevice() throws DeviceNotAvailableException {
-        final int TIMEOUT_MS = 4 * 60 * 1000;
+        final int TIMEOUT_MS = 10 * 60 * 1000;
         TestDeviceOptions options = mDevice.getOptions();
         // store default value and increase time-out for reboot
         int rebootTimeout = options.getRebootTimeout();
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/DisplayTestRunner.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/DisplayTestRunner.java
new file mode 100644
index 0000000..4c83aa9
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/DisplayTestRunner.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2012 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.tradefed.testtype;
+
+import com.android.cts.tradefed.targetprep.SettingsToggler;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.result.ITestInvocationListener;
+
+/**
+ * Running the display tests requires modification of secure settings to create an overlay display.
+ * Secure settings cannot be changed from device CTS tests since system signature permission is
+ * required. Such settings can be modified by the shell user, so a host side test is used.
+ */
+public class DisplayTestRunner extends InstrumentationApkTest {
+    private static final String OVERLAY_DISPLAY_DEVICES_SETTING_NAME = "overlay_display_devices";
+
+    // Use a non-standard pattern, must match values in tests/tests/display/.../DisplayTest.java
+    private static final String OVERLAY_DISPLAY_DEVICES_SETTING_VALUE = "1281x721/214";
+
+    @Override
+    public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
+        // CLog.e("run: About to enable overlay display.");
+        SettingsToggler.setGlobalString(getDevice(), OVERLAY_DISPLAY_DEVICES_SETTING_NAME,
+                OVERLAY_DISPLAY_DEVICES_SETTING_VALUE);
+
+        super.run(listener);
+
+        // Tear down overlay display.
+        // CLog.e("run: About to disable overlay display.");
+        SettingsToggler.setGlobalString(getDevice(), OVERLAY_DISPLAY_DEVICES_SETTING_NAME,
+                "");
+    }
+}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageDef.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageDef.java
index ffbb930..2d11657 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageDef.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageDef.java
@@ -16,6 +16,7 @@
 
 package com.android.cts.tradefed.testtype;
 
+import com.android.ddmlib.Log.LogLevel;
 import com.android.ddmlib.testrunner.TestIdentifier;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.testtype.IRemoteTest;
@@ -43,11 +44,15 @@
 
     public static final String HOST_SIDE_ONLY_TEST = "hostSideOnly";
     public static final String NATIVE_TEST = "native";
+    public static final String WRAPPED_NATIVE_TEST = "wrappednative";
     public static final String VM_HOST_TEST = "vmHostTest";
     public static final String ACCESSIBILITY_TEST =
         "com.android.cts.tradefed.testtype.AccessibilityTestRunner";
     public static final String ACCESSIBILITYSERVICE_TEST =
         "com.android.cts.tradefed.testtype.AccessibilityServiceTestRunner";
+    public static final String DISPLAY_TEST =
+            "com.android.cts.tradefed.testtype.DisplayTestRunner";
+    public static final String UIAUTOMATOR_TEST = "uiAutomator";
 
     private static final String SIGNATURE_TEST_METHOD = "testSignature";
     private static final String SIGNATURE_TEST_CLASS = "android.tests.sigtest.SimpleSignatureTest";
@@ -219,12 +224,21 @@
             return vmHostTest;
         } else if (NATIVE_TEST.equals(mTestType)) {
             return new GeeTest(mUri, mName);
+        } else if (WRAPPED_NATIVE_TEST.equals(mTestType)) {
+            CLog.d("Creating new wrapped native test for %s", mName);
+            return new WrappedGTest(mAppNameSpace, mUri, mName, mRunner);
         } else if (ACCESSIBILITY_TEST.equals(mTestType)) {
             AccessibilityTestRunner test = new AccessibilityTestRunner();
             return setInstrumentationTest(test, testCaseDir);
         } else if (ACCESSIBILITYSERVICE_TEST.equals(mTestType)) {
             AccessibilityServiceTestRunner test = new AccessibilityServiceTestRunner();
             return setInstrumentationTest(test, testCaseDir);
+        } else if (DISPLAY_TEST.equals(mTestType)) {
+            DisplayTestRunner test = new DisplayTestRunner();
+            return setInstrumentationTest(test, testCaseDir);
+        } else if (UIAUTOMATOR_TEST.equals(mTestType)) {
+            UiAutomatorJarTest uiautomatorTest = new UiAutomatorJarTest();
+            return setUiAutomatorTest(uiautomatorTest);
         } else if (mIsSignatureTest) {
             // TODO: hardcode the runner/class/method for now, since current package xml points to
             // specialized instrumentation. Eventually this special case for signatureTest can be
@@ -285,6 +299,28 @@
     }
 
     /**
+     * Populates given {@link UiAutomatorJarTest} with data from the package xml.
+     *
+     * @param uiautomatorTest
+     * @return the populated {@link UiAutomatorJarTest} or <code>null</code>
+     */
+    private IRemoteTest setUiAutomatorTest(UiAutomatorJarTest uiautomatorTest) {
+        uiautomatorTest.setInstallArtifacts(getJarPath());
+        if (mClassName != null) {
+            if (mMethodName != null) {
+                CLog.logAndDisplay(LogLevel.WARN, "ui automator tests don't currently support" +
+                        "running  individual methods");
+            }
+            uiautomatorTest.addClassName(mClassName);
+        } else {
+            uiautomatorTest.addClassNames(mTestClasses);
+        }
+        uiautomatorTest.setRunName(getUri());
+        uiautomatorTest.setCaptureLogs(false);
+        return uiautomatorTest;
+    }
+
+    /**
      * Filter the tests to run based on list of excluded tests, class and method name.
      *
      * @return the filtered collection of tests
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/UiAutomatorJarTest.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/UiAutomatorJarTest.java
new file mode 100644
index 0000000..83cc6d6
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/UiAutomatorJarTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2011 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.tradefed.testtype;
+
+import com.android.cts.tradefed.build.CtsBuildHelper;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.testtype.UiAutomatorTest;
+
+import java.io.FileNotFoundException;
+import java.util.Arrays;
+
+import junit.framework.Assert;
+
+/**
+ * A {@link UiAutomatorTest} that will install a uiautomator jar before test
+ * execution, and uninstall on execution completion.
+ */
+public class UiAutomatorJarTest extends UiAutomatorTest implements IBuildReceiver {
+
+    // TODO: expose this in parent
+    private static final String SHELL_EXE_BASE = "/data/local/tmp/";
+
+    /** the file names of the CTS jar to install */
+    private String mTestJarFileName;
+
+    private CtsBuildHelper mCtsBuild = null;
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setBuild(IBuildInfo build) {
+        mCtsBuild  = CtsBuildHelper.createBuildHelper(build);
+    }
+
+    /**
+     * Setter for CTS build files needed to perform the test. 
+     *
+     * @param testJarName the file name of the jar containing the uiautomator tests
+     */
+    public void setInstallArtifacts(String testJarName) {
+        mTestJarFileName = testJarName;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void run(final ITestInvocationListener listener)
+            throws DeviceNotAvailableException {
+        Assert.assertNotNull("missing device", getDevice());
+        Assert.assertNotNull("missing build", mCtsBuild);
+        Assert.assertNotNull("missing jar to install", mTestJarFileName);
+
+        installJar();
+
+        super.run(listener);
+
+        uninstallJar();
+    }
+
+    private void installJar() throws DeviceNotAvailableException {
+        CLog.d("Installing %s on %s", mTestJarFileName, getDevice().getSerialNumber());
+        String fullJarPath = String.format("%s%s", SHELL_EXE_BASE, mTestJarFileName);
+        try {
+            boolean result = getDevice().pushFile(mCtsBuild.getTestApp(mTestJarFileName),
+                    fullJarPath);
+            Assert.assertTrue(String.format("Failed to push file to %s", fullJarPath), result);
+            setTestJarPaths(Arrays.asList(fullJarPath));
+        }  catch (FileNotFoundException e) {
+            Assert.fail(String.format("Could not find file %s", mTestJarFileName));
+        }
+    }
+
+    private void uninstallJar() throws DeviceNotAvailableException {
+        CLog.d("Uninstalling %s on %s", mTestJarFileName, getDevice().getSerialNumber());
+        String fullJarPath = String.format("%s%s", SHELL_EXE_BASE, mTestJarFileName);
+        getDevice().executeShellCommand(String.format("rm %s", fullJarPath));
+    }
+}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/WrappedGTest.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/WrappedGTest.java
new file mode 100644
index 0000000..74c15f6
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/WrappedGTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2012 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.tradefed.testtype;
+
+import com.android.cts.tradefed.build.CtsBuildHelper;
+import com.android.ddmlib.testrunner.ITestRunListener;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.testtype.IDeviceTest;
+import com.android.tradefed.testtype.IRemoteTest;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+
+/**
+ * Test runner for wrapped (native) GTests
+ */
+public class WrappedGTest implements IBuildReceiver, IDeviceTest, IRemoteTest {
+
+    private int mMaxTestTimeMs = 1 * 60 * 1000;
+
+    private CtsBuildHelper mCtsBuild;
+    private ITestDevice mDevice;
+
+    private final String mAppNameSpace;
+    private final String mRunner;
+    private final String mName;
+    private final String mUri;
+
+
+    public WrappedGTest(String appNameSpace, String uri, String name, String runner) {
+        mAppNameSpace = appNameSpace;
+        mRunner = runner;
+        mName = name;
+        mUri = uri;
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = CtsBuildHelper.createBuildHelper(buildInfo);
+    }
+
+    @Override
+    public void setDevice(ITestDevice device) {
+        mDevice = device;
+    }
+
+    @Override
+    public ITestDevice getDevice() {
+        return mDevice;
+    }
+
+    @Override
+    public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
+        if (installTest()) {
+            runTest(listener);
+            uninstallTest();
+        } else {
+            CLog.e("Failed to install test");
+        }
+    }
+
+    private boolean installTest() throws DeviceNotAvailableException {
+        try {
+            File testApp = mCtsBuild.getTestApp(String.format("%s.apk", mName));
+            String installCode = mDevice.installPackage(testApp, true);
+
+            if (installCode != null) {
+                CLog.e("Failed to install %s.apk on %s. Reason: %s", mName,
+                    mDevice.getSerialNumber(), installCode);
+                return false;
+            }
+        }
+        catch (FileNotFoundException e) {
+            CLog.e("Package %s.apk not found", mName);
+            return false;
+        }
+        return true;
+    }
+
+    private void runTest(ITestRunListener listener) throws DeviceNotAvailableException {
+        WrappedGTestResultParser resultParser = new WrappedGTestResultParser(mUri, listener);
+        resultParser.setFakePackagePrefix(mUri + ".");
+        try {
+            String command = String.format("am instrument -w %s/.%s", mAppNameSpace, mRunner);
+            mDevice.executeShellCommand(command, resultParser, mMaxTestTimeMs, 0);
+        } catch (DeviceNotAvailableException e) {
+            resultParser.flush();
+            throw e;
+        } catch (RuntimeException e) {
+            resultParser.flush();
+            throw e;
+        }
+    }
+
+    private void uninstallTest() throws DeviceNotAvailableException {
+        mDevice.uninstallPackage(mAppNameSpace);
+    }
+}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/WrappedGTestResultParser.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/WrappedGTestResultParser.java
new file mode 100644
index 0000000..cc3c53a
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/WrappedGTestResultParser.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2012 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.tradefed.testtype;
+
+import com.android.ddmlib.testrunner.ITestRunListener;
+import com.android.tradefed.log.LogUtil.CLog;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.ArrayList;
+
+public class WrappedGTestResultParser extends GeeTestResultParser {
+
+    private boolean mInstrumentationError;
+
+    /**
+     * Creates the WrappedGTestResultParser.
+     *
+     * @param testRunName the test run name to provide to
+     *            {@link ITestRunListener#testRunStarted(String, int)}
+     * @param listeners informed of test results as the tests are executing
+     */
+    public WrappedGTestResultParser(String testRunName, Collection<ITestRunListener> listeners) {
+        super(testRunName, listeners);
+    }
+
+    /**
+     * Creates the WrappedGTestResultParser for a single listener.
+     *
+     * @param testRunName the test run name to provide to
+     *            {@link ITestRunListener#testRunStarted(String, int)}
+     * @param listener informed of test results as the tests are executing
+     */
+    public WrappedGTestResultParser(String testRunName, ITestRunListener listener) {
+        super(testRunName, listener);
+    }
+
+    /**
+     * Strips the instrumentation information and then forwards
+     * the raw gtest output to the {@link GeeTestResultParser}.
+     */
+    @Override
+    public void processNewLines(String[] lines) {
+        if (mInstrumentationError) {
+            return;
+        }
+
+        String[] gtestOutput = parseInstrumentation(lines);
+        super.processNewLines(gtestOutput);
+    }
+
+    /**
+     * Parses raw instrumentation output and returns the
+     * contained gtest output
+     *
+     * @param lines the raw instrumentation output
+     * @return the gtest output
+     */
+    public String[] parseInstrumentation(String[] lines) {
+        List<String> output = new ArrayList<String>();
+        boolean readMultiLine = false;
+        for (String line : lines) {
+
+            if (line.startsWith("INSTRUMENTATION_RESULT: ")) {
+                CLog.e("Instrumentation Error:");
+                mInstrumentationError = true;
+            }
+
+            if (mInstrumentationError) {
+                CLog.e(line);
+                continue;
+            }
+
+            if (line.startsWith("INSTRUMENTATION_STATUS: gtest=")) {
+                output.add(line.replace("INSTRUMENTATION_STATUS: gtest=", ""));
+                readMultiLine = true;
+                continue;
+            }
+
+            if (line.startsWith("INSTRUMENTATION_")) {
+                readMultiLine = false;
+                continue;
+            }
+
+            if (readMultiLine) {
+                output.add(line);
+            }
+        }
+
+        return output.toArray(new String[output.size()]);
+    }
+}
+
diff --git a/tools/tradefed-host/tests/src/com/android/cts/tradefed/UnitTests.java b/tools/tradefed-host/tests/src/com/android/cts/tradefed/UnitTests.java
index 7dfac13..3c36a3d 100644
--- a/tools/tradefed-host/tests/src/com/android/cts/tradefed/UnitTests.java
+++ b/tools/tradefed-host/tests/src/com/android/cts/tradefed/UnitTests.java
@@ -27,6 +27,7 @@
 import com.android.cts.tradefed.testtype.TestPackageDefTest;
 import com.android.cts.tradefed.testtype.TestPackageXmlParserTest;
 import com.android.cts.tradefed.testtype.TestPlanTest;
+import com.android.cts.tradefed.testtype.WrappedGTestResultParserTest;
 
 import junit.framework.Test;
 import junit.framework.TestSuite;
@@ -57,6 +58,7 @@
         addTestSuite(TestPackageDefTest.class);
         addTestSuite(TestPackageXmlParserTest.class);
         addTestSuite(TestPlanTest.class);
+        addTestSuite(WrappedGTestResultParserTest.class);
     }
 
     public static Test suite() {
diff --git a/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/WrappedGTestResultParserTest.java b/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/WrappedGTestResultParserTest.java
new file mode 100644
index 0000000..d386c7a
--- /dev/null
+++ b/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/WrappedGTestResultParserTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2012 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.tradefed.testtype;
+
+import com.android.ddmlib.testrunner.ITestRunListener;
+
+import junit.framework.TestCase;
+
+
+
+/**
+ * Unit tests for {@link WrappedGTestResultParser}.
+ */
+public class WrappedGTestResultParserTest extends TestCase {
+
+    private WrappedGTestResultParser mParser;
+    private final String[] INPUT1 = new String[] {
+        "INSTRUMENTATION_STATUS: gtest=[==========] Running 9 tests from 2 test cases.",
+        "INSTRUMENTATION_STATUS_CODE: 1",
+        "INSTRUMENTATION_STATUS: gtest=[ RUN      ] GLTest.Test1",
+        "INSTRUMENTATION_STATUS: gtest=[       OK ] GLTest.Test1 (10 ms)",
+        "INSTRUMENTATION_STATUS: gtest=/tests/SomeTestFile.cpp:1337: Failure",
+        "Value of: 1 == 0",
+        "  Actual: false",
+        "Expected: true",
+        "INSTRUMENTATION_STATUS: gtest=[  FAILED  ] GLTest.Test2 (1016 ms)",
+        "INSTRUMENTATION_STATUS: gtest=[==========] 2 tests from 1 test cases ran. (17 ms total)",
+        "INSTRUMENTATION_CODE: -1"
+    };
+
+    private final String[] EXPECTED_OUTPUT1 = new String[] {
+        "[==========] Running 9 tests from 2 test cases.",
+        "[ RUN      ] GLTest.Test1",
+        "[       OK ] GLTest.Test1 (10 ms)",
+        "/tests/SomeTestFile.cpp:1337: Failure",
+        "Value of: 1 == 0",
+        "  Actual: false",
+        "Expected: true",
+        "[  FAILED  ] GLTest.Test2 (1016 ms)",
+        "[==========] 2 tests from 1 test cases ran. (17 ms total)",
+    };
+
+    private final String[] INPUT2 = new String[] {
+        "INSTRUMENTATION_STATUS_CODE: 1",
+        "invalid text",
+        "INSTRUMENTATION_STATUS: gtest=[==========] Running 9 tests from 2 test cases.",
+        "INSTRUMENTATION_RESULT: some error",
+        "INSTRUMENTATION_STATUS: gtest=[ RUN      ] GLTest.ExpectTestThatShouldBeSuccessful",
+    };
+
+    private final String[] EXPECTED_OUTPUT2 = new String[] {
+        "[==========] Running 9 tests from 2 test cases.",
+    };
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mParser = new WrappedGTestResultParser("unused", (ITestRunListener)null);
+    }
+
+    private void assertArrayEquals(String[] expected, String[] result) throws Exception {
+        if (expected == null) {
+            assertNull(result);
+            return;
+        }
+
+        assertEquals(expected.length, result.length);
+
+        for (int i = 0; i < expected.length; i++) {
+            assertEquals(expected[i], result[i]);
+        }
+    }
+
+    /**
+     * Test normal case {@link WrappedGTestResultParser#getRawGTestOutput(java.lang.String[])}
+     * with all kinds of valid input lines.
+     */
+    public void testGetRawGTestOutput_valid() throws Exception {
+        assertArrayEquals(EXPECTED_OUTPUT1, mParser.parseInstrumentation(INPUT1));
+    }
+
+    /**
+     * Test normal case {@link WrappedGTestResultParser#getRawGTestOutput(java.lang.String[])}
+     * with a instrumentation error/invalid input.
+     */
+    public void testGetRawGTestOutput_invalid() throws Exception {
+        assertArrayEquals(EXPECTED_OUTPUT2, mParser.parseInstrumentation(INPUT2));
+    }
+}
diff --git a/tools/utils/buildCts.py b/tools/utils/buildCts.py
index 8e23f13..2eda363 100755
--- a/tools/utils/buildCts.py
+++ b/tools/utils/buildCts.py
@@ -54,24 +54,20 @@
 
   def __init__(self, argv):
     """Initialize the CtsBuilder from command line arguments."""
-    if not (len(argv) == 6 or len(argv)==7):
-      print 'Usage: %s <testRoot> <ctsOutputDir> <tempDir> <androidRootDir> <docletPath> [-pts]' % argv[0]
+    if len(argv) != 6:
+      print 'Usage: %s <testRoot> <ctsOutputDir> <tempDir> <androidRootDir> <docletPath>' % argv[0]
       print ''
       print 'testRoot:       Directory under which to search for CTS tests.'
       print 'ctsOutputDir:   Directory in which the CTS repository should be created.'
       print 'tempDir:        Directory to use for storing temporary files.'
       print 'androidRootDir: Root directory of the Android source tree.'
       print 'docletPath:     Class path where the DescriptionGenerator doclet can be found.'
-      print '-pts:           generate plan for PTS.'
       sys.exit(1)
     self.test_root = sys.argv[1]
     self.out_dir = sys.argv[2]
     self.temp_dir = sys.argv[3]
     self.android_root = sys.argv[4]
     self.doclet_path = sys.argv[5]
-    self.isCts = True
-    if len(argv) ==7 and sys.argv[6] == "-pts":
-      self.isCts = False
 
     self.test_repository = os.path.join(self.out_dir, 'repository/testcases')
     self.plan_repository = os.path.join(self.out_dir, 'repository/plans')
@@ -102,36 +98,46 @@
     for description in descriptions:
       doc = tools.XmlFile(description)
       packages.append(doc.GetAttr('TestPackage', 'appPackageName'))
+    # sort the list to give the same sequence based on name
+    packages.sort()
 
-    if not self.isCts: # PTS
-      plan = tools.TestPlan(packages)
-      plan.Include('.*')
-      plan.Exclude(r'android\.tests\.sigtest')
-      self.__WritePlan(plan, 'PTS')
-      return
+    ptsPattern = r'com\.android\.pts\..*'
+    plan = tools.TestPlan(packages)
+    plan.Exclude('.*')
+    plan.Include(ptsPattern)
+    self.__WritePlan(plan, 'PTS')
 
     plan = tools.TestPlan(packages)
     plan.Exclude('android\.performance.*')
     self.__WritePlan(plan, 'CTS')
     self.__WritePlan(plan, 'CTS-TF')
 
+    plan = tools.TestPlan(packages)
+    plan.Exclude(ptsPattern)
+    plan.Exclude('android\.performance.*')
+    self.__WritePlan(plan, 'SDK')
+
     plan.Exclude(r'android\.tests\.sigtest')
     plan.Exclude(r'android\.core.*')
     self.__WritePlan(plan, 'Android')
 
     plan = tools.TestPlan(packages)
+    plan.Exclude(ptsPattern)
     plan.Include(r'android\.core\.tests.*')
     self.__WritePlan(plan, 'Java')
 
     plan = tools.TestPlan(packages)
+    plan.Exclude(ptsPattern)
     plan.Include(r'android\.core\.vm-tests-tf')
     self.__WritePlan(plan, 'VM-TF')
 
     plan = tools.TestPlan(packages)
+    plan.Exclude(ptsPattern)
     plan.Include(r'android\.tests\.sigtest')
     self.__WritePlan(plan, 'Signature')
 
     plan = tools.TestPlan(packages)
+    plan.Exclude(ptsPattern)
     plan.Include(r'android\.tests\.appsecurity')
     self.__WritePlan(plan, 'AppSecurity')
 
@@ -165,9 +171,8 @@
 
 if __name__ == '__main__':
   builder = CtsBuilder(sys.argv)
-  if builder.isCts:
-    result = builder.GenerateTestDescriptions()
-    if result != 0:
-      sys.exit(result)
+  result = builder.GenerateTestDescriptions()
+  if result != 0:
+    sys.exit(result)
   builder.GenerateTestPlans()