Merge \"CTS: Replace deviceSerial with startTime as MetricsStore key.\" into nyc-dev
am: 6a2a6975fe

Change-Id: I93ce754df192136790ef098309dc9826890bbb46
diff --git a/apps/CameraITS/tools/run_all_tests.py b/apps/CameraITS/tools/run_all_tests.py
index 301ea73..52780eb 100644
--- a/apps/CameraITS/tools/run_all_tests.py
+++ b/apps/CameraITS/tools/run_all_tests.py
@@ -51,7 +51,8 @@
 
     # Get all the scene0 and scene1 tests, which can be run using the same
     # physical setup.
-    scenes = ["scene0", "scene1", "scene2", "scene3", "scene4", "scene5"]
+    scenes = ["scene0", "scene1", "scene2", "scene3", "scene4", "scene5",
+              "sensor_fusion"]
 
     scene_req = {
         "scene0" : None,
diff --git a/apps/CtsVerifier/Android.mk b/apps/CtsVerifier/Android.mk
index a52ea7a..304c982 100644
--- a/apps/CtsVerifier/Android.mk
+++ b/apps/CtsVerifier/Android.mk
@@ -42,7 +42,9 @@
 
 LOCAL_PACKAGE_NAME := CtsVerifier
 
-LOCAL_JNI_SHARED_LIBRARIES := libctsverifier_jni libaudioloopback_jni
+LOCAL_JNI_SHARED_LIBRARIES := libctsverifier_jni \
+		libaudioloopback_jni \
+		libnativehelper_compat_libc++
 
 LOCAL_PROGUARD_FLAG_FILES := proguard.flags
 
diff --git a/apps/CtsVerifier/jni/verifier/Android.mk b/apps/CtsVerifier/jni/verifier/Android.mk
index 4840e62..2350085 100644
--- a/apps/CtsVerifier/jni/verifier/Android.mk
+++ b/apps/CtsVerifier/jni/verifier/Android.mk
@@ -21,8 +21,6 @@
 
 LOCAL_MODULE_TAGS := optional
 
-
-
 LOCAL_SRC_FILES := \
 		CtsVerifierJniOnLoad.cpp \
 		com_android_cts_verifier_camera_StatsImage.cpp \
@@ -30,6 +28,10 @@
 
 LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
 
-LOCAL_SHARED_LIBRARIES := liblog
+LOCAL_CXX_STL := libc++_static
+
+LOCAL_SHARED_LIBRARIES := liblog \
+		libnativehelper_compat_libc++
 
 include $(BUILD_SHARED_LIBRARY)
+
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 52da8c4..ef77c84 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -2217,8 +2217,7 @@
     <string name="device_owner_disallow_config_wifi_info">
         Please press the Set restriction button to set the user restriction.
         Then press Go to open the WiFi page in Settings.
-        Confirm that:\n
-        \n
+        Confirm that:\n\n
         - You cannot view WiFi networks in range.\n
         - Trying to edit, add or remove any existing WiFi configs triggers a support message.\n
         \n
@@ -2229,13 +2228,24 @@
         Device should have a sim card to perform this test.
         Please press the Set restriction button to set the user restriction.
         Then press Go to open the Cellular network page in Settings.
-        Confirm that:\n
-        \n
+        Confirm that:\n\n
         - Data roaming is disabled.\n
-        - Enabling data roaming is not possible and triggers a support message.\n
-        \n
+        - Enabling data roaming is not possible and triggers a support message.\n\n
         Use the Back button to return to this page.
     </string>
+    <string name="device_owner_disallow_factory_reset">Disallow factory reset</string>
+    <string name="device_owner_disallow_factory_reset_info">
+        Please press the Set button to set the user restriction.\n
+        1. Go to the factory reset settings. It is often located in \"Backup &amp; reset\" settings.\n
+        Confirm that:\n
+           - Factory data reset is disabled.\n
+           - Pressing factory data reset is not possible and triggers a support message.\n\n
+        2. Go to OEM unlocking settings, if this device has this Settings option. It is often located under \"Developer options\".\n
+        Confirm that:\n
+           - Oem Unlocking is disabled.\n
+           - Enabling Oem unlocking is not possible and triggers a support message.\n\n
+        Return back to this page.
+    </string>
     <string name="device_owner_user_restriction_set">Set restriction</string>
     <string name="device_owner_settings_go">Go</string>
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java
index 724f03d..024854c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java
@@ -68,6 +68,7 @@
     private static final String DISALLOW_USB_FILE_TRANSFER_ID = "DISALLOW_USB_FILE_TRANSFER";
     private static final String SET_USER_ICON_TEST_ID = "SET_USER_ICON";
     private static final String DISALLOW_DATA_ROAMING_ID = "DISALLOW_DATA_ROAMING";
+    private static final String DISALLOW_FACTORY_RESET_ID = "DISALLOW_FACTORY_RESET";
     private static final String POLICY_TRANSPARENCY_TEST_ID = "POLICY_TRANSPARENCY";
     private static final String REMOVE_DEVICE_OWNER_TEST_ID = "REMOVE_DEVICE_OWNER";
 
@@ -201,6 +202,16 @@
                                     new Intent(Settings.ACTION_DATA_ROAMING_SETTINGS))}));
         }
 
+        // DISALLOW_FACTORY_RESET
+        adapter.add(createInteractiveTestItem(this, DISALLOW_FACTORY_RESET_ID,
+                R.string.device_owner_disallow_factory_reset,
+                R.string.device_owner_disallow_factory_reset_info,
+                new ButtonInfo[] {
+                        new ButtonInfo(
+                                R.string.device_owner_user_restriction_set,
+                                createSetUserRestrictionIntent(
+                                        UserManager.DISALLOW_FACTORY_RESET))}));
+
         // DISALLOW_CONFIG_BLUETOOTH
         if (packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
             adapter.add(createInteractiveTestItem(this, DISALLOW_CONFIG_BT_ID,
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
index 554f53b..b467aed 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
@@ -16,6 +16,10 @@
 
 package com.android.cts.devicepolicy;
 
+import com.android.tradefed.log.LogUtil.CLog;
+
+import java.lang.AssertionError;
+
 /**
  * Set of tests for managed profile owner use cases that also apply to device owners.
  * Tests that should be run identically in both cases are added in DeviceAndProfileOwnerTest.
@@ -69,13 +73,29 @@
         if (!mHasFeature) {
             return;
         }
-        executeDeviceTestMethod(".ScreenCaptureDisabledTest", "testSetScreenCaptureDisabled_true");
-        // start the ScreenCaptureDisabledActivity in the parent
-        installAppAsUser(DEVICE_ADMIN_APK, mParentUserId);
-        String command = "am start -W --user " + mParentUserId + " " + DEVICE_ADMIN_PKG + "/"
-                + DEVICE_ADMIN_PKG + ".ScreenCaptureDisabledActivity";
-        getDevice().executeShellCommand(command);
-        executeDeviceTestMethod(".ScreenCaptureDisabledTest", "testScreenCapturePossible");
+        runDumpsysWindow();
+        try {
+            executeDeviceTestMethod(".ScreenCaptureDisabledTest",
+                    "testSetScreenCaptureDisabled_true");
+            // start the ScreenCaptureDisabledActivity in the parent
+            installAppAsUser(DEVICE_ADMIN_APK, mParentUserId);
+            String command = "am start -W --user " + mParentUserId + " " + DEVICE_ADMIN_PKG + "/"
+                    + DEVICE_ADMIN_PKG + ".ScreenCaptureDisabledActivity";
+            getDevice().executeShellCommand(command);
+            executeDeviceTestMethod(".ScreenCaptureDisabledTest", "testScreenCapturePossible");
+        } catch (AssertionError e) {
+            runDumpsysWindow();
+            CLog.e("testScreenCaptureDisabled_allowedPrimaryUser failed", e);
+            fail("testScreenCaptureDisabled_allowedPrimaryUser failed");
+        }
+    }
+
+    // TODO: Remove this after investigation in b/28995242 is done
+    private void runDumpsysWindow() throws Exception {
+        String command = "dumpsys window displays";
+        CLog.d("Output for command " + command + ": " + getDevice().executeShellCommand(command));
+        command = "dumpsys window policy";
+        CLog.d("Output for command " + command + ": " + getDevice().executeShellCommand(command));
     }
 
     @Override
diff --git a/hostsidetests/os/src/android/os/cts/OsHostTests.java b/hostsidetests/os/src/android/os/cts/OsHostTests.java
index c7b3cc7..200ef40 100644
--- a/hostsidetests/os/src/android/os/cts/OsHostTests.java
+++ b/hostsidetests/os/src/android/os/cts/OsHostTests.java
@@ -125,8 +125,7 @@
                             Arrays.asList(hostgroup.split(" ")));
                     assertEquals(2, allHosts.size());
                     assertTrue("AllHosts Contains: " + allHosts, allHosts.contains(HOST_EXPLICIT));
-                    // Disable wildcard test until next API bump
-                    // assertTrue("AllHosts Contains: " + allHosts, allHosts.contains(HOST_WILDCARD));
+                    assertTrue("AllHosts Contains: " + allHosts, allHosts.contains(HOST_WILDCARD));
                     foundVerifierOutput = true;
                     break;
                 }
diff --git a/hostsidetests/services/activitymanager/app/AndroidManifest.xml b/hostsidetests/services/activitymanager/app/AndroidManifest.xml
index 5065ed6..3523478 100755
--- a/hostsidetests/services/activitymanager/app/AndroidManifest.xml
+++ b/hostsidetests/services/activitymanager/app/AndroidManifest.xml
@@ -46,7 +46,7 @@
         />
         <activity android:name=".NoRelaunchActivity"
                 android:resizeableActivity="true"
-                android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+                android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|fontScale"
                 android:exported="true"
                 android:taskAffinity="nobody.but.NoRelaunchActivity"
         />
diff --git a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerConfigChangeTests.java b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerConfigChangeTests.java
new file mode 100644
index 0000000..e5a121d
--- /dev/null
+++ b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerConfigChangeTests.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2016 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.server.cts;
+
+public class ActivityManagerConfigChangeTests extends ActivityManagerTestBase {
+
+    private static final String TEST_ACTIVITY_NAME = "TestActivity";
+    private static final String NO_RELAUNCH_ACTIVITY_NAME = "NoRelaunchActivity";
+
+    public void testRotation90Relaunch() throws Exception{
+        // Should relaunch on every rotation and receive no onConfigurationChanged()
+        testRotation(TEST_ACTIVITY_NAME, 1, 1, 0);
+    }
+
+    public void testRotation90NoRelaunch() throws Exception {
+        // Should receive onConfigurationChanged() on every rotation and no relaunch
+        testRotation(NO_RELAUNCH_ACTIVITY_NAME, 1, 0, 1);
+    }
+
+    public void testRotation180Relaunch() throws Exception {
+        // Should receive nothing
+        testRotation(TEST_ACTIVITY_NAME, 2, 0, 0);
+    }
+
+    public void testRotation180NoRelaunch() throws Exception {
+        // Should receive nothing
+        testRotation(NO_RELAUNCH_ACTIVITY_NAME, 2, 0, 0);
+    }
+
+    public void testChangeFontScaleRelaunch() throws Exception {
+        // Should relaunch and receive no onConfigurationChanged()
+        testChangeFontScale(TEST_ACTIVITY_NAME, true);
+    }
+
+    public void testChangeFontScaleNoRelaunch() throws Exception {
+        // Should receive onConfigurationChanged() and no relaunch
+        testChangeFontScale(NO_RELAUNCH_ACTIVITY_NAME, false);
+    }
+
+    private void testRotation(
+            String activityName, int rotationStep, int numRelaunch, int numConfigChange)
+                    throws Exception {
+        executeShellCommand(getAmStartCmd(activityName));
+
+        final String[] waitForActivitiesVisible = new String[] {activityName};
+        mAmWmState.computeState(mDevice, waitForActivitiesVisible);
+        mAmWmState.assertContainsStack(
+                "Must contain fullscreen stack.", FULLSCREEN_WORKSPACE_STACK_ID);
+
+        setDeviceRotation(4 - rotationStep);
+        mAmWmState.computeState(mDevice, waitForActivitiesVisible);
+
+        for (int rotation = 0; rotation < 4; rotation += rotationStep) {
+            clearLogcat();
+            setDeviceRotation(rotation);
+            mAmWmState.computeState(mDevice, waitForActivitiesVisible);
+            assertRelaunchOrConfigChanged(activityName, numRelaunch, numConfigChange);
+        }
+    }
+
+    private void testChangeFontScale(
+            String activityName, boolean relaunch) throws Exception {
+        executeShellCommand(getAmStartCmd(activityName));
+        final String[] waitForActivitiesVisible = new String[] {activityName};
+        mAmWmState.computeState(mDevice, waitForActivitiesVisible);
+        mAmWmState.assertContainsStack(
+                "Must contain fullscreen stack.", FULLSCREEN_WORKSPACE_STACK_ID);
+
+        setFontScale(1.0f);
+        mAmWmState.computeState(mDevice, waitForActivitiesVisible);
+
+        for (float fontScale = 0.85f; fontScale <= 1.3f; fontScale += 0.15f) {
+            clearLogcat();
+            setFontScale(fontScale);
+            mAmWmState.computeState(mDevice, waitForActivitiesVisible);
+            assertRelaunchOrConfigChanged(activityName, relaunch ? 1 : 0, relaunch ? 0 : 1);
+        }
+    }
+}
diff --git a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerTestBase.java b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerTestBase.java
index 3a21fda..04b0653 100644
--- a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerTestBase.java
+++ b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerTestBase.java
@@ -99,6 +99,7 @@
 
     private int mInitialAccelerometerRotation;
     private int mUserRotation;
+    private float mFontScale;
 
     @Override
     protected void setUp() throws Exception {
@@ -114,6 +115,7 @@
         // Store rotation settings.
         mInitialAccelerometerRotation = getAccelerometerRotation();
         mUserRotation = getUserRotation();
+        mFontScale = getFontScale();
     }
 
     @Override
@@ -125,6 +127,7 @@
             // Restore rotation settings to the state they were before test.
             setAccelerometerRotation(mInitialAccelerometerRotation);
             setUserRotation(mUserRotation);
+            setFontScale(mFontScale);
             // Remove special stacks.
             executeShellCommand(AM_REMOVE_STACK + PINNED_STACK_ID);
             executeShellCommand(AM_REMOVE_STACK + DOCKED_STACK_ID);
@@ -319,6 +322,22 @@
         }
     }
 
+    protected void setFontScale(float fontScale) throws DeviceNotAvailableException {
+        if (fontScale == 0.0f) {
+            runCommandAndPrintOutput(
+                    "settings delete system font_scale");
+        } else {
+            runCommandAndPrintOutput(
+                    "settings put system font_scale " + fontScale);
+        }
+    }
+
+    protected float getFontScale() throws DeviceNotAvailableException {
+        final String fontScale =
+                runCommandAndPrintOutput("settings get system font_scale").trim();
+        return Float.parseFloat(fontScale);
+    }
+
     protected String runCommandAndPrintOutput(String command) throws DeviceNotAvailableException {
         final String output = executeShellCommand(command);
         log(output);
@@ -359,6 +378,23 @@
         }
     }
 
+    protected void assertRelaunchOrConfigChanged(
+            String activityName, int numRelaunch, int numConfigChange)
+            throws DeviceNotAvailableException {
+        final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(activityName);
+
+        if (lifecycleCounts.mDestroyCount != numRelaunch) {
+            fail(activityName + " has been destroyed " + lifecycleCounts.mDestroyCount
+                    + " time(s), expecting " + numRelaunch);
+        } else if (lifecycleCounts.mCreateCount != numRelaunch) {
+            fail(activityName + " has been (re)created " + lifecycleCounts.mCreateCount
+                    + " time(s), expecting " + numRelaunch);
+        } else if (lifecycleCounts.mConfigurationChangedCount != numConfigChange) {
+            fail(activityName + " has received " + lifecycleCounts.mConfigurationChangedCount
+                    + " onConfigurationChanged() calls, expecting " + numConfigChange);
+        }
+    }
+
     private String[] getDeviceLogsForActivity(String activityName)
             throws DeviceNotAvailableException {
         return mDevice.executeAdbCommand("logcat", "-v", "brief", "-d", activityName + ":I", "*:S")
diff --git a/tests/app/src/android/app/cts/ActivityManagerMemoryClassTest.java b/tests/app/src/android/app/cts/ActivityManagerMemoryClassTest.java
index cb331d1..2b84be6 100644
--- a/tests/app/src/android/app/cts/ActivityManagerMemoryClassTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerMemoryClassTest.java
@@ -57,8 +57,11 @@
             expectedMemorySizeForWatch.put(DisplayMetrics.DENSITY_MEDIUM, 32);
             expectedMemorySizeForWatch.put(DisplayMetrics.DENSITY_TV, 32);
             expectedMemorySizeForWatch.put(DisplayMetrics.DENSITY_HIGH, 36);
+            expectedMemorySizeForWatch.put(DisplayMetrics.DENSITY_260, 36);
             expectedMemorySizeForWatch.put(DisplayMetrics.DENSITY_280, 36);
+            expectedMemorySizeForWatch.put(DisplayMetrics.DENSITY_300, 36);
             expectedMemorySizeForWatch.put(DisplayMetrics.DENSITY_XHIGH, 48);
+            expectedMemorySizeForWatch.put(DisplayMetrics.DENSITY_340, 48);
             expectedMemorySizeForWatch.put(DisplayMetrics.DENSITY_360, 48);
             expectedMemorySizeForWatch.put(DisplayMetrics.DENSITY_400, 56);
             expectedMemorySizeForWatch.put(DisplayMetrics.DENSITY_420, 64);
@@ -72,8 +75,11 @@
             expectedMemorySizeForSmallNormalScreen.put(DisplayMetrics.DENSITY_MEDIUM, 32);
             expectedMemorySizeForSmallNormalScreen.put(DisplayMetrics.DENSITY_TV, 48);
             expectedMemorySizeForSmallNormalScreen.put(DisplayMetrics.DENSITY_HIGH, 48);
+            expectedMemorySizeForSmallNormalScreen.put(DisplayMetrics.DENSITY_260, 48);
             expectedMemorySizeForSmallNormalScreen.put(DisplayMetrics.DENSITY_280, 48);
+            expectedMemorySizeForSmallNormalScreen.put(DisplayMetrics.DENSITY_300, 48);
             expectedMemorySizeForSmallNormalScreen.put(DisplayMetrics.DENSITY_XHIGH, 80);
+            expectedMemorySizeForSmallNormalScreen.put(DisplayMetrics.DENSITY_340, 80);
             expectedMemorySizeForSmallNormalScreen.put(DisplayMetrics.DENSITY_360, 80);
             expectedMemorySizeForSmallNormalScreen.put(DisplayMetrics.DENSITY_400, 96);
             expectedMemorySizeForSmallNormalScreen.put(DisplayMetrics.DENSITY_420, 112);
@@ -87,8 +93,11 @@
             expectedMemorySizeForLargeScreen.put(DisplayMetrics.DENSITY_MEDIUM, 64);
             expectedMemorySizeForLargeScreen.put(DisplayMetrics.DENSITY_TV, 80);
             expectedMemorySizeForLargeScreen.put(DisplayMetrics.DENSITY_HIGH, 80);
+            expectedMemorySizeForLargeScreen.put(DisplayMetrics.DENSITY_260, 96);
             expectedMemorySizeForLargeScreen.put(DisplayMetrics.DENSITY_280, 96);
+            expectedMemorySizeForLargeScreen.put(DisplayMetrics.DENSITY_300, 96);
             expectedMemorySizeForLargeScreen.put(DisplayMetrics.DENSITY_XHIGH, 128);
+            expectedMemorySizeForLargeScreen.put(DisplayMetrics.DENSITY_340, 160);
             expectedMemorySizeForLargeScreen.put(DisplayMetrics.DENSITY_360, 160);
             expectedMemorySizeForLargeScreen.put(DisplayMetrics.DENSITY_400, 192);
             expectedMemorySizeForLargeScreen.put(DisplayMetrics.DENSITY_420, 228);
@@ -102,8 +111,11 @@
             expectedMemorySizeForXLargeScreen.put(DisplayMetrics.DENSITY_MEDIUM, 80);
             expectedMemorySizeForXLargeScreen.put(DisplayMetrics.DENSITY_TV, 96);
             expectedMemorySizeForXLargeScreen.put(DisplayMetrics.DENSITY_HIGH, 96);
+            expectedMemorySizeForXLargeScreen.put(DisplayMetrics.DENSITY_260, 144);
             expectedMemorySizeForXLargeScreen.put(DisplayMetrics.DENSITY_280, 144);
+            expectedMemorySizeForXLargeScreen.put(DisplayMetrics.DENSITY_300, 144);
             expectedMemorySizeForXLargeScreen.put(DisplayMetrics.DENSITY_XHIGH, 192);
+            expectedMemorySizeForXLargeScreen.put(DisplayMetrics.DENSITY_340, 192);
             expectedMemorySizeForXLargeScreen.put(DisplayMetrics.DENSITY_360, 240);
             expectedMemorySizeForXLargeScreen.put(DisplayMetrics.DENSITY_400, 288);
             expectedMemorySizeForXLargeScreen.put(DisplayMetrics.DENSITY_420, 336);
diff --git a/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java b/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
index a2dddb5..03ab3cf 100644
--- a/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
+++ b/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
@@ -242,4 +242,8 @@
     public void testViewDownloads() {
         assertCanBeHandled(new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS));
     }
+
+    public void testDeletionHelper() {
+        assertCanBeHandled(new Intent(Settings.ACTION_DELETION_HELPER_SETTINGS));
+    }
 }
diff --git a/tests/tests/dpi/src/android/dpi/cts/ConfigurationTest.java b/tests/tests/dpi/src/android/dpi/cts/ConfigurationTest.java
index fc38fdd..78ce89a 100644
--- a/tests/tests/dpi/src/android/dpi/cts/ConfigurationTest.java
+++ b/tests/tests/dpi/src/android/dpi/cts/ConfigurationTest.java
@@ -59,8 +59,11 @@
         allowedDensities.add(DisplayMetrics.DENSITY_MEDIUM);
         allowedDensities.add(DisplayMetrics.DENSITY_TV);
         allowedDensities.add(DisplayMetrics.DENSITY_HIGH);
+        allowedDensities.add(DisplayMetrics.DENSITY_260);
         allowedDensities.add(DisplayMetrics.DENSITY_280);
+        allowedDensities.add(DisplayMetrics.DENSITY_300);
         allowedDensities.add(DisplayMetrics.DENSITY_XHIGH);
+        allowedDensities.add(DisplayMetrics.DENSITY_340);
         allowedDensities.add(DisplayMetrics.DENSITY_360);
         allowedDensities.add(DisplayMetrics.DENSITY_400);
         allowedDensities.add(DisplayMetrics.DENSITY_420);
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedVectorDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedVectorDrawableTest.java
index 487eb3b..d678e1e 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedVectorDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedVectorDrawableTest.java
@@ -147,9 +147,11 @@
         mActivity.runOnUiThread(new Runnable() {
             @Override
             public void run() {
+                mActivity.setContentView(mLayoutId);
+                ImageView imageView = (ImageView) mActivity.findViewById(mImageViewId);
+                imageView.setImageDrawable(d1);
                 d1.start();
                 d1.stop();
-
             }
         });
         getInstrumentation().waitForIdleSync();
diff --git a/tests/tests/jni/libjnitest/android_jni_cts_LinkerNamespacesTest.cpp b/tests/tests/jni/libjnitest/android_jni_cts_LinkerNamespacesTest.cpp
index 8c74158..1407cc9 100644
--- a/tests/tests/jni/libjnitest/android_jni_cts_LinkerNamespacesTest.cpp
+++ b/tests/tests/jni/libjnitest/android_jni_cts_LinkerNamespacesTest.cpp
@@ -126,7 +126,7 @@
       *error_msg = "The library \"" + path + "\" should be accessible but isn't: " + dlerror();
       return false;
     }
-  } else if (handle != nullptr) {
+  } else if (handle.get() != nullptr) {
     *error_msg = "The library \"" + path + "\" should not be accessible";
     return false;
   } else { // (handle == nullptr && !shouldBeAccessible(path))
@@ -178,17 +178,43 @@
   return true;
 }
 
-static void jobject_array_to_set(JNIEnv* env,
+static bool jobject_array_to_set(JNIEnv* env,
                                  jobjectArray java_libraries_array,
-                                 std::unordered_set<std::string>* libraries) {
+                                 std::unordered_set<std::string>* libraries,
+                                 std::string* error_msg) {
   size_t size = env->GetArrayLength(java_libraries_array);
   for (size_t i = 0; i<size; ++i) {
     ScopedLocalRef<jstring> java_soname(
         env, (jstring) env->GetObjectArrayElement(java_libraries_array, i));
+    std::string soname(ScopedUtfChars(env, java_soname.get()).c_str());
 
-    ScopedUtfChars soname(env, java_soname.get());
-    libraries->insert(soname.c_str());
+    // Check to see if the string ends in " 32" or " 64" to indicate the
+    // library is only public for one bitness.
+    size_t space_pos = soname.rfind(' ');
+    if (space_pos != std::string::npos) {
+      std::string type = soname.substr(space_pos + 1);
+      if (type != "32" && type != "64") {
+        *error_msg = "public library line is malformed: " + soname;
+        return false;
+      }
+#if defined(__LP64__)
+      if (type == "32") {
+        // Skip this, it's a 32 bit only public library.
+        continue;
+      }
+#else
+      if (type == "64") {
+        // Skip this, it's a 64 bit only public library.
+        continue;
+      }
+#endif
+      soname.resize(space_pos);
+    }
+
+    libraries->insert(soname);
   }
+
+  return true;
 }
 
 extern "C" JNIEXPORT jstring JNICALL
@@ -202,8 +228,12 @@
   std::unordered_set<std::string> vendor_public_libraries;
   std::unordered_set<std::string> system_public_libraries;
   std::unordered_set<std::string> empty_set;
-  jobject_array_to_set(env, java_vendor_public_libraries, &vendor_public_libraries);
-  jobject_array_to_set(env, java_system_public_libraries, &system_public_libraries);
+  if (!jobject_array_to_set(env, java_vendor_public_libraries, &vendor_public_libraries, &error)) {
+    return env->NewStringUTF(("Vendor " + error).c_str());
+  }
+  if (!jobject_array_to_set(env, java_system_public_libraries, &system_public_libraries, &error)) {
+    return env->NewStringUTF(("System " + error).c_str());
+  }
 
   if (!check_libs(kSystemLibraryPath, system_public_libraries, kSystemLibraries, &error) ||
       !check_libs(kVendorLibraryPath, vendor_public_libraries, empty_set, &error)) {
diff --git a/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java b/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java
index e596a91..ff84655 100644
--- a/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java
@@ -93,6 +93,7 @@
                             mConnection = (MockConnection) connection;
                             // Modify the connection object created with local values.
                             connection.setConnectionCapabilities(CONNECTION_CAPABILITIES);
+                            connection.setConnectionProperties(CONNECTION_PROPERTIES);
                             connection.setCallerDisplayName(
                                     CALLER_DISPLAY_NAME,
                                     CALLER_DISPLAY_NAME_PRESENTATION);
@@ -273,8 +274,7 @@
 
         assertThat(mCall.getDetails().getCallProperties(), is(Integer.class));
 
-        // No public call properties at the moment, so ensure we have 0 as a return.
-        assertEquals(0, mCall.getDetails().getCallProperties());
+        assertEquals(CALL_PROPERTIES, mCall.getDetails().getCallProperties());
     }
 
     /**
@@ -415,6 +415,214 @@
     }
 
     /**
+     * Tests that {@link Connection} extras changes made via {@link Connection#putExtras(Bundle)}
+     * are propagated to the {@link Call} via
+     * {@link android.telecom.Call.Callback#onDetailsChanged(Call, Call.Details)}.
+     */
+    public void testConnectionPutExtras() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+
+        Bundle testBundle = new Bundle();
+        testBundle.putString(TEST_EXTRA_KEY, TEST_SUBJECT);
+        testBundle.putInt(TEST_EXTRA_KEY2, TEST_EXTRA_VALUE);
+        mConnection.putExtras(testBundle);
+        // Wait for the 2nd invocation; setExtras is called in the setup method.
+        mOnExtrasChangedCounter.waitForCount(2, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+
+        Bundle extras = mCall.getDetails().getExtras();
+        assertEquals(2, extras.size());
+        assertTrue(extras.containsKey(TEST_EXTRA_KEY));
+        assertEquals(TEST_SUBJECT, extras.getString(TEST_EXTRA_KEY));
+        assertTrue(extras.containsKey(TEST_EXTRA_KEY2));
+        assertEquals(TEST_EXTRA_VALUE, extras.getInt(TEST_EXTRA_KEY2));
+    }
+
+    /**
+     * Tests that {@link Connection} extras changes made via {@link Connection#removeExtras(List)}
+     * are propagated to the {@link Call} via
+     * {@link android.telecom.Call.Callback#onDetailsChanged(Call, Call.Details)}.
+     */
+    public void testConnectionRemoveExtras() {
+        testConnectionPutExtras();
+
+        mConnection.removeExtras(Arrays.asList(TEST_EXTRA_KEY));
+        verifyRemoveConnectionExtras();
+
+    }
+
+    /**
+     * Tests that {@link Connection} extras changes made via {@link Connection#removeExtras(List)}
+     * are propagated to the {@link Call} via
+     * {@link android.telecom.Call.Callback#onDetailsChanged(Call, Call.Details)}.
+     */
+    public void testConnectionRemoveExtras2() {
+        testConnectionPutExtras();
+
+        mConnection.removeExtras(TEST_EXTRA_KEY);
+        // testConnectionPutExtra will have waited for the 2nd invocation, so wait for the 3rd here.
+        verifyRemoveConnectionExtras();
+    }
+
+    private void verifyRemoveConnectionExtras() {
+        // testConnectionPutExtra will have waited for the 2nd invocation, so wait for the 3rd here.
+        mOnExtrasChangedCounter.waitForCount(3, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+
+        Bundle extras = mCall.getDetails().getExtras();
+        assertEquals(1, extras.size());
+        assertFalse(extras.containsKey(TEST_EXTRA_KEY));
+        assertTrue(extras.containsKey(TEST_EXTRA_KEY2));
+        assertEquals(TEST_EXTRA_VALUE, extras.getInt(TEST_EXTRA_KEY2));
+    }
+
+    /**
+     * Tests that {@link Call} extras changes made via {@link Call#putExtras(Bundle)} are propagated
+     * to {@link Connection#onExtrasChanged(Bundle)}.
+     */
+    public void testCallPutExtras() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+
+        Bundle testBundle = new Bundle();
+        testBundle.putString(TEST_EXTRA_KEY, TEST_SUBJECT);
+        final InvokeCounter counter = mConnection.getInvokeCounter(
+                MockConnection.ON_EXTRAS_CHANGED);
+        mCall.putExtras(testBundle);
+        counter.waitForCount(1, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+        Bundle extras = mConnection.getExtras();
+
+        assertNotNull(extras);
+        assertTrue(extras.containsKey(TEST_EXTRA_KEY));
+        assertEquals(TEST_SUBJECT, extras.getString(TEST_EXTRA_KEY));
+    }
+
+    /**
+     * Tests that {@link Call} extra operations using {@link Call#removeExtras(List)} are propagated
+     * to the {@link Connection} via {@link Connection#onExtrasChanged(Bundle)}.
+     *
+     * This test specifically tests addition and removal of extras values.
+     */
+    public void testCallRemoveExtras() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+
+        final InvokeCounter counter = setupCallExtras();
+        Bundle extras;
+
+        mCall.removeExtras(Arrays.asList(TEST_EXTRA_KEY));
+        counter.waitForCount(2, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+        extras = mConnection.getExtras();
+        assertNotNull(extras);
+        assertFalse(extras.containsKey(TEST_EXTRA_KEY));
+        assertTrue(extras.containsKey(TEST_EXTRA_KEY2));
+        assertEquals(TEST_EXTRA_VALUE, extras.getInt(TEST_EXTRA_KEY2));
+        assertTrue(extras.containsKey(TEST_EXTRA_KEY3));
+        assertEquals(TEST_SUBJECT, extras.getString(TEST_EXTRA_KEY3));
+
+        mCall.removeExtras(Arrays.asList(TEST_EXTRA_KEY2, TEST_EXTRA_KEY3));
+        counter.waitForCount(3, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+        extras = mConnection.getExtras();
+        assertTrue(extras.isEmpty());
+    }
+
+    /**
+     * Tests that {@link Call} extra operations using {@link Call#removeExtras(String[])} are
+     * propagated to the {@link Connection} via {@link Connection#onExtrasChanged(Bundle)}.
+     *
+     * This test specifically tests addition and removal of extras values.
+     */
+    public void testCallRemoveExtras2() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+
+        final InvokeCounter counter = setupCallExtras();
+        Bundle extras;
+
+        mCall.removeExtras(TEST_EXTRA_KEY);
+        counter.waitForCount(2, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+        extras = mConnection.getExtras();
+        assertNotNull(extras);
+        assertFalse(extras.containsKey(TEST_EXTRA_KEY));
+        assertTrue(extras.containsKey(TEST_EXTRA_KEY2));
+        assertEquals(TEST_EXTRA_VALUE, extras.getInt(TEST_EXTRA_KEY2));
+        assertTrue(extras.containsKey(TEST_EXTRA_KEY3));
+        assertEquals(TEST_SUBJECT, extras.getString(TEST_EXTRA_KEY3));
+    }
+
+    private InvokeCounter setupCallExtras() {
+        Bundle testBundle = new Bundle();
+        testBundle.putString(TEST_EXTRA_KEY, TEST_SUBJECT);
+        testBundle.putInt(TEST_EXTRA_KEY2, TEST_EXTRA_VALUE);
+        testBundle.putString(TEST_EXTRA_KEY3, TEST_SUBJECT);
+        final InvokeCounter counter = mConnection.getInvokeCounter(
+                MockConnection.ON_EXTRAS_CHANGED);
+        mCall.putExtras(testBundle);
+        counter.waitForCount(1, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+        Bundle extras = mConnection.getExtras();
+
+        assertNotNull(extras);
+        assertTrue(extras.containsKey(TEST_EXTRA_KEY));
+        assertEquals(TEST_SUBJECT, extras.getString(TEST_EXTRA_KEY));
+        assertTrue(extras.containsKey(TEST_EXTRA_KEY2));
+        assertEquals(TEST_EXTRA_VALUE, extras.getInt(TEST_EXTRA_KEY2));
+        assertTrue(extras.containsKey(TEST_EXTRA_KEY3));
+        assertEquals(TEST_SUBJECT, extras.getString(TEST_EXTRA_KEY3));
+        return counter;
+    }
+
+    /**
+     * Tests that {@link Connection} events are propagated from
+     * {@link Connection#sendConnectionEvent(String, Bundle)} to
+     * {@link android.telecom.Call.Callback#onConnectionEvent(Call, String, Bundle)}.
+     */
+    public void testConnectionEvent() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+
+        Bundle testBundle = new Bundle();
+        testBundle.putString(TEST_EXTRA_KEY, TEST_SUBJECT);
+
+        mConnection.sendConnectionEvent(Connection.EVENT_CALL_PULL_FAILED, testBundle);
+        mOnConnectionEventCounter.waitForCount(1, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+        String event = (String) (mOnConnectionEventCounter.getArgs(0)[1]);
+        Bundle extras = (Bundle) (mOnConnectionEventCounter.getArgs(0)[2]);
+
+        assertEquals(Connection.EVENT_CALL_PULL_FAILED, event);
+        assertNotNull(extras);
+        assertTrue(extras.containsKey(TEST_EXTRA_KEY));
+        assertEquals(TEST_SUBJECT, extras.getString(TEST_EXTRA_KEY));
+    }
+
+    /**
+     * Tests that {@link Call} events are propagated from {@link Call#sendCallEvent(String, Bundle)}
+     * to {@link Connection#onCallEvent(String, Bundle)}.
+     */
+    public void testCallEvent() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+
+        Bundle testBundle = new Bundle();
+        testBundle.putString(TEST_EXTRA_KEY, TEST_SUBJECT);
+        final InvokeCounter counter = mConnection.getInvokeCounter(MockConnection.ON_CALL_EVENT);
+        mCall.sendCallEvent(TEST_EVENT, testBundle);
+        counter.waitForCount(1, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+
+        String event = (String) (counter.getArgs(0)[0]);
+        Bundle extras = (Bundle) (counter.getArgs(0)[1]);
+
+        assertEquals(TEST_EVENT, event);
+        assertNotNull(extras);
+        assertTrue(extras.containsKey(TEST_EXTRA_KEY));
+        assertEquals(TEST_SUBJECT, extras.getString(TEST_EXTRA_KEY));
+    }
+
+    /**
      * Asserts that a call's extras contain a specified key.
      *
      * @param call The call.
diff --git a/tests/tests/telecom/src/android/telecom/cts/CallTest.java b/tests/tests/telecom/src/android/telecom/cts/CallTest.java
index d63a9e8..b892ede 100644
--- a/tests/tests/telecom/src/android/telecom/cts/CallTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/CallTest.java
@@ -32,6 +32,8 @@
                 | CAPABILITY_DISCONNECT_FROM_CONFERENCE | CAPABILITY_MUTE,
                 CAPABILITY_MUTE));
         assertTrue(Call.Details.can(CAPABILITY_CAN_PAUSE_VIDEO, CAPABILITY_CAN_PAUSE_VIDEO));
+        assertTrue(Call.Details.can(CAPABILITY_CAN_PULL_CALL, CAPABILITY_CAN_PULL_CALL));
+
         assertFalse(Call.Details.can(CAPABILITY_MUTE, CAPABILITY_HOLD));
         assertFalse(Call.Details.can(CAPABILITY_MERGE_CONFERENCE
                 | CAPABILITY_DISCONNECT_FROM_CONFERENCE | CAPABILITY_MUTE,
@@ -78,6 +80,8 @@
         assertTrue(Call.Details.hasProperty(PROPERTY_HIGH_DEF_AUDIO, PROPERTY_HIGH_DEF_AUDIO));
         assertTrue(Call.Details.hasProperty(PROPERTY_HIGH_DEF_AUDIO | PROPERTY_CONFERENCE
                 | PROPERTY_WIFI, PROPERTY_CONFERENCE));
+        assertTrue(Call.Details.hasProperty(PROPERTY_IS_EXTERNAL_CALL, PROPERTY_IS_EXTERNAL_CALL));
+
         assertFalse(Call.Details.hasProperty(PROPERTY_WIFI, PROPERTY_CONFERENCE));
         assertFalse(Call.Details.hasProperty(PROPERTY_HIGH_DEF_AUDIO | PROPERTY_CONFERENCE
                 | PROPERTY_WIFI, PROPERTY_GENERIC_CONFERENCE));
diff --git a/tests/tests/telecom/src/android/telecom/cts/ConferenceTest.java b/tests/tests/telecom/src/android/telecom/cts/ConferenceTest.java
index 913ca82..c6c01f1 100644
--- a/tests/tests/telecom/src/android/telecom/cts/ConferenceTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/ConferenceTest.java
@@ -222,6 +222,118 @@
         assertCallState(conf, Call.STATE_DISCONNECTED);
     }
 
+    /**
+     * Tests end to end propagation of the {@link Conference} properties to the associated
+     * {@link Call}.
+     */
+    public void testConferenceProperties() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+        final Call conf = mInCallService.getLastConferenceCall();
+        assertCallState(conf, Call.STATE_ACTIVE);
+
+        int properties  = mConferenceObject.getConnectionProperties();
+        properties |= Connection.PROPERTY_IS_EXTERNAL_CALL;
+
+        mConferenceObject.setConnectionProperties(properties);
+
+        // Wait for 2nd properties change; the first will be when the conference is marked with
+        // Call.Details.PROPERTY_CONFERENCE.
+        assertCallProperties(conf, Call.Details.PROPERTY_IS_EXTERNAL_CALL);
+        assertTrue(conf.getDetails().hasProperty(Call.Details.PROPERTY_IS_EXTERNAL_CALL));
+    }
+
+    /**
+     * Verifies {@link Conference#putExtras(Bundle)} calls are propagated to
+     * {@link android.telecom.Call.Callback#onDetailsChanged(Call, Call.Details)}.
+     */
+    public void testConferencePutExtras() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+        final Call conf = mInCallService.getLastConferenceCall();
+        assertCallState(conf, Call.STATE_ACTIVE);
+
+        Bundle extras = new Bundle();
+        extras.putString(TEST_EXTRA_KEY_1, TEST_EXTRA_VALUE_1);
+        extras.putInt(TEST_EXTRA_KEY_2, TEST_EXTRA_VALUE_2);
+        mConferenceObject.putExtras(extras);
+
+        mOnExtrasChangedCounter.waitForCount(1);
+
+        assertTrue(areBundlesEqual(extras, conf.getDetails().getExtras()));
+    }
+
+    /**
+     * Verifies {@link Conference#removeExtras(List)} calls are propagated to
+     * {@link android.telecom.Call.Callback#onDetailsChanged(Call, Call.Details)}.
+     */
+    public void testConferenceRemoveExtras() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+        final Call conf = mInCallService.getLastConferenceCall();
+        assertCallState(conf, Call.STATE_ACTIVE);
+
+        setupExtras();
+
+        mConferenceObject.removeExtras(Arrays.asList(TEST_EXTRA_KEY_1));
+        mOnExtrasChangedCounter.waitForCount(2);
+        Bundle extras = mConferenceObject.getExtras();
+
+        assertFalse(extras.containsKey(TEST_EXTRA_KEY_1));
+        assertTrue(extras.containsKey(TEST_EXTRA_KEY_2));
+    }
+
+    /**
+     * Verifies {@link Conference#removeExtras(String[])} calls are propagated to
+     * {@link android.telecom.Call.Callback#onDetailsChanged(Call, Call.Details)}.
+     */
+    public void testConferenceRemoveExtras2() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+        final Call conf = mInCallService.getLastConferenceCall();
+        assertCallState(conf, Call.STATE_ACTIVE);
+
+        setupExtras();
+
+        mConferenceObject.removeExtras(TEST_EXTRA_KEY_1, TEST_EXTRA_KEY_2);
+        mOnExtrasChangedCounter.waitForCount(2);
+        Bundle extras = mConferenceObject.getExtras();
+
+        assertNull(extras);
+    }
+
+    private void setupExtras() {
+        Bundle extras = new Bundle();
+        extras.putString(TEST_EXTRA_KEY_1, TEST_EXTRA_VALUE_1);
+        extras.putInt(TEST_EXTRA_KEY_2, TEST_EXTRA_VALUE_2);
+        mConferenceObject.putExtras(extras);
+        mOnExtrasChangedCounter.waitForCount(1);
+    }
+
+    /**
+     * Verifies {@link android.telecom.Call#putExtras(Bundle)} changes are propagated to
+     * {@link Conference#onExtrasChanged(Bundle)}.
+     */
+    public void testConferenceOnExtraschanged() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+        final Call conf = mInCallService.getLastConferenceCall();
+        assertCallState(conf, Call.STATE_ACTIVE);
+
+        Bundle extras = new Bundle();
+        extras.putString(TEST_EXTRA_KEY_1, TEST_EXTRA_VALUE_1);
+        extras.putInt(TEST_EXTRA_KEY_2, TEST_EXTRA_VALUE_2);
+        conf.putExtras(extras);
+        mConferenceObject.mOnExtrasChanged.waitForCount(1);
+
+        assertTrue(areBundlesEqual(extras, mConferenceObject.getExtras()));
+    }
+
     public void testConferenceAddAndRemoveConnection() {
         if (!mShouldTestTelecom) {
             return;
diff --git a/tests/tests/telecom/src/android/telecom/cts/ConnectionTest.java b/tests/tests/telecom/src/android/telecom/cts/ConnectionTest.java
index 58dfcdb..874f116 100644
--- a/tests/tests/telecom/src/android/telecom/cts/ConnectionTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/ConnectionTest.java
@@ -164,6 +164,22 @@
         assertEquals(capabilities, connection.getConnectionCapabilities());
     }
 
+    public void testSetAndGetConnectionProperties() {
+        if (!shouldTestTelecom(getContext())) {
+            return;
+        }
+
+        final Semaphore lock = new Semaphore(0);
+        Connection connection = createConnection(lock);
+        waitForStateChange(lock);
+
+        final int properties = Connection.PROPERTY_IS_EXTERNAL_CALL;
+
+        connection.setConnectionProperties(properties);
+
+        assertEquals(properties, connection.getConnectionProperties());
+    }
+
     public void testSetAndGetDisconnectCause() {
         if (!shouldTestTelecom(getContext())) {
             return;
@@ -217,6 +233,79 @@
         assertTrue(extras.getBoolean("test-extra-key"));
     }
 
+    /**
+     * Basic local test of adding extra keys via {@link Connection#removeExtras(List)}.
+     *
+     * Extended end-to-end passing of extras is verified in
+     * {@link CallDetailsTest#testConnectionPutExtras()} and
+     * @link CallDetailsTest#testConnectionRemoveExtras()}.
+     */
+    public void testPutExtras() {
+        if (!shouldTestTelecom(getContext())) {
+            return;
+        }
+
+        final Semaphore lock = new Semaphore(0);
+        Connection connection = createConnection(lock);
+        waitForStateChange(lock);
+
+        assertEquals(null, connection.getExtras());
+
+        final Bundle extras = new Bundle();
+        extras.putBoolean("test-extra-key", true);
+        connection.putExtras(extras);
+
+        final Bundle retrieved = connection.getExtras();
+        assertNotNull(retrieved);
+        assertTrue(extras.getBoolean("test-extra-key"));
+    }
+
+    /**
+     * Basic local test of removing extra keys via {@link Connection#removeExtras(List)}.
+     *
+     * Extended end-to-end passing of extras is verified in
+     * {@link CallDetailsTest#testConnectionPutExtras()} and
+     * @link CallDetailsTest#testConnectionRemoveExtras()}.
+     */
+    public void testRemoveExtras() {
+        if (!shouldTestTelecom(getContext())) {
+            return;
+        }
+
+        final Semaphore lock = new Semaphore(0);
+        Connection connection = createConnection(lock);
+        waitForStateChange(lock);
+
+        assertEquals(null, connection.getExtras());
+
+        final Bundle extras = new Bundle();
+        extras.putBoolean("test-extra-key", true);
+        connection.putExtras(extras);
+        connection.removeExtras(Arrays.asList("test-extra-key"));
+
+        final Bundle retrieved = connection.getExtras();
+        assertNotNull(retrieved);
+        assertFalse(extras.containsKey("test-extra-key"));
+    }
+
+    /**
+     * Tests that the {@link Connection#sendConnectionEvent(String, Bundle)} method exists and can
+     * be called.
+     *
+     * Actual end-to-end tests can be found in {@link CallDetailsTest#testConnectionEvent()}.
+     */
+    public void testSendConnectionEvent() {
+        if (!shouldTestTelecom(getContext())) {
+            return;
+        }
+
+        final Semaphore lock = new Semaphore(0);
+        Connection connection = createConnection(lock);
+        waitForStateChange(lock);
+
+        connection.sendConnectionEvent("test", null);
+    }
+
     public void testSetAndGetStatusHints() {
         if (!shouldTestTelecom(getContext())) {
             return;
@@ -295,6 +384,18 @@
                         | Connection.CAPABILITY_MANAGE_CONFERENCE));
     }
 
+    /**
+     * Tests the {@link Connection#propertiesToString(int)} method.
+     */
+    public void testPropertiesToString() {
+        if (!shouldTestTelecom(getContext())) {
+            return;
+        }
+
+        assertEquals("[Properties: PROPERTY_IS_EXTERNAL_CALL]",
+                Connection.propertiesToString(Connection.PROPERTY_IS_EXTERNAL_CALL));
+    }
+
     private static Connection createConnection(final Semaphore lock) {
         BasicConnection connection = new BasicConnection();
         connection.setLock(lock);
diff --git a/tests/tests/telecom/src/android/telecom/cts/ExternalCallTest.java b/tests/tests/telecom/src/android/telecom/cts/ExternalCallTest.java
new file mode 100644
index 0000000..b50e5cc
--- /dev/null
+++ b/tests/tests/telecom/src/android/telecom/cts/ExternalCallTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2016 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.telecom.cts;
+
+import android.telecom.Call;
+import android.telecom.Connection;
+import android.telecom.ConnectionRequest;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+
+import static android.telecom.cts.TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS;
+
+/**
+ * Tests which verify functionality related to {@link android.telecom.Connection}s and
+ * {@link android.telecom.Call}s with the
+ * {@link android.telecom.Connection#PROPERTY_IS_EXTERNAL_CALL} and
+ * {@link android.telecom.Call.Details#PROPERTY_IS_EXTERNAL_CALL} properties, respectively, set.
+ */
+public class ExternalCallTest extends BaseTelecomTestWithMockServices {
+    public static final int CONNECTION_PROPERTIES = Connection.PROPERTY_IS_EXTERNAL_CALL;
+    public static final int CONNECTION_CAPABILITIES = Connection.CAPABILITY_CAN_PULL_CALL;
+
+    private Call mCall;
+    private MockConnection mConnection;
+    private MockInCallService mInCallService;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        if (mShouldTestTelecom) {
+            PhoneAccount account = setupConnectionService(
+                    new MockConnectionService() {
+                        @Override
+                        public Connection onCreateOutgoingConnection(
+                                PhoneAccountHandle connectionManagerPhoneAccount,
+                                ConnectionRequest request) {
+                            Connection connection = super.onCreateOutgoingConnection(
+                                    connectionManagerPhoneAccount,
+                                    request);
+                            mConnection = (MockConnection) connection;
+                            // Modify the connection object created with local values.
+                            connection.setConnectionCapabilities(CONNECTION_CAPABILITIES);
+                            connection.setConnectionProperties(CONNECTION_PROPERTIES);
+
+                            lock.release();
+                            return connection;
+                        }
+                    }, FLAG_REGISTER | FLAG_ENABLE);
+
+            placeAndVerifyCall();
+            verifyConnectionForOutgoingCall();
+
+            mInCallService = mInCallCallbacks.getService();
+            mCall = mInCallService.getLastCall();
+
+            assertCallState(mCall, Call.STATE_DIALING);
+            assertCallProperties(mCall, Call.Details.PROPERTY_IS_EXTERNAL_CALL);
+            assertCallCapabilities(mCall, Call.Details.CAPABILITY_CAN_PULL_CALL);
+        }
+    }
+
+    /**
+     * Tests that a request to pull an external call via {@link Call#pullExternalCall()} is
+     * communicated to the {@link Connection} via {@link Connection#onPullExternalCall()}.
+     */
+    public void testPullExternalCall() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+
+        final InvokeCounter counter = mConnection.getInvokeCounter(
+                MockConnection.ON_PULL_EXTERNAL_CALL);
+        mCall.pullExternalCall();
+        counter.waitForCount(1, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+    }
+
+    public void testNonPullableExternalCall() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+
+        // Remove the pullable attribute of the connection.
+        mConnection.setConnectionCapabilities(0);
+        assertCallCapabilities(mCall, 0);
+
+        final InvokeCounter counter = mConnection.getInvokeCounter(
+                MockConnection.ON_PULL_EXTERNAL_CALL);
+        // Try to pull -- we expect Telecom to absorb the request since the call is not pullable.
+        mCall.pullExternalCall();
+        counter.waitForCount(0, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+    }
+}
diff --git a/tests/tests/telecom/src/android/telecom/cts/MockConference.java b/tests/tests/telecom/src/android/telecom/cts/MockConference.java
index 89c3772..d84610d 100644
--- a/tests/tests/telecom/src/android/telecom/cts/MockConference.java
+++ b/tests/tests/telecom/src/android/telecom/cts/MockConference.java
@@ -152,4 +152,9 @@
     public String getDtmfString() {
         return mDtmfString;
     }
+
+    @Override
+    public void onExtrasChanged(Bundle extras) {
+        mOnExtrasChanged.invoke(extras);
+    }
 }
diff --git a/tests/tests/telecom/src/android/telecom/cts/MockConnection.java b/tests/tests/telecom/src/android/telecom/cts/MockConnection.java
index 708540a..4436219 100644
--- a/tests/tests/telecom/src/android/telecom/cts/MockConnection.java
+++ b/tests/tests/telecom/src/android/telecom/cts/MockConnection.java
@@ -163,6 +163,30 @@
         }
     }
 
+    @Override
+    public void onCallEvent(String event, Bundle extras) {
+        super.onCallEvent(event, extras);
+        if (mInvokeCounterMap.get(ON_CALL_EVENT) != null) {
+            mInvokeCounterMap.get(ON_CALL_EVENT).invoke(event, extras);
+        }
+    }
+
+    @Override
+    public void onPullExternalCall() {
+        super.onPullExternalCall();
+        if (mInvokeCounterMap.get(ON_PULL_EXTERNAL_CALL) != null) {
+            mInvokeCounterMap.get(ON_PULL_EXTERNAL_CALL).invoke();
+        }
+    }
+
+    @Override
+    public void onExtrasChanged(Bundle extras) {
+        super.onExtrasChanged(extras);
+        if (mInvokeCounterMap.get(ON_EXTRAS_CHANGED) != null) {
+            mInvokeCounterMap.get(ON_EXTRAS_CHANGED).invoke(extras);
+        }
+    }
+
     public int getCurrentState()  {
         return mState;
     }
diff --git a/tests/tests/telecom/src/android/telecom/cts/MockInCallService.java b/tests/tests/telecom/src/android/telecom/cts/MockInCallService.java
index d59a801..4ff3cb6 100644
--- a/tests/tests/telecom/src/android/telecom/cts/MockInCallService.java
+++ b/tests/tests/telecom/src/android/telecom/cts/MockInCallService.java
@@ -145,6 +145,14 @@
                 getCallbacks().onCannedTextResponsesLoaded(call, cannedTextResponses);
             }
         }
+
+        @Override
+        public void onConnectionEvent(Call call, String event, Bundle extras) {
+            super.onConnectionEvent(call, event, extras);
+            if (getCallbacks() != null) {
+                getCallbacks().onConnectionEvent(call, event, extras);
+            }
+        }
     };
 
     private void saveVideoCall(Call call, VideoCall videoCall) {
diff --git a/tests/tests/telecom/src/android/telecom/cts/RemoteConferenceTest.java b/tests/tests/telecom/src/android/telecom/cts/RemoteConferenceTest.java
index f29e09d..3246b9c 100644
--- a/tests/tests/telecom/src/android/telecom/cts/RemoteConferenceTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/RemoteConferenceTest.java
@@ -343,6 +343,35 @@
         mRemoteConferenceObject.unregisterCallback(callback);
     }
 
+    public void testRemoteConferenceCallbacks_ConnectionProperties() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+        Handler handler = setupRemoteConferenceCallbacksTest();
+
+        final InvokeCounter callbackInvoker =
+                new InvokeCounter("testRemoteConferenceCallbacks_ConnectionProperties");
+        RemoteConference.Callback callback;
+
+        callback = new RemoteConference.Callback() {
+            @Override
+            public void onConnectionPropertiesChanged(
+                    RemoteConference conference,
+                    int connectionProperties) {
+                super.onConnectionPropertiesChanged(conference, connectionProperties);
+                callbackInvoker.invoke(conference, connectionProperties);
+            }
+        };
+        mRemoteConferenceObject.registerCallback(callback, handler);
+        int properties = mRemoteConference.getConnectionCapabilities()
+                | Connection.PROPERTY_IS_EXTERNAL_CALL;
+        mRemoteConference.setConnectionProperties(properties);
+        callbackInvoker.waitForCount(1, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+        assertEquals(mRemoteConferenceObject, callbackInvoker.getArgs(0)[0]);
+        assertEquals(properties, callbackInvoker.getArgs(0)[1]);
+        mRemoteConferenceObject.unregisterCallback(callback);
+    }
+
     public void testRemoteConferenceCallbacks_ConferenceableConnections() {
         if (!mShouldTestTelecom) {
             return;
diff --git a/tests/tests/telecom/src/android/telecom/cts/RemoteConnectionTest.java b/tests/tests/telecom/src/android/telecom/cts/RemoteConnectionTest.java
index eb9e055..81080b0 100644
--- a/tests/tests/telecom/src/android/telecom/cts/RemoteConnectionTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/RemoteConnectionTest.java
@@ -240,6 +240,36 @@
         mRemoteConnectionObject.unregisterCallback(callback);
     }
 
+    public void testRemoteConnectionCallbacks_ConnectionProperties() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+
+        Handler handler = setupRemoteConnectionCallbacksTest();
+
+        final InvokeCounter callbackInvoker =
+                new InvokeCounter("testRemoteConnectionCallbacks_ConnectionCapabilities");
+        RemoteConnection.Callback callback;
+
+        callback = new RemoteConnection.Callback() {
+            @Override
+            public void onConnectionPropertiesChanged(
+                    RemoteConnection connection,
+                    int connectionProperties) {
+                super.onConnectionPropertiesChanged(connection, connectionProperties);
+                callbackInvoker.invoke(connection, connectionProperties);
+            }
+        };
+        mRemoteConnectionObject.registerCallback(callback, handler);
+        int properties = mRemoteConnection.getConnectionCapabilities()
+                | Connection.PROPERTY_IS_EXTERNAL_CALL;
+        mRemoteConnection.setConnectionProperties(properties);
+        callbackInvoker.waitForCount(1, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+        assertEquals(mRemoteConnectionObject, callbackInvoker.getArgs(0)[0]);
+        assertEquals(properties, callbackInvoker.getArgs(0)[1]);
+        mRemoteConnectionObject.unregisterCallback(callback);
+    }
+
     public void testRemoteConnectionCallbacks_PostDialWait() {
         if (!mShouldTestTelecom) {
             return;
@@ -528,6 +558,40 @@
         mRemoteConnectionObject.unregisterCallback(callback);
     }
 
+    /**
+     * Verifies that a {@link RemoteConnection} receives a
+     * {@link Connection#sendConnectionEvent(String, Bundle)} notification.
+     */
+    public void testRemoteConnectionCallbacks_ConnectionEvent() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+
+        Handler handler = setupRemoteConnectionCallbacksTest();
+
+        final InvokeCounter callbackInvoker =
+                new InvokeCounter("testRemoteConnectionCallbacks_Extras");
+        RemoteConnection.Callback callback;
+
+        callback = new RemoteConnection.Callback() {
+            @Override
+            public void onConnectionEvent(RemoteConnection connection, String event,
+                    Bundle extras) {
+                super.onConnectionEvent(connection, event, extras);
+                callbackInvoker.invoke(connection, event, extras);
+            }
+        };
+        mRemoteConnectionObject.registerCallback(callback, handler);
+        Bundle extras = new Bundle();
+        extras.putString(TelecomManager.EXTRA_CALL_DISCONNECT_MESSAGE, "Test");
+        mRemoteConnection.sendConnectionEvent(Connection.EVENT_CALL_PULL_FAILED, extras);
+        callbackInvoker.waitForCount(1, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+        assertEquals(mRemoteConnectionObject, callbackInvoker.getArgs(0)[0]);
+        assertEquals(Connection.EVENT_CALL_PULL_FAILED, callbackInvoker.getArgs(0)[1]);
+        assertTrue(areBundlesEqual(extras, (Bundle) callbackInvoker.getArgs(0)[2]));
+        mRemoteConnectionObject.unregisterCallback(callback);
+    }
+
     public void testRemoteConnectionCallbacks_Disconnect() {
         if (!mShouldTestTelecom) {
             return;
@@ -557,6 +621,23 @@
         mRemoteConnectionObject.unregisterCallback(callback);
     }
 
+    /**
+     * Verifies that a call to {@link RemoteConnection#pullExternalCall()} is proxied to
+     * {@link Connection#onPullExternalCall()}.
+     */
+    public void testRemoteConnectionCallbacks_PullExternalCall() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+
+        Handler handler = setupRemoteConnectionCallbacksTest();
+
+        InvokeCounter counter =
+                mRemoteConnection.getInvokeCounter(MockConnection.ON_PULL_EXTERNAL_CALL);
+        mRemoteConnectionObject.pullExternalCall();
+        counter.waitForCount(1);
+    }
+
     public void testRemoteConnectionCallbacks_Destroy() {
         if (!mShouldTestTelecom) {
             return;
@@ -1104,6 +1185,8 @@
                 remoteConnection.getCallerDisplayNamePresentation());
         assertEquals(connection.getConnectionCapabilities(),
                 remoteConnection.getConnectionCapabilities());
+        assertEquals(connection.getConnectionProperties(),
+                remoteConnection.getConnectionProperties());
         assertEquals(connection.getDisconnectCause(), remoteConnection.getDisconnectCause());
         assertEquals(connection.getExtras(), remoteConnection.getExtras());
         assertEquals(connection.getStatusHints(), remoteConnection.getStatusHints());
diff --git a/tests/tests/view/Android.mk b/tests/tests/view/Android.mk
index 6a8d49c..ba4be93 100644
--- a/tests/tests/view/Android.mk
+++ b/tests/tests/view/Android.mk
@@ -29,11 +29,15 @@
 LOCAL_JAVA_LIBRARIES := android.test.runner
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
-    ctsdeviceutil ctstestrunner mockito-target
+    ctsdeviceutil \
+    ctstestrunner \
+    mockito-target \
+    ub-uiautomator \
+    android-support-test
 
 LOCAL_JNI_SHARED_LIBRARIES := libctsview_jni libnativehelper_compat_libc++
 
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-renderscript-files-under, src)
 
 LOCAL_PACKAGE_NAME := CtsViewTestCases
 
diff --git a/tests/tests/view/AndroidManifest.xml b/tests/tests/view/AndroidManifest.xml
index a98c447..ba1f3d2 100644
--- a/tests/tests/view/AndroidManifest.xml
+++ b/tests/tests/view/AndroidManifest.xml
@@ -239,6 +239,13 @@
             </intent-filter>
         </activity>
 
+        <activity android:name="android.view.cts.surfacevalidator.CapturedActivity"
+                  android:theme="@style/WhiteBackgroundTheme">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
     </application>
 
     <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/view/res/raw/colors_video.mp4 b/tests/tests/view/res/raw/colors_video.mp4
new file mode 100644
index 0000000..0bec670
--- /dev/null
+++ b/tests/tests/view/res/raw/colors_video.mp4
Binary files differ
diff --git a/tests/tests/view/res/values/styles.xml b/tests/tests/view/res/values/styles.xml
index 9de4abd..4979241 100644
--- a/tests/tests/view/res/values/styles.xml
+++ b/tests/tests/view/res/values/styles.xml
@@ -177,4 +177,13 @@
         <item name="android:windowSwipeToDismiss">false</item>
     </style>
 
+    <style name="WhiteBackgroundTheme" parent="@android:style/Theme.Holo.NoActionBar.Fullscreen">
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:windowFullscreen">true</item>
+        <item name="android:windowOverscan">true</item>
+        <item name="android:fadingEdge">none</item>
+        <item name="android:windowBackground">@android:color/white</item>
+        <item name="android:windowContentTransitions">false</item>
+        <item name="android:windowAnimationStyle">@null</item>
+    </style>
 </resources>
diff --git a/tests/tests/view/src/android/view/cts/SurfaceViewSyncTests.java b/tests/tests/view/src/android/view/cts/SurfaceViewSyncTests.java
new file mode 100644
index 0000000..da453dd
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/SurfaceViewSyncTests.java
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2016 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.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
+import android.annotation.SuppressLint;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.media.MediaPlayer;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.UiSelector;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.LinearInterpolator;
+import android.view.cts.surfacevalidator.AnimationFactory;
+import android.view.cts.surfacevalidator.AnimationTestCase;
+import android.view.cts.surfacevalidator.CapturedActivity;
+import android.view.cts.surfacevalidator.ViewFactory;
+import android.widget.FrameLayout;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+@SuppressLint("RtlHardcoded")
+public class SurfaceViewSyncTests {
+    private static final String TAG = "SurfaceViewSyncTests";
+    private static final int PERMISSION_DIALOG_WAIT_MS = 500;
+
+    @Before
+    public void setUp() throws UiObjectNotFoundException {
+        // The permission dialog will be auto-opened by the activity - find it and accept
+        UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        UiSelector acceptButtonSelector = new UiSelector().resourceId("android:id/button1");
+        UiObject acceptButton = uiDevice.findObject(acceptButtonSelector);
+        if (acceptButton.waitForExists(PERMISSION_DIALOG_WAIT_MS)) {
+            assertTrue(acceptButton.click());
+        }
+    }
+
+    private CapturedActivity getActivity() {
+        return (CapturedActivity) mActivityRule.getActivity();
+    }
+
+    private MediaPlayer getMediaPlayer() {
+        return getActivity().getMediaPlayer();
+    }
+
+    @Rule
+    public ActivityTestRule mActivityRule = new ActivityTestRule<>(CapturedActivity.class);
+
+    static ValueAnimator makeInfinite(ValueAnimator a) {
+        a.setRepeatMode(ObjectAnimator.REVERSE);
+        a.setRepeatCount(ObjectAnimator.INFINITE);
+        a.setDuration(200);
+        a.setInterpolator(new LinearInterpolator());
+        return a;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // ViewFactories
+    ///////////////////////////////////////////////////////////////////////////
+
+    private ViewFactory sEmptySurfaceViewFactory = SurfaceView::new;
+
+    private ViewFactory sGreenSurfaceViewFactory = context -> {
+        SurfaceView surfaceView = new SurfaceView(context);
+        surfaceView.getHolder().setFixedSize(640, 480);
+        surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
+            @Override
+            public void surfaceCreated(SurfaceHolder holder) {}
+
+            @Override
+            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+                Canvas canvas = holder.lockCanvas();
+                canvas.drawColor(Color.GREEN);
+                holder.unlockCanvasAndPost(canvas);
+            }
+
+            @Override
+            public void surfaceDestroyed(SurfaceHolder holder) {}
+        });
+        return surfaceView;
+    };
+
+    private ViewFactory sVideoViewFactory = context -> {
+        SurfaceView surfaceView = new SurfaceView(context);
+        surfaceView.getHolder().setFixedSize(640, 480);
+        surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
+            @Override
+            public void surfaceCreated(SurfaceHolder holder) {
+                getMediaPlayer().setSurface(holder.getSurface());
+                getMediaPlayer().start();
+            }
+
+            @Override
+            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}
+
+            @Override
+            public void surfaceDestroyed(SurfaceHolder holder) {
+                getMediaPlayer().pause();
+                getMediaPlayer().setSurface(null);
+            }
+        });
+        return surfaceView;
+    };
+
+    ///////////////////////////////////////////////////////////////////////////
+    // AnimationFactories
+    ///////////////////////////////////////////////////////////////////////////
+
+    private AnimationFactory sSmallScaleAnimationFactory = view -> {
+        view.setPivotX(0);
+        view.setPivotY(0);
+        PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat(View.SCALE_X, 0.01f, 1f);
+        PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat(View.SCALE_Y, 0.01f, 1f);
+        return makeInfinite(ObjectAnimator.ofPropertyValuesHolder(view, pvhX, pvhY));
+    };
+
+    private AnimationFactory sBigScaleAnimationFactory = view -> {
+        view.setTranslationX(10);
+        view.setTranslationY(10);
+        view.setPivotX(0);
+        view.setPivotY(0);
+        PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat(View.SCALE_X, 1f, 3f);
+        PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat(View.SCALE_Y, 1f, 3f);
+        return makeInfinite(ObjectAnimator.ofPropertyValuesHolder(view, pvhX, pvhY));
+    };
+
+    private AnimationFactory sTranslateAnimationFactory = view -> {
+        PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat(View.TRANSLATION_X, 10f, 30f);
+        PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, 10f, 30f);
+        return makeInfinite(ObjectAnimator.ofPropertyValuesHolder(view, pvhX, pvhY));
+    };
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Tests
+    ///////////////////////////////////////////////////////////////////////////
+
+    /** Draws a moving 10x10 black rectangle, validates 100 pixels of black are seen each frame */
+    @Test
+    public void testSmallRect() {
+        CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
+                context -> new View(context) {
+                    // draw a single pixel
+                    final Paint sBlackPaint = new Paint();
+                    @Override
+                    protected void onDraw(Canvas canvas) {
+                        canvas.drawRect(0, 0, 10, 10, sBlackPaint);
+                    }
+
+                    @SuppressWarnings("unused")
+                    void setOffset(int offset) {
+                        // Note: offset by integer values, to ensure no rounding
+                        // is done in rendering layer, as that may be brittle
+                        setTranslationX(offset);
+                        setTranslationY(offset);
+                    }
+                },
+                new FrameLayout.LayoutParams(100, 100, Gravity.LEFT | Gravity.TOP),
+                view -> makeInfinite(ObjectAnimator.ofInt(view, "offset", 10, 30)),
+                (blackishPixelCount, width, height) -> blackishPixelCount == 100));
+        assertTrue(result.passFrames > 100);
+        assertTrue(result.failFrames == 0);
+    }
+
+    /**
+     * Verifies that a SurfaceView without a surface is entirely black, with pixel count being
+     * approximate to avoid rounding brittleness.
+     */
+    @Test
+    public void testEmptySurfaceView() {
+        CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
+                sEmptySurfaceViewFactory,
+                new FrameLayout.LayoutParams(100, 100, Gravity.LEFT | Gravity.TOP),
+                sTranslateAnimationFactory,
+                (blackishPixelCount, width, height) ->
+                        blackishPixelCount > 9000 && blackishPixelCount < 11000));
+        assertTrue(result.passFrames > 100);
+        assertTrue(result.failFrames == 0);
+    }
+
+    @Test
+    public void testSurfaceViewSmallScale() {
+        CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
+                sGreenSurfaceViewFactory,
+                new FrameLayout.LayoutParams(640, 480, Gravity.LEFT | Gravity.TOP),
+                sSmallScaleAnimationFactory,
+                (blackishPixelCount, width, height) -> blackishPixelCount == 0));
+        assertTrue(result.passFrames > 100);
+        assertTrue(result.failFrames == 0);
+    }
+
+    @Test
+    public void testSurfaceViewBigScale() {
+        CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
+                sGreenSurfaceViewFactory,
+                new FrameLayout.LayoutParams(640, 480, Gravity.LEFT | Gravity.TOP),
+                sBigScaleAnimationFactory,
+                (blackishPixelCount, width, height) -> blackishPixelCount == 0));
+        assertTrue(result.passFrames > 100);
+        assertTrue(result.failFrames == 0);
+    }
+
+    @Test
+    public void testVideoSurfaceViewTranslate() {
+        CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
+                sVideoViewFactory,
+                new FrameLayout.LayoutParams(640, 480, Gravity.LEFT | Gravity.TOP),
+                sTranslateAnimationFactory,
+                (blackishPixelCount, width, height) -> blackishPixelCount == 0));
+        assertTrue(result.passFrames > 100);
+        assertTrue(result.failFrames == 0);
+    }
+
+    @Test
+    public void testVideoSurfaceViewRotated() {
+        CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
+                sVideoViewFactory,
+                new FrameLayout.LayoutParams(100, 100, Gravity.LEFT | Gravity.TOP),
+                view -> makeInfinite(ObjectAnimator.ofPropertyValuesHolder(view,
+                        PropertyValuesHolder.ofFloat(View.TRANSLATION_X, 10f, 30f),
+                        PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, 10f, 30f),
+                        PropertyValuesHolder.ofFloat(View.ROTATION, 45f, 45f))),
+                (blackishPixelCount, width, height) -> blackishPixelCount == 0));
+        assertTrue(result.passFrames > 100);
+        assertTrue(result.failFrames == 0);
+    }
+
+    @Test
+    public void testVideoSurfaceViewEdgeCoverage() {
+        CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
+                sVideoViewFactory,
+                new FrameLayout.LayoutParams(640, 480, Gravity.CENTER),
+                view -> {
+                    ViewGroup parent = (ViewGroup) view.getParent();
+                    final int x = parent.getWidth() / 2;
+                    final int y = parent.getHeight() / 2;
+
+                    // Animate from left, to top, to right, to bottom
+                    return makeInfinite(ObjectAnimator.ofPropertyValuesHolder(view,
+                            PropertyValuesHolder.ofFloat(View.TRANSLATION_X, -x, 0, x, 0, -x),
+                            PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, 0, -y, 0, y, 0)));
+                },
+                (blackishPixelCount, width, height) -> blackishPixelCount == 0));
+        assertTrue(result.passFrames > 100);
+        assertTrue(result.failFrames == 0);
+    }
+
+    @Test
+    public void testVideoSurfaceViewCornerCoverage() {
+        CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
+                sVideoViewFactory,
+                new FrameLayout.LayoutParams(640, 480, Gravity.CENTER),
+                view -> {
+                    ViewGroup parent = (ViewGroup) view.getParent();
+                    final int x = parent.getWidth() / 2;
+                    final int y = parent.getHeight() / 2;
+
+                    // Animate from top left, to top right, to bottom right, to bottom left
+                    return makeInfinite(ObjectAnimator.ofPropertyValuesHolder(view,
+                            PropertyValuesHolder.ofFloat(View.TRANSLATION_X, -x, x, x, -x, -x),
+                            PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, -y, -y, y, y, -y)));
+                },
+                (blackishPixelCount, width, height) -> blackishPixelCount == 0));
+        assertTrue(result.passFrames > 100);
+        assertTrue(result.failFrames == 0);
+    }
+}
diff --git a/tests/tests/view/src/android/view/cts/surfacevalidator/AnimationFactory.java b/tests/tests/view/src/android/view/cts/surfacevalidator/AnimationFactory.java
new file mode 100644
index 0000000..c4c19cf
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/surfacevalidator/AnimationFactory.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2016 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.surfacevalidator;
+
+import android.animation.ValueAnimator;
+import android.view.View;
+
+public interface AnimationFactory {
+    ValueAnimator createAnimator(View view);
+}
diff --git a/tests/tests/view/src/android/view/cts/surfacevalidator/AnimationTestCase.java b/tests/tests/view/src/android/view/cts/surfacevalidator/AnimationTestCase.java
new file mode 100644
index 0000000..6b455e2
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/surfacevalidator/AnimationTestCase.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2016 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.surfacevalidator;
+
+import android.animation.ValueAnimator;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.view.View;
+import android.widget.FrameLayout;
+
+public class AnimationTestCase {
+    private final ViewFactory mViewFactory;
+    private final FrameLayout.LayoutParams mLayoutParams;
+    private final AnimationFactory mAnimationFactory;
+    private final PixelChecker mPixelChecker;
+
+    private FrameLayout mParent;
+    private ValueAnimator mAnimator;
+
+    public AnimationTestCase(ViewFactory viewFactory,
+            FrameLayout.LayoutParams layoutParams,
+            AnimationFactory animationFactory,
+            PixelChecker pixelChecker) {
+        mViewFactory = viewFactory;
+        mLayoutParams = layoutParams;
+        mAnimationFactory = animationFactory;
+        mPixelChecker = pixelChecker;
+    }
+
+    PixelChecker getChecker() {
+        return mPixelChecker;
+    }
+
+    public void start(Context context, FrameLayout parent) {
+        mParent = parent;
+        mParent.removeAllViews();
+        View view = mViewFactory.createView(context);
+        mParent.addView(view, mLayoutParams);
+        mAnimator = mAnimationFactory.createAnimator(view);
+        mAnimator.start();
+    }
+
+    public void end() {
+        mAnimator.cancel();
+        mParent.removeAllViews();
+    }
+}
diff --git a/tests/tests/view/src/android/view/cts/surfacevalidator/CapturedActivity.java b/tests/tests/view/src/android/view/cts/surfacevalidator/CapturedActivity.java
new file mode 100644
index 0000000..a198136
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/surfacevalidator/CapturedActivity.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2016 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.surfacevalidator;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.graphics.Point;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.media.MediaPlayer;
+import android.media.projection.MediaProjection;
+import android.media.projection.MediaProjectionManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Display;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import android.view.cts.R;
+
+public class CapturedActivity extends Activity {
+    public static class TestResult {
+        public int passFrames;
+        public int failFrames;
+    }
+
+    private static final String TAG = "CapturedActivity";
+    private static final long TIME_OUT_MS = 10000;
+    private static final int PERMISSION_CODE = 1;
+    private MediaProjectionManager mProjectionManager;
+    private MediaProjection mMediaProjection;
+    private VirtualDisplay mVirtualDisplay;
+
+    private SurfacePixelValidator mSurfacePixelValidator;
+    private final Object mLock = new Object();
+
+    private static final long START_CAPTURE_DELAY_MS = 1000;
+    private static final long END_CAPTURE_DELAY_MS = START_CAPTURE_DELAY_MS + 4000;
+    private static final long END_DELAY_MS = END_CAPTURE_DELAY_MS + 500;
+
+    private MediaPlayer mMediaPlayer;
+
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+    private volatile boolean mOnWatch;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        getWindow().getDecorView().setSystemUiVisibility(
+                View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN);
+
+        mProjectionManager =
+                (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
+
+        startActivityForResult(mProjectionManager.createScreenCaptureIntent(), PERMISSION_CODE);
+
+        mMediaPlayer = MediaPlayer.create(this, R.raw.colors_video);
+        mMediaPlayer.setLooping(true);
+
+        int uiMode = getResources().getConfiguration().uiMode;
+        mOnWatch = (uiMode & Configuration.UI_MODE_TYPE_WATCH) == Configuration.UI_MODE_TYPE_WATCH;
+    }
+
+    /**
+     * MediaPlayer pre-loaded with a video with no black pixels. Be kind, rewind.
+     */
+    public MediaPlayer getMediaPlayer() {
+        return mMediaPlayer;
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        Log.d(TAG, "onDestroy");
+        if (mMediaProjection != null) {
+            mMediaProjection.stop();
+            mMediaProjection = null;
+        }
+    }
+
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (requestCode != PERMISSION_CODE) {
+            throw new IllegalStateException("Unknown request code: " + requestCode);
+        }
+        if (resultCode != RESULT_OK) {
+            throw new IllegalStateException("User denied screen sharing permission");
+        }
+        mMediaProjection = mProjectionManager.getMediaProjection(resultCode, data);
+        mMediaProjection.registerCallback(new MediaProjectionCallback(), null);
+    }
+
+    public TestResult runTest(AnimationTestCase animationTestCase) {
+        TestResult testResult = new TestResult();
+        if (mOnWatch) {
+            /**
+             * Watch devices not supported, since they may not support:
+             *    1) displaying unmasked windows
+             *    2) RenderScript
+             *    3) Video playback
+             */
+            Log.d(TAG, "Skipping test on watch.");
+            testResult.passFrames = 1000;
+            testResult.failFrames = 0;
+            return testResult;
+        }
+
+        mHandler.post(() -> {
+            Log.d(TAG, "Setting up test case");
+
+            // shouldn't be necessary, since we've already done this in #create,
+            // but ensure status/nav are hidden for test
+            getWindow().getDecorView().setSystemUiVisibility(
+                    View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN);
+
+            animationTestCase.start(getApplicationContext(),
+                    (FrameLayout) findViewById(android.R.id.content));
+        });
+
+        mHandler.postDelayed(() -> {
+            Log.d(TAG, "Starting capture");
+
+            Display display = getWindow().getDecorView().getDisplay();
+            Point size = new Point();
+            DisplayMetrics metrics = new DisplayMetrics();
+            display.getRealSize(size);
+            display.getMetrics(metrics);
+
+            mSurfacePixelValidator = new SurfacePixelValidator(CapturedActivity.this,
+                    size, animationTestCase.getChecker());
+            Log.d("MediaProjection", "Size is " + size.toString());
+            mVirtualDisplay = mMediaProjection.createVirtualDisplay("ScreenSharingDemo",
+                    size.x, size.y,
+                    metrics.densityDpi,
+                    DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
+                    mSurfacePixelValidator.getSurface(),
+                    null /*Callbacks*/,
+                    null /*Handler*/);
+        }, START_CAPTURE_DELAY_MS);
+
+        mHandler.postDelayed(() -> {
+            Log.d(TAG, "Stopping capture");
+            mVirtualDisplay.release();
+            mVirtualDisplay = null;
+        }, END_CAPTURE_DELAY_MS);
+
+        mHandler.postDelayed(() -> {
+            Log.d(TAG, "Ending test case");
+            animationTestCase.end();
+            synchronized (mLock) {
+                mSurfacePixelValidator.finish(testResult);
+                mLock.notify();
+            }
+            mSurfacePixelValidator = null;
+        }, END_DELAY_MS);
+
+        synchronized (mLock) {
+            try {
+                mLock.wait(TIME_OUT_MS);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+        Log.d(TAG, "Test finished, passFrames " + testResult.passFrames
+                + ", failFrames " + testResult.failFrames);
+        return testResult;
+    }
+
+    private class MediaProjectionCallback extends MediaProjection.Callback {
+        @Override
+        public void onStop() {
+            Log.d(TAG, "MediaProjectionCallback#onStop");
+            if (mVirtualDisplay != null) {
+                mVirtualDisplay.release();
+                mVirtualDisplay = null;
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/view/src/android/view/cts/surfacevalidator/PixelChecker.java b/tests/tests/view/src/android/view/cts/surfacevalidator/PixelChecker.java
new file mode 100644
index 0000000..76f0adc
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/surfacevalidator/PixelChecker.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2016 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.surfacevalidator;
+
+public interface PixelChecker {
+    boolean checkPixels(int blackishPixelCount, int width, int height);
+}
\ No newline at end of file
diff --git a/tests/tests/view/src/android/view/cts/surfacevalidator/PixelCounter.rs b/tests/tests/view/src/android/view/cts/surfacevalidator/PixelCounter.rs
new file mode 100644
index 0000000..55bc251
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/surfacevalidator/PixelCounter.rs
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+#pragma version(1)
+#pragma rs java_package_name(android.view.cts.surfacevalidator)
+
+int WIDTH;
+uchar THRESHOLD;
+
+rs_allocation image;
+
+void countBlackishPixels(const int32_t *v_in, int *v_out){
+    int y = v_in[0];
+    v_out[0] = 0;
+
+    for(int i = 0 ; i < WIDTH; i++){
+        uchar4 pixel = rsGetElementAt_uchar4(image, i, y);
+        if (pixel.r < THRESHOLD
+                && pixel.g < THRESHOLD
+                && pixel.b < THRESHOLD) {
+            v_out[0]++;
+        }
+    }
+}
diff --git a/tests/tests/view/src/android/view/cts/surfacevalidator/SurfacePixelValidator.java b/tests/tests/view/src/android/view/cts/surfacevalidator/SurfacePixelValidator.java
new file mode 100644
index 0000000..c9bff1d
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/surfacevalidator/SurfacePixelValidator.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2016 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.surfacevalidator;
+
+import android.content.Context;
+import android.graphics.Point;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Trace;
+import android.renderscript.Allocation;
+import android.renderscript.Element;
+import android.renderscript.RenderScript;
+import android.renderscript.Type;
+import android.util.Log;
+import android.view.Surface;
+import android.view.cts.surfacevalidator.PixelChecker;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+public class SurfacePixelValidator {
+    private static final String TAG = "SurfacePixelValidator";
+
+    /**
+     * Observed that first few frames have errors with SurfaceView placement, so we skip for now.
+     * b/29603849 tracking that issue.
+     */
+    private static final int NUM_FIRST_FRAMES_SKIPPED = 8;
+
+    // If no channel is greater than this value, pixel will be considered 'blackish'.
+    private static final short PIXEL_CHANNEL_THRESHOLD = 4;
+
+    private final int mWidth;
+    private final int mHeight;
+
+    private final HandlerThread mWorkerThread;
+    private final Handler mWorkerHandler;
+
+    private final PixelChecker mPixelChecker;
+
+    private final RenderScript mRS;
+
+    private final Allocation mInPixelsAllocation;
+    private final Allocation mInRowsAllocation;
+    private final Allocation mOutRowsAllocation;
+    private final ScriptC_PixelCounter mScript;
+
+
+    private final Object mResultLock = new Object();
+    private int mResultSuccessFrames;
+    private int mResultFailureFrames;
+
+    private Runnable mConsumeRunnable = new Runnable() {
+        int numSkipped = 0;
+        @Override
+        public void run() {
+            Trace.beginSection("consume buffer");
+            mInPixelsAllocation.ioReceive();
+            mScript.set_image(mInPixelsAllocation);
+            Trace.endSection();
+
+            Trace.beginSection("compare");
+            mScript.forEach_countBlackishPixels(mInRowsAllocation, mOutRowsAllocation);
+            Trace.endSection();
+
+            Trace.beginSection("sum");
+            int blackishPixelCount = sum1DIntAllocation(mOutRowsAllocation, mHeight);
+            Trace.endSection();
+
+            boolean success = mPixelChecker.checkPixels(blackishPixelCount, mWidth, mHeight);
+            synchronized (mResultLock) {
+                if (numSkipped < NUM_FIRST_FRAMES_SKIPPED) {
+                    numSkipped++;
+                } else {
+
+                    if (success) {
+                        mResultSuccessFrames++;
+                    } else {
+                        mResultFailureFrames++;
+                        int totalFramesSeen = mResultSuccessFrames + mResultFailureFrames;
+                        Log.d(TAG, "Failure (pixel count = " + blackishPixelCount
+                                + ") occurred on frame " + totalFramesSeen);
+                    }
+                }
+            }
+        }
+    };
+
+    public SurfacePixelValidator(Context context, Point size, PixelChecker pixelChecker) {
+        mWidth = size.x;
+        mHeight = size.y;
+
+        mWorkerThread = new HandlerThread("SurfacePixelValidator");
+        mWorkerThread.start();
+        mWorkerHandler = new Handler(mWorkerThread.getLooper());
+
+        mPixelChecker = pixelChecker;
+
+        mRS = RenderScript.create(context);
+        mScript = new ScriptC_PixelCounter(mRS);
+
+        mInPixelsAllocation = createBufferQueueAllocation();
+        mInRowsAllocation = createInputRowIndexAllocation();
+        mOutRowsAllocation = createOutputRowAllocation();
+        mScript.set_WIDTH(mWidth);
+        mScript.set_THRESHOLD(PIXEL_CHANNEL_THRESHOLD);
+
+        mInPixelsAllocation.setOnBufferAvailableListener(
+                allocation -> mWorkerHandler.post(mConsumeRunnable));
+    }
+
+    public Surface getSurface() {
+        return mInPixelsAllocation.getSurface();
+    }
+
+    static private int sum1DIntAllocation(Allocation array, int length) {
+        //Get the values returned from the function
+        int[] returnValue = new int[length];
+        array.copyTo(returnValue);
+        int sum = 0;
+        //If any row had any different pixels, then it fails
+        for (int i = 0; i < length; i++) {
+            sum += returnValue[i];
+        }
+        return sum;
+    }
+
+    /**
+     * Creates an allocation where the values in it are the indices of each row
+     */
+    private Allocation createInputRowIndexAllocation() {
+        //Create an array with the index of each row
+        int[] inputIndices = new int[mHeight];
+        for (int i = 0; i < mHeight; i++) {
+            inputIndices[i] = i;
+        }
+        //Create the allocation from that given array
+        Allocation inputAllocation = Allocation.createSized(mRS, Element.I32(mRS),
+                inputIndices.length, Allocation.USAGE_SCRIPT);
+        inputAllocation.copyFrom(inputIndices);
+        return inputAllocation;
+    }
+
+    private Allocation createOutputRowAllocation() {
+        return Allocation.createSized(mRS, Element.I32(mRS), mHeight, Allocation.USAGE_SCRIPT);
+    }
+
+    private Allocation createBufferQueueAllocation() {
+        return Allocation.createAllocations(mRS, Type.createXY(mRS,
+                Element.U8_4(mRS), mWidth, mHeight),
+                Allocation.USAGE_SCRIPT | Allocation.USAGE_IO_INPUT,
+                1)[0];
+    }
+
+    /**
+     * Shuts down processing pipeline, and returns current pass/fail counts.
+     *
+     * Wait for pipeline to flush before calling this method. If not, frames that are still in
+     * flight may be lost.
+     */
+    public void finish(CapturedActivity.TestResult testResult) {
+        synchronized (mResultLock) {
+            // could in theory miss results still processing, but only if latency is extremely high.
+            // Caller should only call this
+            testResult.failFrames = mResultFailureFrames;
+            testResult.passFrames = mResultSuccessFrames;
+        }
+        mWorkerThread.quitSafely();
+    }
+}
diff --git a/tests/tests/view/src/android/view/cts/surfacevalidator/ViewFactory.java b/tests/tests/view/src/android/view/cts/surfacevalidator/ViewFactory.java
new file mode 100644
index 0000000..9ef2ef8
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/surfacevalidator/ViewFactory.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2016 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.surfacevalidator;
+
+import android.content.Context;
+import android.view.View;
+
+public interface ViewFactory {
+    View createView(Context context);
+}
diff --git a/tests/tests/view/src/android/view/inputmethod/cts/EditorInfoTest.java b/tests/tests/view/src/android/view/inputmethod/cts/EditorInfoTest.java
index e9c3058..0780460 100644
--- a/tests/tests/view/src/android/view/inputmethod/cts/EditorInfoTest.java
+++ b/tests/tests/view/src/android/view/inputmethod/cts/EditorInfoTest.java
@@ -21,6 +21,7 @@
 import android.os.LocaleList;
 import android.os.Parcel;
 import android.test.AndroidTestCase;
+import android.test.MoreAsserts;
 import android.text.TextUtils;
 import android.util.Printer;
 import android.view.inputmethod.EditorInfo;
@@ -50,6 +51,7 @@
         b.putString(key, value);
         info.extras = b;
         info.hintLocales = LocaleList.forLanguageTags("en-PH,en-US");
+        info.contentMimeTypes = new String[]{"image/gif", "image/png"};
 
         assertEquals(0, info.describeContents());
 
@@ -73,6 +75,7 @@
         assertEquals(info.label.toString(), targetInfo.label.toString());
         assertEquals(info.extras.getString(key), targetInfo.extras.getString(key));
         assertEquals(info.hintLocales, targetInfo.hintLocales);
+        MoreAsserts.assertEquals(info.contentMimeTypes, targetInfo.contentMimeTypes);
 
         TestPrinter printer = new TestPrinter();
         String prefix = "TestEditorInfo";
diff --git a/tests/tests/view/src/android/view/inputmethod/cts/InputConnectionWrapperTest.java b/tests/tests/view/src/android/view/inputmethod/cts/InputConnectionWrapperTest.java
index 1ddfd2b..c9b3ad7 100644
--- a/tests/tests/view/src/android/view/inputmethod/cts/InputConnectionWrapperTest.java
+++ b/tests/tests/view/src/android/view/inputmethod/cts/InputConnectionWrapperTest.java
@@ -17,6 +17,8 @@
 package android.view.inputmethod.cts;
 
 
+import android.content.ClipDescription;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
 import android.test.AndroidTestCase;
@@ -29,6 +31,7 @@
 import android.view.inputmethod.ExtractedTextRequest;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputConnectionWrapper;
+import android.view.inputmethod.InputContentInfo;
 
 public class InputConnectionWrapperTest extends AndroidTestCase {
 
@@ -94,6 +97,13 @@
         assertFalse(inputConnection.isGetHandlerCalled);
         assertNull(inputConnection.getHandler());
         assertTrue(inputConnection.isGetHandlerCalled);
+        assertFalse(inputConnection.isCommitContentCalled);
+        final InputContentInfo inputContentInfo = new InputContentInfo(
+                Uri.parse("content://com.example/path"),
+                new ClipDescription("sample content", new String[]{"image/png"}),
+                Uri.parse("https://example.com"));
+        assertTrue(inputConnection.commitContent(inputContentInfo, null /* opt */));
+        assertTrue(inputConnection.isCommitContentCalled);
     }
 
     private class MockInputConnection implements InputConnection {
@@ -122,6 +132,7 @@
         public boolean isRequestCursorUpdatesCalled;
         public boolean isGetHandlerCalled;
         public boolean isCloseConnectionCalled;
+        public boolean isCommitContentCalled;
 
         public boolean beginBatchEdit() {
             isBeginBatchEditCalled = true;
@@ -246,5 +257,10 @@
         public void closeConnection() {
             isCloseConnectionCalled = true;
         }
+
+        public boolean commitContent(InputContentInfo inputContentInfo, Bundle opts) {
+            isCommitContentCalled = true;
+            return true;
+        }
     }
 }
diff --git a/tests/tests/view/src/android/view/inputmethod/cts/InputContentInfoTest.java b/tests/tests/view/src/android/view/inputmethod/cts/InputContentInfoTest.java
new file mode 100644
index 0000000..30c86bf
--- /dev/null
+++ b/tests/tests/view/src/android/view/inputmethod/cts/InputContentInfoTest.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2016 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.inputmethod.cts;
+
+
+import android.content.ClipDescription;
+import android.net.Uri;
+import android.os.Parcel;
+import android.test.AndroidTestCase;
+import android.view.inputmethod.InputContentInfo;
+
+import java.lang.NullPointerException;
+import java.security.InvalidParameterException;
+
+public class InputContentInfoTest extends AndroidTestCase {
+
+    public void testInputContentInfo() {
+        InputContentInfo info = new InputContentInfo(
+                 Uri.parse("content://com.example/path"),
+                 new ClipDescription("sample content", new String[]{"image/png"}),
+                 Uri.parse("https://example.com"));
+
+        assertEquals(Uri.parse("content://com.example/path"), info.getContentUri());
+        assertEquals(1, info.getDescription().getMimeTypeCount());
+        assertEquals("image/png", info.getDescription().getMimeType(0));
+        assertEquals("sample content", info.getDescription().getLabel());
+        assertEquals(Uri.parse("https://example.com"), info.getLinkUri());
+
+        Parcel p = Parcel.obtain();
+        info.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        InputContentInfo targetInfo = InputContentInfo.CREATOR.createFromParcel(p);
+        p.recycle();
+
+        assertEquals(info.getContentUri(), targetInfo.getContentUri());
+        assertEquals(info.getDescription().getMimeTypeCount(),
+                targetInfo.getDescription().getMimeTypeCount());
+        assertEquals(info.getDescription().getMimeType(0),
+                targetInfo.getDescription().getMimeType(0));
+        assertEquals(info.getDescription().getLabel(), targetInfo.getDescription().getLabel());
+        assertEquals(info.getLinkUri(), targetInfo.getLinkUri());
+    }
+
+    public void testContentUri() {
+        try {
+            InputContentInfo info = new InputContentInfo(
+                    null, new ClipDescription("sample content", new String[]{"image/png"}),
+                    Uri.parse("https://example.com"));
+            fail("InputContentInfo must not accept a null content URI.");
+        } catch (NullPointerException e) {
+            // OK.
+        } catch (Exception e) {
+            fail("Unexpected exception=" + e);
+        }
+
+        try {
+            InputContentInfo info = new InputContentInfo(
+                    Uri.parse("https://example.com"),
+                    new ClipDescription("sample content", new String[]{"image/png"}),
+                    Uri.parse("https://example.com"));
+            fail("InputContentInfo must accept content URI only.");
+        } catch (InvalidParameterException e) {
+            // OK.
+        } catch (Exception e) {
+            fail("Unexpected exception=" + e);
+        }
+    }
+
+    public void testMimeType() {
+        try {
+            InputContentInfo info = new InputContentInfo(
+                     Uri.parse("content://com.example/path"), null,
+                     Uri.parse("https://example.com"));
+            fail("InputContentInfo must not accept a null description.");
+        } catch (NullPointerException e) {
+            // OK.
+        } catch (Exception e) {
+            fail("Unexpected exception=" + e);
+        }
+    }
+
+    public void testLinkUri() {
+        try {
+            InputContentInfo info = new InputContentInfo(
+                     Uri.parse("content://com.example/path"),
+                     new ClipDescription("sample content", new String[]{"image/png"}),
+                     null);
+        } catch (Exception e) {
+            fail("InputContentInfo must accept a null link Uri.");
+        }
+
+        try {
+            InputContentInfo info = new InputContentInfo(
+                     Uri.parse("content://com.example/path"),
+                     new ClipDescription("sample content", new String[]{"image/png"}),
+                     Uri.parse("http://example.com/path"));
+        } catch (Exception e) {
+            fail("InputContentInfo must accept http link Uri.");
+        }
+
+        try {
+            InputContentInfo info = new InputContentInfo(
+                     Uri.parse("content://com.example/path"),
+                     new ClipDescription("sample content", new String[]{"image/png"}),
+                     Uri.parse("https://example.com/path"));
+        } catch (Exception e) {
+            fail("InputContentInfo must accept https link Uri.");
+        }
+
+        try {
+            InputContentInfo info = new InputContentInfo(
+                     Uri.parse("content://com.example/path"),
+                     new ClipDescription("sample content", new String[]{"image/png"}),
+                     Uri.parse("ftp://example.com/path"));
+            fail("InputContentInfo must accept http and https link Uri only.");
+        } catch (InvalidParameterException e) {
+            // OK.
+        } catch (Exception e) {
+            fail("Unexpected exception=" + e);
+        }
+
+        try {
+            InputContentInfo info = new InputContentInfo(
+                     Uri.parse("content://com.example/path"),
+                     new ClipDescription("sample content", new String[]{"image/png"}),
+                     Uri.parse("content://com.example/path"));
+            fail("InputContentInfo must accept http and https link Uri only.");
+        } catch (InvalidParameterException e) {
+            // OK.
+        } catch (Exception e) {
+            fail("Unexpected exception=" + e);
+        }
+    }
+}