Merge "camera2: check image size after it's available" into lmp-mr1-dev
diff --git a/CtsTestCaseList.mk b/CtsTestCaseList.mk
index e8e7485..953a1dd 100644
--- a/CtsTestCaseList.mk
+++ b/CtsTestCaseList.mk
@@ -72,9 +72,12 @@
     CtsHostsideNetworkTestsApp \
     CtsIntentReceiverApp \
     CtsIntentSenderApp \
+    CtsLauncherAppsTests \
+    CtsLauncherAppsTestsSupport \
     CtsManagedProfileApp \
     CtsMonkeyApp \
     CtsMonkeyApp2 \
+    CtsSimpleApp \
     CtsSomeAccessibilityServices \
     CtsThemeDeviceApp \
     TestDeviceSetup \
diff --git a/apps/CameraITS/pymodules/its/caps.py b/apps/CameraITS/pymodules/its/caps.py
index b713db9..24f4e75 100644
--- a/apps/CameraITS/pymodules/its/caps.py
+++ b/apps/CameraITS/pymodules/its/caps.py
@@ -198,7 +198,6 @@
     return props.has_key("android.flash.info.available") and \
            props["android.flash.info.available"] == 1
 
-
 def per_frame_control(props):
     """Returns whether a device supports per frame control
 
@@ -211,6 +210,18 @@
     return props.has_key("android.sync.maxLatency") and \
            props["android.sync.maxLatency"] == 0
 
+def ev_compensation(props):
+    """Returns whether a device supports ev compensation
+
+    Args:
+        props: Camera properties object.
+
+    Return:
+        Boolean.
+    """
+    return props.has_key("android.control.aeCompensationRange") and \
+           props["android.control.aeCompensationRange"] != [0, 0]
+
 class __UnitTest(unittest.TestCase):
     """Run a suite of unit tests on this module.
     """
diff --git a/apps/CameraITS/tests/scene0/test_jitter.py b/apps/CameraITS/tests/scene0/test_jitter.py
index 82e8e38..c519792 100644
--- a/apps/CameraITS/tests/scene0/test_jitter.py
+++ b/apps/CameraITS/tests/scene0/test_jitter.py
@@ -33,7 +33,8 @@
 
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
-        its.caps.skip_unless(its.caps.manual_sensor(props))
+        its.caps.skip_unless(its.caps.manual_sensor(props) and
+                             its.caps.sensor_fusion(props))
 
         req, fmt = its.objects.get_fastest_manual_capture_settings(props)
         caps = cam.do_capture([req]*50, [fmt])
diff --git a/apps/CameraITS/tests/scene0/test_param_sensitivity_burst.py b/apps/CameraITS/tests/scene0/test_param_sensitivity_burst.py
index a6a5214..c3c2147 100644
--- a/apps/CameraITS/tests/scene0/test_param_sensitivity_burst.py
+++ b/apps/CameraITS/tests/scene0/test_param_sensitivity_burst.py
@@ -24,6 +24,7 @@
     """
 
     NUM_STEPS = 3
+    ERROR_TOLERANCE = 0.97 # Allow ISO to be rounded down by 3%
 
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
@@ -41,7 +42,8 @@
         for i,cap in enumerate(caps):
             s_req = sens_list[i]
             s_res = cap["metadata"]["android.sensor.sensitivity"]
-            assert(s_req == s_res)
+            assert(s_req >= s_res)
+            assert(s_res/float(s_req) > ERROR_TOLERANCE)
 
 if __name__ == '__main__':
     main()
diff --git a/apps/CameraITS/tests/scene1/test_ev_compensation_advanced.py b/apps/CameraITS/tests/scene1/test_ev_compensation_advanced.py
index 6341c67..2c311f8 100644
--- a/apps/CameraITS/tests/scene1/test_ev_compensation_advanced.py
+++ b/apps/CameraITS/tests/scene1/test_ev_compensation_advanced.py
@@ -33,7 +33,8 @@
         props = cam.get_camera_properties()
         its.caps.skip_unless(its.caps.manual_sensor(props) and
                              its.caps.manual_post_proc(props) and
-                             its.caps.per_frame_control(props))
+                             its.caps.per_frame_control(props) and
+                             its.caps.ev_compensation(props))
 
         evs = range(-4,5)
         lumas = []
diff --git a/apps/CameraITS/tests/scene1/test_ev_compensation_basic.py b/apps/CameraITS/tests/scene1/test_ev_compensation_basic.py
index 13f318f..30f1df2 100644
--- a/apps/CameraITS/tests/scene1/test_ev_compensation_basic.py
+++ b/apps/CameraITS/tests/scene1/test_ev_compensation_basic.py
@@ -13,6 +13,7 @@
 # limitations under the License.
 
 import its.image
+import its.caps
 import its.device
 import its.objects
 import os.path
@@ -28,6 +29,7 @@
 
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
+        its.caps.skip_unless(its.caps.ev_compensation(props))
 
         evs = range(-4,5)
         lumas = []
diff --git a/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py b/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py
index 49f47a9..efeb665 100644
--- a/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py
+++ b/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py
@@ -81,9 +81,23 @@
     else:
         events, frames = load_data()
 
+    # Sanity check camera timestamps are enclosed by sensor timestamps
+    # This will catch bugs where camera and gyro timestamps go completely out
+    # of sync
+    cam_times = get_cam_times(events["cam"])
+    min_cam_time = min(cam_times) * NSEC_TO_SEC
+    max_cam_time = max(cam_times) * NSEC_TO_SEC
+    gyro_times = [e["time"] for e in events["gyro"]]
+    min_gyro_time = min(gyro_times) * NSEC_TO_SEC
+    max_gyro_time = max(gyro_times) * NSEC_TO_SEC
+    if not (min_cam_time > min_gyro_time and max_cam_time < max_gyro_time):
+        print "Test failed: camera timestamps [%f,%f] " \
+              "are not enclosed by gyro timestamps [%f, %f]" % (
+            min_cam_time, max_cam_time, min_gyro_time, max_gyro_time)
+        assert(0)
+
     # Compute the camera rotation displacements (rad) between each pair of
     # adjacent frames.
-    cam_times = get_cam_times(events["cam"])
     cam_rots = get_cam_rotations(frames)
     if max(abs(cam_rots)) < THRESH_MIN_ROT:
         print "Device wasn't moved enough"
diff --git a/hostsidetests/devicepolicy/app/LauncherTests/Android.mk b/hostsidetests/devicepolicy/app/LauncherTests/Android.mk
new file mode 100644
index 0000000..4517ea2
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/LauncherTests/Android.mk
@@ -0,0 +1,31 @@
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsLauncherAppsTests
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/devicepolicy/app/LauncherTests/AndroidManifest.xml b/hostsidetests/devicepolicy/app/LauncherTests/AndroidManifest.xml
new file mode 100644
index 0000000..a21b8c2
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/LauncherTests/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.cts.launchertests">
+
+    <uses-sdk android:minSdkVersion="21"/>
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="android.test.InstrumentationTestRunner"
+                     android:targetPackage="com.android.cts.launchertests"
+                     android:label="Launcher Apps CTS Tests"/>
+</manifest>
diff --git a/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/LauncherAppsTests.java b/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/LauncherAppsTests.java
new file mode 100644
index 0000000..3d44ecd
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/LauncherAppsTests.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.launchertests;
+
+import android.app.admin.DeviceAdminReceiver;
+import android.app.admin.DevicePolicyManager;
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.Parcel;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.test.InstrumentationTestCase;
+import android.test.InstrumentationTestRunner;
+import android.util.Pair;
+
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.List;
+
+/**
+ * Tests for LauncherApps service
+ */
+public class LauncherAppsTests extends InstrumentationTestCase {
+
+    public static final String SIMPLE_APP_PACKAGE = "com.android.cts.launcherapps.simpleapp";
+
+    public static final String USER_EXTRA = "user_extra";
+    public static final String PACKAGE_EXTRA = "package_extra";
+    public static final String REPLY_EXTRA = "reply_extra";
+
+    public static final int MSG_RESULT = 0;
+    public static final int MSG_CHECK_PACKAGE_ADDED = 1;
+    public static final int MSG_CHECK_PACKAGE_REMOVED = 2;
+    public static final int MSG_CHECK_PACKAGE_CHANGED = 3;
+    public static final int MSG_CHECK_NO_CALLBACK = 4;
+
+    public static final int RESULT_PASS = 1;
+    public static final int RESULT_FAIL = 2;
+    public static final int RESULT_TIMEOUT = 3;
+
+    private LauncherApps mLauncherApps;
+    private UserHandle mUser;
+    private InstrumentationTestRunner mInstrumentation;
+    private Messenger mService;
+    private Connection mConnection;
+    private Result mResult;
+    private Messenger mResultMessenger;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mInstrumentation = (InstrumentationTestRunner) getInstrumentation();
+        Bundle arguments = mInstrumentation.getArguments();
+        UserManager userManager = (UserManager) mInstrumentation.getContext().getSystemService(
+                Context.USER_SERVICE);
+        mUser = getUserHandleArgument(userManager, "testUser", arguments);
+        mLauncherApps = (LauncherApps) mInstrumentation.getContext().getSystemService(
+                Context.LAUNCHER_APPS_SERVICE);
+
+        final Intent intent = new Intent();
+        intent.setComponent(new ComponentName("com.android.cts.launchertests.support",
+                        "com.android.cts.launchertests.support.LauncherCallbackTestsService"));
+
+        mConnection = new Connection();
+        mInstrumentation.getContext().bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+        mConnection.waitForService();
+        mResult = new Result(Looper.getMainLooper());
+        mResultMessenger = new Messenger(mResult);
+    }
+
+    public void testGetActivitiesForUserFails() throws Exception {
+        try {
+            List<LauncherActivityInfo> activities =
+                    mLauncherApps.getActivityList(null, mUser);
+            fail("getActivities for non-profile user failed to throw exception");
+        } catch (SecurityException e) {
+            // Expected.
+        }
+    }
+
+    public void testSimpleAppInstalledForUser() throws Exception {
+        List<LauncherActivityInfo> activities =
+                mLauncherApps.getActivityList(null, mUser);
+        // Check simple app is there.
+        boolean foundSimpleApp = false;
+        for (LauncherActivityInfo activity : activities) {
+            if (activity.getComponentName().getPackageName().equals(
+                    SIMPLE_APP_PACKAGE)) {
+                foundSimpleApp = true;
+            }
+            assertTrue(activity.getUser().equals(mUser));
+        }
+        assertTrue(foundSimpleApp);
+    }
+
+    public void testPackageAddedCallbackForUser() throws Throwable {
+        int result = sendMessageToCallbacksService(MSG_CHECK_PACKAGE_ADDED,
+                mUser, SIMPLE_APP_PACKAGE);
+        assertEquals(RESULT_PASS, result);
+    }
+
+    public void testPackageRemovedCallbackForUser() throws Throwable {
+        int result = sendMessageToCallbacksService(MSG_CHECK_PACKAGE_REMOVED,
+                mUser, SIMPLE_APP_PACKAGE);
+        assertEquals(RESULT_PASS, result);
+    }
+    public void testPackageChangedCallbackForUser() throws Throwable {
+        int result = sendMessageToCallbacksService(MSG_CHECK_PACKAGE_CHANGED,
+                mUser, SIMPLE_APP_PACKAGE);
+        assertEquals(RESULT_PASS, result);
+    }
+
+    public void testNoCallbackForUser() throws Throwable {
+        int result = sendMessageToCallbacksService(MSG_CHECK_NO_CALLBACK,
+                mUser, SIMPLE_APP_PACKAGE);
+        assertEquals(RESULT_PASS, result);
+    }
+
+    public void testLaunchNonExportActivityFails() throws Exception {
+        try {
+            mLauncherApps.startMainActivity(new ComponentName(
+                    SIMPLE_APP_PACKAGE,
+                    SIMPLE_APP_PACKAGE + ".NonExportedActivity"),
+                    mUser, null, null);
+            fail("starting non-exported activity failed to throw exception");
+        } catch (SecurityException e) {
+            // Expected.
+        }
+    }
+
+    public void testLaunchNonExportLauncherFails() throws Exception {
+        try {
+            mLauncherApps.startMainActivity(new ComponentName(
+                    SIMPLE_APP_PACKAGE,
+                    SIMPLE_APP_PACKAGE + ".NonLauncherActivity"),
+                    mUser, null, null);
+            fail("starting non-launcher activity failed to throw exception");
+        } catch (SecurityException e) {
+            // Expected.
+        }
+    }
+
+    public void testLaunchMainActivity() throws Exception {
+        ActivityLaunchedReceiver receiver = new ActivityLaunchedReceiver();
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(ActivityLaunchedReceiver.ACTIVITY_LAUNCHED_ACTION);
+        mInstrumentation.getContext().registerReceiver(receiver, filter);
+        mLauncherApps.startMainActivity(new ComponentName(
+                SIMPLE_APP_PACKAGE,
+                SIMPLE_APP_PACKAGE + ".SimpleActivity"),
+                mUser, null, null);
+        assertEquals(RESULT_PASS, receiver.waitForActivity());
+        mInstrumentation.getContext().unregisterReceiver(receiver);
+    }
+
+    private UserHandle getUserHandleArgument(UserManager userManager, String key,
+            Bundle arguments) throws Exception {
+        String serial = arguments.getString(key);
+        if (serial == null) {
+            return null;
+        }
+        int serialNo = Integer.parseInt(serial);
+        return userManager.getUserForSerialNumber(serialNo);
+    }
+
+    private class Connection implements ServiceConnection {
+        private final Semaphore mSemaphore = new Semaphore(0);
+
+        @Override
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            mService = new Messenger(service);
+            mSemaphore.release();
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName className) {
+            mService = null;
+        }
+
+        public void waitForService() {
+            try {
+                if (mSemaphore.tryAcquire(5, TimeUnit.SECONDS)) {
+                    return;
+                }
+            } catch (InterruptedException e) {
+            }
+            fail("failed to connec to service");
+        }
+    };
+
+    private static class Result extends Handler {
+
+        private final Semaphore mSemaphore = new Semaphore(0);
+        public int result = 0;
+
+        public Result(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (msg.what == MSG_RESULT) {
+                result = msg.arg1;
+                mSemaphore.release();
+            } else {
+                super.handleMessage(msg);
+            }
+        }
+
+        public int waitForResult() {
+            try {
+                if (mSemaphore.tryAcquire(5, TimeUnit.SECONDS)) {
+                    return result;
+                }
+            } catch (InterruptedException e) {
+            }
+            return RESULT_TIMEOUT;
+        }
+    }
+
+    public class ActivityLaunchedReceiver extends BroadcastReceiver {
+        public static final String ACTIVITY_LAUNCHED_ACTION =
+                "com.android.cts.launchertests.LauncherAppsTests.LAUNCHED_ACTION";
+
+        private final Semaphore mSemaphore = new Semaphore(0);
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction().equals(ACTIVITY_LAUNCHED_ACTION)) {
+                mSemaphore.release();
+            }
+        }
+
+        public int waitForActivity() {
+            try {
+                if (mSemaphore.tryAcquire(5, TimeUnit.SECONDS)) {
+                    return RESULT_PASS;
+                }
+            } catch (InterruptedException e) {
+            }
+            return RESULT_TIMEOUT;
+        }
+    }
+
+    private int sendMessageToCallbacksService(int msg, UserHandle user, String packageName)
+            throws Throwable {
+        Bundle params = new Bundle();
+        params.putParcelable(USER_EXTRA, user);
+        params.putString(PACKAGE_EXTRA, packageName);
+
+        Message message = Message.obtain(null, msg, params);
+        message.replyTo = mResultMessenger;
+
+        mService.send(message);
+
+        return mResult.waitForResult();
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/TestActivity.java b/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/TestActivity.java
new file mode 100644
index 0000000..5d62c8f
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/TestActivity.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.launchertests;
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.lang.Override;
+
+/**
+ * A simple activity to install and launch for various users to
+ * test LauncherApps.
+ */
+public class TestActivity extends Activity {
+    public static final String USER_EXTRA = "user_extra";
+    public static final int MSG_RESULT = 0;
+    public static final int RESULT_PASS = 1;
+
+    private static final String TAG = "SimpleActivity";
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        Log.i(TAG, "Created for user " + android.os.Process.myUserHandle());
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/LauncherTestsSupport/Android.mk b/hostsidetests/devicepolicy/app/LauncherTestsSupport/Android.mk
new file mode 100644
index 0000000..2465ef3
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/LauncherTestsSupport/Android.mk
@@ -0,0 +1,31 @@
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsLauncherAppsTestsSupport
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/devicepolicy/app/LauncherTestsSupport/AndroidManifest.xml b/hostsidetests/devicepolicy/app/LauncherTestsSupport/AndroidManifest.xml
new file mode 100644
index 0000000..fbd049f
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/LauncherTestsSupport/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.cts.launchertests.support">
+
+    <uses-sdk android:minSdkVersion="21"/>
+
+    <application>
+        <service android:name=".LauncherCallbackTestsService" >
+            <intent-filter>
+                <action android:name="com.android.cts.launchertests.support.REGISTER_CALLBACK" />
+            </intent-filter>
+        </service>
+    </application>
+</manifest>
diff --git a/hostsidetests/devicepolicy/app/LauncherTestsSupport/src/com/android/cts/launchertests/support/LauncherCallbackTestsService.java b/hostsidetests/devicepolicy/app/LauncherTestsSupport/src/com/android/cts/launchertests/support/LauncherCallbackTestsService.java
new file mode 100644
index 0000000..8d61496
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/LauncherTestsSupport/src/com/android/cts/launchertests/support/LauncherCallbackTestsService.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2014 The Android Open Sour *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.launchertests.support;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.Pair;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Service that registers for LauncherApps callbacks.
+ *
+ * Registering in a service and different process so that
+ * device side code can launch it before running client
+ * side test code.
+ */
+public class LauncherCallbackTestsService extends Service {
+
+    public static final String USER_EXTRA = "user_extra";
+    public static final String PACKAGE_EXTRA = "package_extra";
+
+    public static final int MSG_RESULT = 0;
+    public static final int MSG_CHECK_PACKAGE_ADDED = 1;
+    public static final int MSG_CHECK_PACKAGE_REMOVED = 2;
+    public static final int MSG_CHECK_PACKAGE_CHANGED = 3;
+    public static final int MSG_CHECK_NO_CALLBACK = 4;
+
+    public static final int RESULT_PASS = 1;
+    public static final int RESULT_FAIL = 2;
+
+    private static final String TAG = "LauncherCallbackTests";
+
+    private static List<Pair<String, UserHandle>> mPackagesAdded
+            = new ArrayList<Pair<String, UserHandle>>();
+    private static List<Pair<String, UserHandle>> mPackagesRemoved
+            = new ArrayList<Pair<String, UserHandle>>();
+    private static List<Pair<String, UserHandle>> mPackagesChanged
+            = new ArrayList<Pair<String, UserHandle>>();
+    private static Object mPackagesLock = new Object();
+
+    private TestCallback mCallback;
+    private final Messenger mMessenger = new Messenger(new CheckHandler());
+
+    class CheckHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            Bundle params = null;
+            if (msg.obj instanceof Bundle) {
+                params = (Bundle) (msg.obj);
+            }
+            try {
+                switch (msg.what) {
+                    case MSG_CHECK_PACKAGE_ADDED: {
+                        boolean exists = eventExists(params, mPackagesAdded);
+                        teardown();
+                        msg.replyTo.send(Message.obtain(null, MSG_RESULT,
+                                        exists ? RESULT_PASS : RESULT_FAIL, 0));
+                        break;
+                    }
+                    case MSG_CHECK_PACKAGE_REMOVED: {
+                        boolean exists = eventExists(params, mPackagesRemoved);
+                        teardown();
+                        msg.replyTo.send(Message.obtain(null, MSG_RESULT,
+                                        exists ? RESULT_PASS : RESULT_FAIL, 0));
+                        break;
+                    }
+                    case MSG_CHECK_PACKAGE_CHANGED: {
+                        boolean exists = eventExists(params, mPackagesChanged);
+                        teardown();
+                        msg.replyTo.send(Message.obtain(null, MSG_RESULT,
+                                        exists ? RESULT_PASS : RESULT_FAIL, 0));
+                        break;
+                    }
+                    case MSG_CHECK_NO_CALLBACK: {
+                        boolean exists = eventExists(params, mPackagesAdded)
+                                || eventExists(params, mPackagesRemoved)
+                                || eventExists(params, mPackagesChanged);
+                        teardown();
+                        msg.replyTo.send(Message.obtain(null, MSG_RESULT,
+                                        exists ? RESULT_FAIL : RESULT_PASS, 0));
+                        break;
+                    }
+                    default:
+                        super.handleMessage(msg);
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to report test status");
+            }
+        }
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        if (intent == null) {
+            return START_NOT_STICKY;
+        }
+        if ("com.android.cts.launchertests.support.REGISTER_CALLBACK".equals(intent.getAction())) {
+            setup();
+        }
+        return START_STICKY;
+    }
+
+    private void setup() {
+        LauncherApps launcherApps = (LauncherApps) getSystemService(
+                Context.LAUNCHER_APPS_SERVICE);
+        synchronized (mPackagesLock) {
+            mPackagesAdded.clear();
+            mPackagesRemoved.clear();
+            mPackagesChanged.clear();
+            if (mCallback != null) {
+                launcherApps.unregisterCallback(mCallback);
+            }
+            mCallback = new TestCallback();
+            launcherApps.registerCallback(mCallback);
+        }
+    }
+
+    private void teardown() {
+        LauncherApps launcherApps = (LauncherApps) getSystemService(
+                Context.LAUNCHER_APPS_SERVICE);
+        synchronized (mPackagesLock) {
+            if (mCallback != null) {
+                launcherApps.unregisterCallback(mCallback);
+                mCallback = null;
+            }
+            mPackagesAdded.clear();
+            mPackagesRemoved.clear();
+            mPackagesChanged.clear();
+        }
+    }
+
+    private boolean eventExists(Bundle params, List<Pair<String, UserHandle>> events) {
+        UserHandle user = params.getParcelable(USER_EXTRA);
+        String packageName = params.getString(PACKAGE_EXTRA);
+        synchronized (mPackagesLock) {
+            if (events != null) {
+                for (Pair<String, UserHandle> added : events) {
+                    if (added.first.equals(packageName) && added.second.equals(user)) {
+                        Log.i(TAG, "Event exists " + packageName + " for user " + user);
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mMessenger.getBinder();
+    }
+
+    private class TestCallback extends LauncherApps.Callback {
+        public void onPackageRemoved(String packageName, UserHandle user) {
+            synchronized (mPackagesLock) {
+                mPackagesRemoved.add(new Pair<String, UserHandle>(packageName, user));
+            }
+        }
+
+        public void onPackageAdded(String packageName, UserHandle user) {
+            synchronized (mPackagesLock) {
+                mPackagesAdded.add(new Pair<String, UserHandle>(packageName, user));
+            }
+        }
+
+        public void onPackageChanged(String packageName, UserHandle user) {
+            synchronized (mPackagesLock) {
+                mPackagesChanged.add(new Pair<String, UserHandle>(packageName, user));
+            }
+        }
+
+        public void onPackagesAvailable(String[] packageNames, UserHandle user,
+                boolean replacing) {
+        }
+
+        public void onPackagesUnavailable(String[] packageNames, UserHandle user,
+                boolean replacing) {
+        }
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/SimpleApp/Android.mk b/hostsidetests/devicepolicy/app/SimpleApp/Android.mk
new file mode 100644
index 0000000..eae0a4f
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/SimpleApp/Android.mk
@@ -0,0 +1,31 @@
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Don't include this package in any target.
+LOCAL_MODULE_TAGS := optional
+
+# When built, explicitly put it in the data partition.
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsSimpleApp
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml
new file mode 100644
index 0000000..848317c
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.cts.launcherapps.simpleapp">
+
+    <application>
+        <activity android:name=".SimpleActivity" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity android:name=".NonExportedActivity">
+            android:exported="false">
+        </activity>
+        <activity android:name=".NonLauncherActivity">
+            android:exported="true">
+        </activity>
+    </application>
+
+</manifest>
+
diff --git a/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/NonExportedActivity.java b/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/NonExportedActivity.java
new file mode 100644
index 0000000..4801830
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/NonExportedActivity.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.launcherapps.simpleapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.lang.Override;
+
+/**
+ * A simple activity that isn't exported, shouldn't be launchable
+ * by LauncherApps.
+ */
+public class NonExportedActivity extends Activity {
+
+    private static final String TAG = "NonExportedActivity";
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        Log.i(TAG, "Created for user " + android.os.Process.myUserHandle());
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/NonLauncherActivity.java b/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/NonLauncherActivity.java
new file mode 100644
index 0000000..4809020
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/NonLauncherActivity.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.launcherapps.simpleapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.lang.Override;
+
+/**
+ * A simple activity that isn't main / category launcher, shouldn't be launchable
+ * by LauncherApps.
+ */
+public class NonLauncherActivity extends Activity {
+
+    private static final String TAG = "NonLauncherActivity";
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        Log.i(TAG, "Created for user " + android.os.Process.myUserHandle());
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleActivity.java b/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleActivity.java
new file mode 100644
index 0000000..1d30335
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleActivity.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.launcherapps.simpleapp;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.lang.Override;
+
+/**
+ * A simple activity to install for various users to test LauncherApps.
+ */
+public class SimpleActivity extends Activity {
+    public static String ACTIVITY_LAUNCHED_ACTION =
+            "com.android.cts.launchertests.LauncherAppsTests.LAUNCHED_ACTION";
+
+    private static final String TAG = "SimpleActivity";
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        Log.i(TAG, "Created for user " + android.os.Process.myUserHandle());
+    }
+
+    public void onStart() {
+        super.onStart();
+        Intent reply = new Intent();
+        reply.setAction(ACTIVITY_LAUNCHED_ACTION);
+        sendBroadcast(reply);
+    }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
index cd85188..c35246d 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
@@ -47,6 +47,11 @@
  */
 public class BaseDevicePolicyTest extends DeviceTestCase implements IBuildReceiver {
 
+    protected static final String MANAGED_PROFILE_PKG = "com.android.cts.managedprofile";
+    protected static final String MANAGED_PROFILE_APK = "CtsManagedProfileApp.apk";
+    protected static final String ADMIN_RECEIVER_TEST_CLASS =
+            MANAGED_PROFILE_PKG + ".BaseManagedProfileTest$BasicAdminReceiver";
+
     private static final String RUNNER = "android.test.InstrumentationTestRunner";
 
     private static final String[] REQUIRED_DEVICE_FEATURES = new String[] {
@@ -72,6 +77,7 @@
 
     protected void installApp(String fileName)
             throws FileNotFoundException, DeviceNotAvailableException {
+        CLog.logAndDisplay(LogLevel.INFO, "Installing app " + fileName);
         String installResult = getDevice().installPackage(mCtsBuild.getTestApp(fileName), true);
         assertNull(String.format("Failed to install %s, Reason: %s", fileName, installResult),
                 installResult);
@@ -93,12 +99,15 @@
     }
 
     /** Initializes the user with the given id. This is required so that apps can run on it. */
-    protected void startUser(int userId) throws DeviceNotAvailableException {
+    protected void startUser(int userId) throws Exception {
         String command = "am start-user " + userId;
+        CLog.logAndDisplay(LogLevel.INFO, "Starting command " + command);
         String commandOutput = getDevice().executeShellCommand(command);
         CLog.logAndDisplay(LogLevel.INFO, "Output for command " + command + ": " + commandOutput);
         assertTrue(commandOutput + " expected to start with \"Success:\"",
                 commandOutput.startsWith("Success:"));
+        // Wait 60 seconds for intents generated to be handled.
+        Thread.sleep(60 * 1000);
     }
 
     protected int getMaxNumberOfUsersSupported() throws DeviceNotAvailableException {
@@ -137,10 +146,13 @@
         return users;
     }
 
-    protected void removeUser(int userId) throws DeviceNotAvailableException  {
+    protected void removeUser(int userId) throws Exception  {
         String removeUserCommand = "pm remove-user " + userId;
+        CLog.logAndDisplay(LogLevel.INFO, "starting command " + removeUserCommand);
         CLog.logAndDisplay(LogLevel.INFO, "Output for command " + removeUserCommand + ": "
                 + getDevice().executeShellCommand(removeUserCommand));
+        // Wait 60 seconds for user to finish being removed.
+        Thread.sleep(60 * 1000);
     }
 
     /** Returns true if the specified tests passed. Tests are run as user owner. */
@@ -166,12 +178,19 @@
         return runDeviceTests(pkgName, testClassName, testMethodName, userId);
     }
 
-    private boolean runDeviceTests(String pkgName, @Nullable String testClassName,
+    protected boolean runDeviceTests(String pkgName, @Nullable String testClassName,
             @Nullable String testMethodName, @Nullable Integer userId)
-                    throws DeviceNotAvailableException {
-        TestRunResult runResult = (userId == null)
+            throws DeviceNotAvailableException {
+        return runDeviceTests(pkgName, testClassName, testMethodName, userId, /*params*/ null);
+    }
+
+    protected boolean runDeviceTests(String pkgName, @Nullable String testClassName,
+            @Nullable String testMethodName, @Nullable Integer userId, @Nullable String params)
+                   throws DeviceNotAvailableException {
+        TestRunResult runResult = (userId == null && params == null)
                 ? doRunTests(pkgName, testClassName, testMethodName)
-                : doRunTestsAsUser(pkgName, testClassName, testMethodName, userId);
+                : doRunTestsAsUser(pkgName, testClassName, testMethodName,
+                        userId != null ? userId : 0, params != null ? params : "");
         printTestResult(runResult);
         return !runResult.hasFailedTests() && runResult.getNumTestsInState(TestStatus.PASSED) > 0;
     }
@@ -194,7 +213,7 @@
     }
 
     private TestRunResult doRunTestsAsUser(String pkgName, @Nullable String testClassName,
-            @Nullable String testMethodName, int userId)
+            @Nullable String testMethodName, int userId, String params)
             throws DeviceNotAvailableException {
         // TODO: move this to RemoteAndroidTestRunner once it supports users. Should be straight
         // forward to add a RemoteAndroidTestRunner.setUser(userId) method. Then we can merge both
@@ -206,8 +225,8 @@
                 testsToRun.append("#" + testMethodName);
             }
         }
-        String command = "am instrument --user " + userId + " -w -r " + testsToRun + " "
-                + pkgName + "/" + RUNNER;
+        String command = "am instrument --user " + userId + " " + params + " -w -r "
+                + testsToRun + " " + pkgName + "/" + RUNNER;
         CLog.i("Running " + command);
 
         CollectingTestListener listener = new CollectingTestListener();
@@ -255,4 +274,63 @@
         }
         return true;
     }
+
+    protected int createUser() throws Exception {
+        String command ="pm create-user TestUser_"+ System.currentTimeMillis();
+        CLog.logAndDisplay(LogLevel.INFO, "Starting command " + command);
+        String commandOutput = getDevice().executeShellCommand(command);
+        CLog.logAndDisplay(LogLevel.INFO, "Output for command " + command + ": " + commandOutput);
+
+        // Extract the id of the new user.
+        String[] tokens = commandOutput.split("\\s+");
+        assertTrue(tokens.length > 0);
+        assertEquals("Success:", tokens[0]);
+        // Wait 60 seconds for intents generated to be handled.
+        Thread.sleep(60 * 1000);
+        return Integer.parseInt(tokens[tokens.length-1]);
+    }
+
+    protected int createManagedProfile() throws DeviceNotAvailableException {
+        String command =
+                "pm create-user --profileOf 0 --managed TestProfile_" + System.currentTimeMillis();
+        CLog.logAndDisplay(LogLevel.INFO, "Starting command " + command);
+        String commandOutput = getDevice().executeShellCommand(command);
+        CLog.logAndDisplay(LogLevel.INFO, "Output for command " + command + ": " + commandOutput);
+
+        // Extract the id of the new user.
+        String[] tokens = commandOutput.split("\\s+");
+        assertTrue(commandOutput + " expected to have format \"Success: {USER_ID}\"",
+                tokens.length > 0);
+        assertEquals(commandOutput, "Success:", tokens[0]);
+        return Integer.parseInt(tokens[tokens.length-1]);
+    }
+
+
+    protected int getUserSerialNumber(int userId) throws DeviceNotAvailableException{
+        // dumpsys user return lines like "UserInfo{0:Owner:13} serialNo=0"
+        String commandOutput = getDevice().executeShellCommand("dumpsys user");
+        String[] tokens = commandOutput.split("\\n");
+        for (String token : tokens) {
+            token = token.trim();
+            if (token.contains("UserInfo{" + userId + ":")) {
+                String[] split = token.split("serialNo=");
+                assertTrue(split.length == 2);
+                int serialNumber = Integer.parseInt(split[1]);
+                CLog.logAndDisplay(LogLevel.INFO, "Serial number of user " + userId + ": "
+                        + serialNumber);
+                return serialNumber;
+            }
+        }
+        fail("Couldn't find user " + userId);
+        return -1;
+    }
+
+    protected void setProfileOwner(String componentName, int userId)
+            throws DeviceNotAvailableException {
+        String command = "dpm set-profile-owner '" + componentName + "' " + userId;
+        String commandOutput = getDevice().executeShellCommand(command);
+        CLog.logAndDisplay(LogLevel.INFO, "Output for command " + command + ": " + commandOutput);
+        assertTrue(commandOutput + " expected to start with \"Success:\"",
+                commandOutput.startsWith("Success:"));
+    }
 }
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseLauncherAppsTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseLauncherAppsTest.java
new file mode 100644
index 0000000..dec8bd2
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseLauncherAppsTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.devicepolicy;
+
+import com.android.ddmlib.Log.LogLevel;
+import com.android.tradefed.log.LogUtil.CLog;
+
+/**
+ * Common code for the various LauncherApps tests.
+ */
+public class BaseLauncherAppsTest extends BaseDevicePolicyTest {
+
+    protected static final String SIMPLE_APP_PKG = "com.android.cts.launcherapps.simpleapp";
+    protected static final String SIMPLE_APP_APK = "CtsSimpleApp.apk";
+    protected static final String LAUNCHER_TESTS_PKG = "com.android.cts.launchertests";
+    protected static final String LAUNCHER_TESTS_CLASS = LAUNCHER_TESTS_PKG + ".LauncherAppsTests";
+    private static final String LAUNCHER_TESTS_APK = "CtsLauncherAppsTests.apk";
+    private static final String LAUNCHER_TESTS_SUPPORT_PKG =
+            "com.android.cts.launchertests.support";
+    private static final String LAUNCHER_TESTS_SUPPORT_APK = "CtsLauncherAppsTestsSupport.apk";
+
+    protected void installTestApps() throws Exception {
+        uninstallTestApps();
+        installApp(LAUNCHER_TESTS_APK);
+        installApp(LAUNCHER_TESTS_SUPPORT_APK);
+    }
+
+    protected void uninstallTestApps() throws Exception {
+        getDevice().uninstallPackage(LAUNCHER_TESTS_PKG);
+        getDevice().uninstallPackage(LAUNCHER_TESTS_SUPPORT_PKG);
+        getDevice().uninstallPackage(SIMPLE_APP_PKG);
+    }
+
+    protected void removeTestUsers() throws Exception {
+        for (int userId : listUsers()) {
+            if (userId != 0) {
+                removeUser(userId);
+            }
+        }
+    }
+
+    protected void startCallbackService() throws Exception {
+        String command = "am startservice --user 0 "
+                + "-a " + LAUNCHER_TESTS_SUPPORT_PKG + ".REGISTER_CALLBACK "
+                + LAUNCHER_TESTS_SUPPORT_PKG + "/.LauncherCallbackTestsService";
+        CLog.logAndDisplay(LogLevel.INFO, "Output for command " + command + ": "
+              + getDevice().executeShellCommand(command));
+
+    }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsMultiUserTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsMultiUserTest.java
new file mode 100644
index 0000000..0af38a4
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsMultiUserTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.devicepolicy;
+
+import com.android.ddmlib.Log.LogLevel;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil.CLog;
+
+/**
+ * Set of tests for LauncherApps attempting to access a non-profiles
+ * apps.
+ */
+public class LauncherAppsMultiUserTest extends BaseLauncherAppsTest {
+
+    private int mSecondaryUserId;
+    private int mSecondaryUserSerialNumber;
+
+    private boolean mMultiUserSupported;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        // We need multi user to be supported in order to create a secondary user
+        // and api level 21 to support LauncherApps
+        mMultiUserSupported = getMaxNumberOfUsersSupported() > 1 && getDevice().getApiLevel() >= 21;
+
+        if (mMultiUserSupported) {
+            removeTestUsers();
+            installTestApps();
+            // Create a secondary user.
+            mSecondaryUserId = createUser();
+            mSecondaryUserSerialNumber = getUserSerialNumber(mSecondaryUserId);
+            startUser(mSecondaryUserId);
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (mMultiUserSupported) {
+            removeUser(mSecondaryUserId);
+            uninstallTestApps();
+        }
+        super.tearDown();
+    }
+
+    public void testGetActivitiesForNonProfileFails() throws Exception {
+        if (!mMultiUserSupported) {
+            return;
+        }
+        installApp(SIMPLE_APP_APK);
+        try {
+            assertTrue(runDeviceTests(LAUNCHER_TESTS_PKG,
+                    LAUNCHER_TESTS_CLASS,
+                    "testGetActivitiesForUserFails",
+                            0, "-e testUser " + mSecondaryUserSerialNumber));
+        } finally {
+            getDevice().uninstallPackage(SIMPLE_APP_PKG);
+        }
+    }
+
+    public void testNoLauncherCallbackPackageAddedSecondaryUser() throws Exception {
+        if (!mMultiUserSupported) {
+            return;
+        }
+        startCallbackService();
+        installApp(SIMPLE_APP_APK);
+        try {
+            assertTrue(runDeviceTests(LAUNCHER_TESTS_PKG,
+                    LAUNCHER_TESTS_CLASS,
+                            "testNoCallbackForUser",
+                            0, "-e testUser " + mSecondaryUserSerialNumber));
+        } finally {
+            getDevice().uninstallPackage(SIMPLE_APP_PKG);
+        }
+    }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsProfileTest.java
new file mode 100644
index 0000000..f8c2e7d
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsProfileTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.devicepolicy;
+
+import com.android.ddmlib.Log.LogLevel;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil.CLog;
+
+/**
+ * Set of tests for LauncherApps with managed profiles.
+ */
+public class LauncherAppsProfileTest extends BaseLauncherAppsTest {
+
+    private int mProfileUserId;
+    private int mProfileSerialNumber;
+    private int mMainUserSerialNumber;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        // We need multi user to be supported in order to create a profile of the user owner.
+        mHasFeature = mHasFeature && (getMaxNumberOfUsersSupported() > 1);
+
+        if (mHasFeature) {
+            removeTestUsers();
+            installTestApps();
+            // Create a managed profile
+            mProfileUserId = createManagedProfile();
+            installApp(MANAGED_PROFILE_APK);
+            setProfileOwner(MANAGED_PROFILE_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mProfileUserId);
+            mProfileSerialNumber = getUserSerialNumber(mProfileUserId);
+            mMainUserSerialNumber = getUserSerialNumber(0);
+            startUser(mProfileUserId);
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (mHasFeature) {
+            removeUser(mProfileUserId);
+            uninstallTestApps();
+            getDevice().uninstallPackage(MANAGED_PROFILE_PKG);
+        }
+        super.tearDown();
+    }
+
+    public void testGetActivitiesWithProfile() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        // Install app for all users.
+        installApp(SIMPLE_APP_APK);
+        try {
+            // Run tests to check SimpleApp exists in both profile and main user.
+            assertTrue(runDeviceTests(LAUNCHER_TESTS_PKG,
+                    LAUNCHER_TESTS_CLASS,
+                    "testSimpleAppInstalledForUser",
+                            0, "-e testUser " + mProfileSerialNumber));
+            assertTrue(runDeviceTests(LAUNCHER_TESTS_PKG,
+                    LAUNCHER_TESTS_PKG + ".LauncherAppsTests", "testSimpleAppInstalledForUser",
+                            0, "-e testUser " + mMainUserSerialNumber));
+        } finally {
+            getDevice().uninstallPackage(SIMPLE_APP_PKG);
+        }
+    }
+
+    public void testLauncherCallbackPackageAddedProfile() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        startCallbackService();
+        installApp(SIMPLE_APP_APK);
+        try {
+            assertTrue(runDeviceTests(LAUNCHER_TESTS_PKG,
+                    LAUNCHER_TESTS_CLASS,
+                            "testPackageAddedCallbackForUser",
+                            0, "-e testUser " + mProfileSerialNumber));
+        } finally {
+            getDevice().uninstallPackage(SIMPLE_APP_PKG);
+        }
+    }
+
+    public void testLauncherCallbackPackageRemovedProfile() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        installApp(SIMPLE_APP_APK);
+        try {
+            startCallbackService();
+            getDevice().uninstallPackage(SIMPLE_APP_PKG);
+            assertTrue(runDeviceTests(LAUNCHER_TESTS_PKG,
+                    LAUNCHER_TESTS_CLASS,
+                            "testPackageRemovedCallbackForUser",
+                            0, "-e testUser " + mProfileSerialNumber));
+        } finally {
+            getDevice().uninstallPackage(SIMPLE_APP_PKG);
+        }
+    }
+
+    public void testLauncherCallbackPackageChangedProfile() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        installApp(SIMPLE_APP_APK);
+        try {
+            startCallbackService();
+            installApp(SIMPLE_APP_APK);
+            assertTrue(runDeviceTests(LAUNCHER_TESTS_PKG,
+                    LAUNCHER_TESTS_CLASS,
+                            "testPackageChangedCallbackForUser",
+                            0, "-e testUser " + mProfileSerialNumber));
+        } finally {
+            getDevice().uninstallPackage(SIMPLE_APP_PKG);
+        }
+    }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsSingleUserTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsSingleUserTest.java
new file mode 100644
index 0000000..32be962
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsSingleUserTest.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.devicepolicy;
+
+import com.android.ddmlib.Log.LogLevel;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil.CLog;
+
+/**
+ * Set of tests for LauncherApps with managed profiles.
+ */
+public class LauncherAppsSingleUserTest extends BaseLauncherAppsTest {
+
+    private boolean mHasLauncherApps;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mHasLauncherApps = getDevice().getApiLevel() >= 21;
+
+        if (mHasLauncherApps) {
+            installTestApps();
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (mHasLauncherApps) {
+            uninstallTestApps();
+        }
+        super.tearDown();
+    }
+
+    public void testInstallAppMainUser() throws Exception {
+        if (!mHasLauncherApps) {
+            return;
+        }
+        installApp(SIMPLE_APP_APK);
+        try {
+            int serialNumber = getUserSerialNumber(0);
+            assertTrue(runDeviceTests(LAUNCHER_TESTS_PKG,
+                    LAUNCHER_TESTS_CLASS, "testSimpleAppInstalledForUser",
+                            0, "-e testUser " + serialNumber));
+        } finally {
+            getDevice().uninstallPackage(SIMPLE_APP_PKG);
+        }
+    }
+
+    public void testLauncherCallbackPackageAddedMainUser() throws Exception {
+        if (!mHasLauncherApps) {
+            return;
+        }
+        startCallbackService();
+        installApp(SIMPLE_APP_APK);
+        try {
+            int serialNumber = getUserSerialNumber(0);
+            assertTrue(runDeviceTests(LAUNCHER_TESTS_PKG,
+                    LAUNCHER_TESTS_CLASS,
+                            "testPackageAddedCallbackForUser",
+                            0, "-e testUser " + serialNumber));
+        } finally {
+            getDevice().uninstallPackage(SIMPLE_APP_PKG);
+        }
+    }
+
+    public void testLauncherCallbackPackageRemovedMainUser() throws Exception {
+        if (!mHasLauncherApps) {
+            return;
+        }
+        installApp(SIMPLE_APP_APK);
+        try {
+            startCallbackService();
+            int serialNumber = getUserSerialNumber(0);
+            getDevice().uninstallPackage(SIMPLE_APP_PKG);
+            assertTrue(runDeviceTests(LAUNCHER_TESTS_PKG,
+                    LAUNCHER_TESTS_CLASS,
+                            "testPackageRemovedCallbackForUser",
+                            0, "-e testUser " + serialNumber));
+        } finally {
+            getDevice().uninstallPackage(SIMPLE_APP_PKG);
+        }
+    }
+
+    public void testLauncherCallbackPackageChangedMainUser() throws Exception {
+        if (!mHasLauncherApps) {
+            return;
+        }
+        installApp(SIMPLE_APP_APK);
+        try {
+            startCallbackService();
+            int serialNumber = getUserSerialNumber(0);
+            installApp(SIMPLE_APP_APK);
+            assertTrue(runDeviceTests(LAUNCHER_TESTS_PKG,
+                    LAUNCHER_TESTS_CLASS,
+                            "testPackageChangedCallbackForUser",
+                            0, "-e testUser " + serialNumber));
+        } finally {
+            getDevice().uninstallPackage(SIMPLE_APP_PKG);
+        }
+    }
+
+    public void testLauncherNonExportedAppFails() throws Exception {
+        if (!mHasLauncherApps) {
+            return;
+        }
+        installApp(SIMPLE_APP_APK);
+        try {
+            int serialNumber = getUserSerialNumber(0);
+            assertTrue(runDeviceTests(LAUNCHER_TESTS_PKG,
+                    LAUNCHER_TESTS_CLASS, "testLaunchNonExportActivityFails",
+                            0, "-e testUser " + serialNumber));
+        } finally {
+            getDevice().uninstallPackage(SIMPLE_APP_PKG);
+        }
+    }
+
+    public void testLaunchNonExportActivityFails() throws Exception {
+        if (!mHasLauncherApps) {
+            return;
+        }
+        installApp(SIMPLE_APP_APK);
+        try {
+            int serialNumber = getUserSerialNumber(0);
+            assertTrue(runDeviceTests(LAUNCHER_TESTS_PKG,
+                    LAUNCHER_TESTS_CLASS, "testLaunchNonExportLauncherFails",
+                            0, "-e testUser " + serialNumber));
+        } finally {
+            getDevice().uninstallPackage(SIMPLE_APP_PKG);
+        }
+    }
+
+    public void testLaunchMainActivity() throws Exception {
+        if (!mHasLauncherApps) {
+            return;
+        }
+        installApp(SIMPLE_APP_APK);
+        try {
+            int serialNumber = getUserSerialNumber(0);
+            assertTrue(runDeviceTests(LAUNCHER_TESTS_PKG,
+                    LAUNCHER_TESTS_CLASS, "testLaunchMainActivity",
+                            0, "-e testUser " + serialNumber));
+        } finally {
+            getDevice().uninstallPackage(SIMPLE_APP_PKG);
+        }
+    }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
index d829314..2ef7cad 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
@@ -245,27 +245,4 @@
                 "Output for command " + adbCommand + ": " + commandOutput);
         return commandOutput;
     }
-
-    private int createManagedProfile() throws DeviceNotAvailableException {
-        String command =
-                "pm create-user --profileOf 0 --managed TestProfile_" + System.currentTimeMillis();
-        String commandOutput = getDevice().executeShellCommand(command);
-        CLog.logAndDisplay(LogLevel.INFO, "Output for command " + command + ": " + commandOutput);
-
-        // Extract the id of the new user.
-        String[] tokens = commandOutput.split("\\s+");
-        assertTrue(commandOutput + " expected to have format \"Success: {USER_ID}\"",
-                tokens.length > 0);
-        assertEquals(commandOutput, "Success:", tokens[0]);
-        return Integer.parseInt(tokens[tokens.length-1]);
-    }
-
-    private void setProfileOwner(String componentName, int userId)
-            throws DeviceNotAvailableException {
-        String command = "dpm set-profile-owner '" + componentName + "' " + userId;
-        String commandOutput = getDevice().executeShellCommand(command);
-        CLog.logAndDisplay(LogLevel.INFO, "Output for command " + command + ": " + commandOutput);
-        assertTrue(commandOutput + " expected to start with \"Success:\"",
-                commandOutput.startsWith("Success:"));
-    }
 }
diff --git a/libs/deviceutil/src/android/cts/util/MediaUtils.java b/libs/deviceutil/src/android/cts/util/MediaUtils.java
index eab4808..20153c5 100644
--- a/libs/deviceutil/src/android/cts/util/MediaUtils.java
+++ b/libs/deviceutil/src/android/cts/util/MediaUtils.java
@@ -37,14 +37,13 @@
 
     private static final MediaCodecList sMCL = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
 
-
     /**
-     * Finds test name (heuristically) and prints out standard skip message.
+     * Returns the test name (heuristically).
      *
      * Since it uses heuristics, this method has only been verified for media
-     * tests. This centralizes the way to signal a skipped test.
+     * tests. This centralizes the way to signal errors during a test.
      */
-    public static void skipTest(String tag, String reason) {
+    public static String getTestName() {
         int bestScore = -1;
         String testName = "test???";
         Map<Thread, StackTraceElement[]> traces = Thread.getAllStackTraces();
@@ -110,8 +109,17 @@
                 }
             }
         }
+        return testName;
+    }
 
-        Log.i(tag, "SKIPPING " + testName + "(): " + reason);
+    /**
+     * Finds test name (heuristically) and prints out standard skip message.
+     *
+     * Since it uses heuristics, this method has only been verified for media
+     * tests. This centralizes the way to signal a skipped test.
+     */
+    public static void skipTest(String tag, String reason) {
+        Log.i(tag, "SKIPPING " + getTestName() + "(): " + reason);
     }
 
     /**
diff --git a/tests/expectations/knownfailures.txt b/tests/expectations/knownfailures.txt
index c71eed2..3b2e3f0 100644
--- a/tests/expectations/knownfailures.txt
+++ b/tests/expectations/knownfailures.txt
@@ -131,6 +131,24 @@
   bug: 17508787
 },
 {
+  description: "New tests recently added for Android Enterprise. To be moved out of CTS-staging as soon as they show that they are stable",
+  names: [
+    "com.android.cts.devicepolicy.LauncherAppsMultiUserTest#testGetActivitiesForNonProfileFails",
+    "com.android.cts.devicepolicy.LauncherAppsMultiUserTest#testNoLauncherCallbackPackageAddedSecondaryUser",
+    "com.android.cts.devicepolicy.LauncherAppsProfileTest#testGetActivitiesWithProfile",
+    "com.android.cts.devicepolicy.LauncherAppsProfileTest#testLauncherCallbackPackageAddedProfile",
+    "com.android.cts.devicepolicy.LauncherAppsProfileTest#testLauncherCallbackPackageRemovedProfile",
+    "com.android.cts.devicepolicy.LauncherAppsProfileTest#testLauncherCallbackPackageChangedProfile",
+    "com.android.cts.devicepolicy.LauncherAppsSingleUserTest#testInstallAppMainUser",
+    "com.android.cts.devicepolicy.LauncherAppsSingleUserTest#testLauncherCallbackPackageAddedMainUser",
+    "com.android.cts.devicepolicy.LauncherAppsSingleUserTest#testLauncherCallbackPackageRemovedMainUser",
+    "com.android.cts.devicepolicy.LauncherAppsSingleUserTest#testLauncherCallbackPackageChangedMainUser",
+    "com.android.cts.devicepolicy.LauncherAppsSingleUserTest#testLauncherNonExportedAppFails",
+    "com.android.cts.devicepolicy.LauncherAppsSingleUserTest#testLaunchNonExportActivityFails",
+    "com.android.cts.devicepolicy.LauncherAppsSingleUserTest#testLaunchMainActivity"
+  ]
+},
+{
   description: "This test failed on devices that use effect off loading. In addition it uses hidden apis",
   names: [
     "android.media.cts.AudioEffectTest#test1_1ConstructorFromUuid"
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
index 250601d..02dfccd 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
@@ -513,7 +513,8 @@
                     if (arrayContains(actualCapabilities,
                             CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) {
                         assertTrue("MANUAL_SENSOR capability, need positive min frame duration for"
-                                + "format " + format,
+                                + "format " + format + " for size " + size + " minDuration " +
+                                minDuration,
                                 minDuration > 0);
                     } else {
                         assertTrue("Need non-negative min frame duration for format " + format,
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/StillCaptureTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/StillCaptureTest.java
index c6da122..2554b17 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/StillCaptureTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/StillCaptureTest.java
@@ -71,6 +71,7 @@
     private static final Location sTestLocation0 = new Location(LocationManager.GPS_PROVIDER);
     private static final Location sTestLocation1 = new Location(LocationManager.GPS_PROVIDER);
     private static final Location sTestLocation2 = new Location(LocationManager.NETWORK_PROVIDER);
+    private static final int RELAXED_CAPTURE_IMAGE_TIMEOUT_MS = CAPTURE_IMAGE_TIMEOUT_MS + 1000;
     static {
         sTestLocation0.setTime(1199145600L);
         sTestLocation0.setLatitude(37.736071);
@@ -637,7 +638,8 @@
                 prepareStillCaptureAndStartPreview(previewRequest, stillRequest, previewSz,
                         stillSz, resultListener, imageListener);
                 mSession.capture(stillRequest.build(), resultListener, mHandler);
-                Image image = imageListener.getImage(CAPTURE_IMAGE_TIMEOUT_MS);
+                Image image = imageListener.getImage((mStaticInfo.isHardwareLevelLegacy()) ?
+                        RELAXED_CAPTURE_IMAGE_TIMEOUT_MS : CAPTURE_IMAGE_TIMEOUT_MS);
                 validateJpegCapture(image, stillSz);
                 // stopPreview must be called here to make sure next time a preview stream
                 // is created with new size.
@@ -1230,6 +1232,11 @@
 
     private void aeCompensationTestByCamera() throws Exception {
         Range<Integer> compensationRange = mStaticInfo.getAeCompensationRangeChecked();
+        // Skip the test if exposure compensation is not supported.
+        if (compensationRange.equals(Range.create(0, 0))) {
+            return;
+        }
+
         Rational step = mStaticInfo.getAeCompensationStepChecked();
         float stepF = (float) step.getNumerator() / step.getDenominator();
         int stepsPerEv = (int) Math.round(1.0 / stepF);
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/StaticMetadata.java b/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/StaticMetadata.java
index 4e70bc2..2a654db 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/StaticMetadata.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/StaticMetadata.java
@@ -1309,12 +1309,13 @@
         final Range<Integer> DEFAULT_RANGE = Range.create(
                 (int)(CONTROL_AE_COMPENSATION_RANGE_DEFAULT_MIN / compensationStepF),
                 (int)(CONTROL_AE_COMPENSATION_RANGE_DEFAULT_MAX / compensationStepF));
+        final Range<Integer> ZERO_RANGE = Range.create(0, 0);
         if (compensationRange == null) {
-            return DEFAULT_RANGE;
+            return ZERO_RANGE;
         }
 
         // Legacy devices don't have a minimum range requirement
-        if (isHardwareLevelLimitedOrBetter()) {
+        if (isHardwareLevelLimitedOrBetter() && !compensationRange.equals(ZERO_RANGE)) {
             checkTrueForKey(key, " range value must be at least " + DEFAULT_RANGE
                     + ", actual " + compensationRange + ", compensation step " + compensationStep,
                    compensationRange.getLower() <= DEFAULT_RANGE.getLower() &&
diff --git a/tests/tests/media/libmediandkjni/codec-utils-jni.cpp b/tests/tests/media/libmediandkjni/codec-utils-jni.cpp
index f7a08a1..4f87bb4 100644
--- a/tests/tests/media/libmediandkjni/codec-utils-jni.cpp
+++ b/tests/tests/media/libmediandkjni/codec-utils-jni.cpp
@@ -27,6 +27,8 @@
 #include <ScopedLocalRef.h>
 #include <JNIHelp.h>
 
+#include <math.h>
+
 typedef ssize_t offs_t;
 
 struct NativeImage {
@@ -154,7 +156,7 @@
     gFieldsInitialized = true;
 }
 
-NativeImage *getNativeImage(JNIEnv *env, jobject image) {
+NativeImage *getNativeImage(JNIEnv *env, jobject image, jobject area = NULL) {
     if (image == NULL) {
         jniThrowNullPointerException(env, "image is null");
         return NULL;
@@ -168,17 +170,25 @@
     img->height = env->CallIntMethod(image, gFields.methodHeight);
     img->timestamp = env->CallLongMethod(image, gFields.methodTimestamp);
 
-    jobject cropRect = env->CallObjectMethod(image, gFields.methodCrop);
-    img->crop.left   = env->GetIntField(cropRect, gFields.fieldLeft);
-    img->crop.top    = env->GetIntField(cropRect, gFields.fieldTop);
-    img->crop.right  = env->GetIntField(cropRect, gFields.fieldRight);
-    img->crop.bottom = env->GetIntField(cropRect, gFields.fieldBottom);
+    jobject cropRect = NULL;
+    if (area == NULL) {
+        cropRect = env->CallObjectMethod(image, gFields.methodCrop);
+        area = cropRect;
+    }
+
+    img->crop.left   = env->GetIntField(area, gFields.fieldLeft);
+    img->crop.top    = env->GetIntField(area, gFields.fieldTop);
+    img->crop.right  = env->GetIntField(area, gFields.fieldRight);
+    img->crop.bottom = env->GetIntField(area, gFields.fieldBottom);
     if (img->crop.right == 0 && img->crop.bottom == 0) {
         img->crop.right  = img->width;
         img->crop.bottom = img->height;
     }
-    env->DeleteLocalRef(cropRect);
-    cropRect = NULL;
+
+    if (cropRect != NULL) {
+        env->DeleteLocalRef(cropRect);
+        cropRect = NULL;
+    }
 
     if (img->format != gFields.YUV_420_888) {
         jniThrowException(
@@ -293,3 +303,186 @@
         }
     }
 }
+
+extern "C" void Java_android_media_cts_CodecUtils_fillImageRectWithYUV(JNIEnv *env,
+        jclass /*clazz*/, jobject image, jobject area, jint y, jint u, jint v)
+{
+    NativeImage *img = getNativeImage(env, image, area);
+    if (img == NULL) {
+        return;
+    }
+
+    for (size_t ix = 0; ix < img->numPlanes; ++ix) {
+        const uint8_t *row = img->plane[ix].buffer + img->plane[ix].cropOffs;
+        uint8_t val = ix == 0 ? y : ix == 1 ? u : v;
+        for (size_t y = img->plane[ix].cropHeight; y > 0; --y) {
+            uint8_t *col = (uint8_t *)row;
+            ssize_t colInc = img->plane[ix].colInc;
+            for (size_t x = img->plane[ix].cropWidth; x > 0; --x) {
+                *col = val;
+                col += colInc;
+            }
+            row += img->plane[ix].rowInc;
+        }
+    }
+}
+
+void getRawStats(NativeImage *img, jlong rawStats[10])
+{
+    // this works best if crop area is even
+
+    uint64_t sum_x[3]  = { 0, 0, 0 }; // Y, U, V
+    uint64_t sum_xx[3] = { 0, 0, 0 }; // YY, UU, VV
+    uint64_t sum_xy[3] = { 0, 0, 0 }; // YU, YV, UV
+
+    const uint8_t *yrow = img->plane[0].buffer + img->plane[0].cropOffs;
+    const uint8_t *urow = img->plane[1].buffer + img->plane[1].cropOffs;
+    const uint8_t *vrow = img->plane[2].buffer + img->plane[2].cropOffs;
+
+    ssize_t ycolInc = img->plane[0].colInc;
+    ssize_t ucolInc = img->plane[1].colInc;
+    ssize_t vcolInc = img->plane[2].colInc;
+
+    ssize_t yrowInc = img->plane[0].rowInc;
+    ssize_t urowInc = img->plane[1].rowInc;
+    ssize_t vrowInc = img->plane[2].rowInc;
+
+    size_t rightOdd = img->crop.right & 1;
+    size_t bottomOdd = img->crop.bottom & 1;
+
+    for (size_t y = img->plane[0].cropHeight; y; --y) {
+        uint8_t *ycol = (uint8_t *)yrow;
+        uint8_t *ucol = (uint8_t *)urow;
+        uint8_t *vcol = (uint8_t *)vrow;
+
+        for (size_t x = img->plane[0].cropWidth; x; --x) {
+            uint64_t Y = *ycol;
+            uint64_t U = *ucol;
+            uint64_t V = *vcol;
+
+            sum_x[0] += Y;
+            sum_x[1] += U;
+            sum_x[2] += V;
+            sum_xx[0] += Y * Y;
+            sum_xx[1] += U * U;
+            sum_xx[2] += V * V;
+            sum_xy[0] += Y * U;
+            sum_xy[1] += Y * V;
+            sum_xy[2] += U * V;
+
+            ycol += ycolInc;
+            if (rightOdd ^ (x & 1)) {
+                ucol += ucolInc;
+                vcol += vcolInc;
+            }
+        }
+
+        yrow += yrowInc;
+        if (bottomOdd ^ (y & 1)) {
+            urow += urowInc;
+            vrow += vrowInc;
+        }
+    }
+
+    rawStats[0] = img->plane[0].cropWidth * (uint64_t)img->plane[0].cropHeight;
+    for (size_t i = 0; i < 3; i++) {
+        rawStats[i + 1] = sum_x[i];
+        rawStats[i + 4] = sum_xx[i];
+        rawStats[i + 7] = sum_xy[i];
+    }
+}
+
+bool Raw2YUVStats(jlong rawStats[10], jfloat stats[9]) {
+    int64_t sum_x[3], sum_xx[3]; // Y, U, V
+    int64_t sum_xy[3];           // YU, YV, UV
+
+    int64_t num = rawStats[0];   // #Y,U,V
+    for (size_t i = 0; i < 3; i++) {
+        sum_x[i] = rawStats[i + 1];
+        sum_xx[i] = rawStats[i + 4];
+        sum_xy[i] = rawStats[i + 7];
+    }
+
+    if (num > 0) {
+        stats[0] = sum_x[0] / (float)num;  // y average
+        stats[1] = sum_x[1] / (float)num;  // u average
+        stats[2] = sum_x[2] / (float)num;  // v average
+
+        // 60 bits for 4Mpixel image
+        // adding 1 to avoid degenerate case when deviation is 0
+        stats[3] = sqrtf((sum_xx[0] + 1) * num - sum_x[0] * sum_x[0]) / num; // y stdev
+        stats[4] = sqrtf((sum_xx[1] + 1) * num - sum_x[1] * sum_x[1]) / num; // u stdev
+        stats[5] = sqrtf((sum_xx[2] + 1) * num - sum_x[2] * sum_x[2]) / num; // v stdev
+
+        // yu covar
+        stats[6] = (float)(sum_xy[0] + 1 - sum_x[0] * sum_x[1] / num) / num / stats[3] / stats[4];
+        // yv covar
+        stats[7] = (float)(sum_xy[1] + 1 - sum_x[0] * sum_x[2] / num) / num / stats[3] / stats[5];
+        // uv covar
+        stats[8] = (float)(sum_xy[2] + 1 - sum_x[1] * sum_x[2] / num) / num / stats[4] / stats[5];
+        return true;
+    } else {
+        return false;
+    }
+}
+
+extern "C" jobject Java_android_media_cts_CodecUtils_getRawStats(JNIEnv *env,
+        jclass /*clazz*/, jobject image, jobject area)
+{
+    NativeImage *img = getNativeImage(env, image, area);
+    if (img == NULL) {
+        return NULL;
+    }
+
+    jlong rawStats[10];
+    getRawStats(img, rawStats);
+    jlongArray jstats = env->NewLongArray(10);
+    if (jstats != NULL) {
+        env->SetLongArrayRegion(jstats, 0, 10, rawStats);
+    }
+    return jstats;
+}
+
+extern "C" jobject Java_android_media_cts_CodecUtils_getYUVStats(JNIEnv *env,
+        jclass /*clazz*/, jobject image, jobject area)
+{
+    NativeImage *img = getNativeImage(env, image, area);
+    if (img == NULL) {
+        return NULL;
+    }
+
+    jlong rawStats[10];
+    getRawStats(img, rawStats);
+    jfloat stats[9];
+    jfloatArray jstats = NULL;
+    if (Raw2YUVStats(rawStats, stats)) {
+        jstats = env->NewFloatArray(9);
+        if (jstats != NULL) {
+            env->SetFloatArrayRegion(jstats, 0, 9, stats);
+        }
+    } else {
+        jniThrowRuntimeException(env, "empty area");
+    }
+
+    return jstats;
+}
+
+extern "C" jobject Java_android_media_cts_CodecUtils_Raw2YUVStats(JNIEnv *env,
+        jclass /*clazz*/, jobject jrawStats)
+{
+    jfloatArray jstats = NULL;
+    jlong rawStats[10];
+    env->GetLongArrayRegion((jlongArray)jrawStats, 0, 10, rawStats);
+    if (!env->ExceptionCheck()) {
+        jfloat stats[9];
+        if (Raw2YUVStats(rawStats, stats)) {
+            jstats = env->NewFloatArray(9);
+            if (jstats != NULL) {
+                env->SetFloatArrayRegion(jstats, 0, 9, stats);
+            }
+        } else {
+            jniThrowRuntimeException(env, "no raw statistics");
+        }
+    }
+    return jstats;
+}
diff --git a/tests/tests/media/res/raw/swirl_128x128_h264.mp4 b/tests/tests/media/res/raw/swirl_128x128_h264.mp4
new file mode 100644
index 0000000..3ff485a
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_128x128_h264.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_128x128_h265.mp4 b/tests/tests/media/res/raw/swirl_128x128_h265.mp4
new file mode 100644
index 0000000..a0b112b
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_128x128_h265.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_128x128_mpeg4.mp4 b/tests/tests/media/res/raw/swirl_128x128_mpeg4.mp4
new file mode 100644
index 0000000..694ce95
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_128x128_mpeg4.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_128x128_vp8.webm b/tests/tests/media/res/raw/swirl_128x128_vp8.webm
new file mode 100644
index 0000000..7b606a2
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_128x128_vp8.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_128x128_vp9.webm b/tests/tests/media/res/raw/swirl_128x128_vp9.webm
new file mode 100644
index 0000000..7acff11
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_128x128_vp9.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_128x96_h263.3gp b/tests/tests/media/res/raw/swirl_128x96_h263.3gp
new file mode 100644
index 0000000..f0ef242
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_128x96_h263.3gp
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_130x132_h264.mp4 b/tests/tests/media/res/raw/swirl_130x132_h264.mp4
new file mode 100644
index 0000000..60027fd
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_130x132_h264.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_130x132_h265.mp4 b/tests/tests/media/res/raw/swirl_130x132_h265.mp4
new file mode 100644
index 0000000..46fab26
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_130x132_h265.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_130x132_mpeg4.mp4 b/tests/tests/media/res/raw/swirl_130x132_mpeg4.mp4
new file mode 100644
index 0000000..ed6b529
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_130x132_mpeg4.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_130x132_vp8.webm b/tests/tests/media/res/raw/swirl_130x132_vp8.webm
new file mode 100644
index 0000000..a3f2d21
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_130x132_vp8.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_130x132_vp9.webm b/tests/tests/media/res/raw/swirl_130x132_vp9.webm
new file mode 100644
index 0000000..840dc59
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_130x132_vp9.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_132x130_h264.mp4 b/tests/tests/media/res/raw/swirl_132x130_h264.mp4
new file mode 100644
index 0000000..dc17f8f
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_132x130_h264.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_132x130_h265.mp4 b/tests/tests/media/res/raw/swirl_132x130_h265.mp4
new file mode 100644
index 0000000..f9a59f5
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_132x130_h265.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_132x130_mpeg4.mp4 b/tests/tests/media/res/raw/swirl_132x130_mpeg4.mp4
new file mode 100644
index 0000000..ed975db
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_132x130_mpeg4.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_132x130_vp8.webm b/tests/tests/media/res/raw/swirl_132x130_vp8.webm
new file mode 100644
index 0000000..8cd8d4e
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_132x130_vp8.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_132x130_vp9.webm b/tests/tests/media/res/raw/swirl_132x130_vp9.webm
new file mode 100644
index 0000000..4a8d79f
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_132x130_vp9.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_136x144_h264.mp4 b/tests/tests/media/res/raw/swirl_136x144_h264.mp4
new file mode 100644
index 0000000..bc5fadf
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_136x144_h264.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_136x144_h265.mp4 b/tests/tests/media/res/raw/swirl_136x144_h265.mp4
new file mode 100644
index 0000000..38f1fc8
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_136x144_h265.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_136x144_mpeg4.mp4 b/tests/tests/media/res/raw/swirl_136x144_mpeg4.mp4
new file mode 100644
index 0000000..c74bd96
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_136x144_mpeg4.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_136x144_vp8.webm b/tests/tests/media/res/raw/swirl_136x144_vp8.webm
new file mode 100644
index 0000000..960d02f
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_136x144_vp8.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_136x144_vp9.webm b/tests/tests/media/res/raw/swirl_136x144_vp9.webm
new file mode 100644
index 0000000..5898f07
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_136x144_vp9.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_144x136_h264.mp4 b/tests/tests/media/res/raw/swirl_144x136_h264.mp4
new file mode 100644
index 0000000..962a218
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_144x136_h264.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_144x136_h265.mp4 b/tests/tests/media/res/raw/swirl_144x136_h265.mp4
new file mode 100644
index 0000000..8098621
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_144x136_h265.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_144x136_mpeg4.mp4 b/tests/tests/media/res/raw/swirl_144x136_mpeg4.mp4
new file mode 100644
index 0000000..81c1db3
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_144x136_mpeg4.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_144x136_vp8.webm b/tests/tests/media/res/raw/swirl_144x136_vp8.webm
new file mode 100644
index 0000000..b050ade
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_144x136_vp8.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_144x136_vp9.webm b/tests/tests/media/res/raw/swirl_144x136_vp9.webm
new file mode 100644
index 0000000..9c0539a
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_144x136_vp9.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_176x144_h263.3gp b/tests/tests/media/res/raw/swirl_176x144_h263.3gp
new file mode 100644
index 0000000..ee51660
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_176x144_h263.3gp
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_352x288_h263.3gp b/tests/tests/media/res/raw/swirl_352x288_h263.3gp
new file mode 100644
index 0000000..53830a9
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_352x288_h263.3gp
Binary files differ
diff --git a/tests/tests/media/src/android/media/cts/CodecUtils.java b/tests/tests/media/src/android/media/cts/CodecUtils.java
index df6eb4c..ef20e67 100644
--- a/tests/tests/media/src/android/media/cts/CodecUtils.java
+++ b/tests/tests/media/src/android/media/cts/CodecUtils.java
@@ -16,6 +16,7 @@
 
 package android.media.cts;
 
+import android.graphics.Rect;
 import android.media.cts.CodecImage;
 import android.media.Image;
 import android.util.Log;
@@ -116,5 +117,26 @@
                 ImageWrapper.createFromImage(target),
                 ImageWrapper.createFromImage(source));
     }
+
+    public native static void fillImageRectWithYUV(
+            CodecImage image, Rect area, int y, int u, int v);
+
+    public static void fillImageRectWithYUV(Image image, Rect area, int y, int u, int v) {
+        fillImageRectWithYUV(ImageWrapper.createFromImage(image), area, y, u, v);
+    }
+
+    public native static long[] getRawStats(CodecImage image, Rect area);
+
+    public static long[] getRawStats(Image image, Rect area) {
+        return getRawStats(ImageWrapper.createFromImage(image), area);
+    }
+
+    public native static float[] getYUVStats(CodecImage image, Rect area);
+
+    public static float[] getYUVStats(Image image, Rect area) {
+        return getYUVStats(ImageWrapper.createFromImage(image), area);
+    }
+
+    public native static float[] Raw2YUVStats(long[] rawStats);
 }
 
diff --git a/tests/tests/media/src/android/media/cts/ImageReaderDecoderTest.java b/tests/tests/media/src/android/media/cts/ImageReaderDecoderTest.java
index 96150ca..cc28b86 100644
--- a/tests/tests/media/src/android/media/cts/ImageReaderDecoderTest.java
+++ b/tests/tests/media/src/android/media/cts/ImageReaderDecoderTest.java
@@ -21,14 +21,19 @@
 import android.content.Context;
 import android.content.res.AssetFileDescriptor;
 import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
 import android.cts.util.MediaUtils;
+import android.graphics.Rect;
 import android.graphics.ImageFormat;
+import android.media.cts.CodecUtils;
 import android.media.Image;
 import android.media.Image.Plane;
 import android.media.ImageReader;
 import android.media.MediaCodec;
 import android.media.MediaCodecInfo;
 import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaCodecInfo.VideoCapabilities;
+import android.media.MediaCodecList;
 import android.media.MediaExtractor;
 import android.media.MediaFormat;
 import android.os.Handler;
@@ -38,9 +43,15 @@
 import android.util.Log;
 import android.view.Surface;
 
+import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible;
+
+import java.io.File;
 import java.io.FileOutputStream;
+import java.io.InputStream;
 import java.io.IOException;
 import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.ArrayList;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 
@@ -64,11 +75,14 @@
     private static final int NUM_FRAME_DECODED = 100;
     // video decoders only support a single outstanding image with the consumer
     private static final int MAX_NUM_IMAGES = 1;
+    private static final float COLOR_STDEV_ALLOWANCE = 5f;
+    private static final float COLOR_DELTA_ALLOWANCE = 5f;
+
+    private final static int MODE_IMAGEREADER = 0;
+    private final static int MODE_IMAGE       = 1;
 
     private Resources mResources;
     private MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo();
-    private ByteBuffer[] mInputBuffers;
-    private ByteBuffer[] mOutputBuffers;
     private ImageReader mReader;
     private Surface mReaderSurface;
     private HandlerThread mHandlerThread;
@@ -96,31 +110,322 @@
         mHandler = null;
     }
 
-    /**
-     * Test ImageReader with 480x360 hw AVC decoding for flexible yuv format, which is mandatory
-     * to be supported by hw decoder.
-     */
-    public void testHwAVCDecode360pForFlexibleYuv() throws Exception {
-        try {
-            int format = ImageFormat.YUV_420_888;
-            videoDecodeToSurface(
-                    R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz,
-                    /* width */480, /* height */ 360, format, /* useHw */ true);
-        } finally {
-            closeImageReader();
+    static class MediaAsset {
+        public MediaAsset(int resource, int width, int height) {
+            mResource = resource;
+            mWidth = width;
+            mHeight = height;
+        }
+
+        public int getWidth() {
+            return mWidth;
+        }
+
+        public int getHeight() {
+            return mHeight;
+        }
+
+        public int getResource() {
+            return mResource;
+        }
+
+        private final int mResource;
+        private final int mWidth;
+        private final int mHeight;
+    }
+
+    static class MediaAssets {
+        public MediaAssets(String mime, MediaAsset... assets) {
+            mMime = mime;
+            mAssets = assets;
+        }
+
+        public String getMime() {
+            return mMime;
+        }
+
+        public MediaAsset[] getAssets() {
+            return mAssets;
+        }
+
+        private final String mMime;
+        private final MediaAsset[] mAssets;
+    }
+
+    private static MediaAssets H263_ASSETS = new MediaAssets(
+            MediaFormat.MIMETYPE_VIDEO_H263,
+            new MediaAsset(R.raw.swirl_176x144_h263, 176, 144),
+            new MediaAsset(R.raw.swirl_352x288_h263, 352, 288),
+            new MediaAsset(R.raw.swirl_128x96_h263, 128, 96));
+
+    private static MediaAssets MPEG4_ASSETS = new MediaAssets(
+            MediaFormat.MIMETYPE_VIDEO_MPEG4,
+            new MediaAsset(R.raw.swirl_128x128_mpeg4, 128, 128),
+            new MediaAsset(R.raw.swirl_144x136_mpeg4, 144, 136),
+            new MediaAsset(R.raw.swirl_136x144_mpeg4, 136, 144),
+            new MediaAsset(R.raw.swirl_132x130_mpeg4, 132, 130),
+            new MediaAsset(R.raw.swirl_130x132_mpeg4, 130, 132));
+
+    private static MediaAssets H264_ASSETS = new MediaAssets(
+            MediaFormat.MIMETYPE_VIDEO_AVC,
+            new MediaAsset(R.raw.swirl_128x128_h264, 128, 128),
+            new MediaAsset(R.raw.swirl_144x136_h264, 144, 136),
+            new MediaAsset(R.raw.swirl_136x144_h264, 136, 144),
+            new MediaAsset(R.raw.swirl_132x130_h264, 132, 130),
+            new MediaAsset(R.raw.swirl_130x132_h264, 130, 132));
+
+    private static MediaAssets H265_ASSETS = new MediaAssets(
+            MediaFormat.MIMETYPE_VIDEO_HEVC,
+            new MediaAsset(R.raw.swirl_128x128_h265, 128, 128),
+            new MediaAsset(R.raw.swirl_144x136_h265, 144, 136),
+            new MediaAsset(R.raw.swirl_136x144_h265, 136, 144),
+            new MediaAsset(R.raw.swirl_132x130_h265, 132, 130),
+            new MediaAsset(R.raw.swirl_130x132_h265, 130, 132));
+
+    private static MediaAssets VP8_ASSETS = new MediaAssets(
+            MediaFormat.MIMETYPE_VIDEO_VP8,
+            new MediaAsset(R.raw.swirl_128x128_vp8, 128, 128),
+            new MediaAsset(R.raw.swirl_144x136_vp8, 144, 136),
+            new MediaAsset(R.raw.swirl_136x144_vp8, 136, 144),
+            new MediaAsset(R.raw.swirl_132x130_vp8, 132, 130),
+            new MediaAsset(R.raw.swirl_130x132_vp8, 130, 132));
+
+    private static MediaAssets VP9_ASSETS = new MediaAssets(
+            MediaFormat.MIMETYPE_VIDEO_VP9,
+            new MediaAsset(R.raw.swirl_128x128_vp9, 128, 128),
+            new MediaAsset(R.raw.swirl_144x136_vp9, 144, 136),
+            new MediaAsset(R.raw.swirl_136x144_vp9, 136, 144),
+            new MediaAsset(R.raw.swirl_132x130_vp9, 132, 130),
+            new MediaAsset(R.raw.swirl_130x132_vp9, 130, 132));
+
+    static final float SWIRL_FPS = 12.f;
+
+    class Decoder {
+        final private String mName;
+        final private String mMime;
+        final private VideoCapabilities mCaps;
+        final private ArrayList<MediaAsset> mAssets;
+
+        boolean isFlexibleFormatSupported(CodecCapabilities caps) {
+            for (int c : caps.colorFormats) {
+                if (c == COLOR_FormatYUV420Flexible) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        Decoder(String name, MediaAssets assets, CodecCapabilities caps) {
+            mName = name;
+            mMime = assets.getMime();
+            mCaps = caps.getVideoCapabilities();
+            mAssets = new ArrayList<MediaAsset>();
+
+            for (MediaAsset asset : assets.getAssets()) {
+                if (mCaps.areSizeAndRateSupported(asset.getWidth(), asset.getHeight(), SWIRL_FPS)
+                        && isFlexibleFormatSupported(caps)) {
+                    mAssets.add(asset);
+                }
+            }
+        }
+
+        public boolean videoDecode(int mode, boolean checkSwirl) {
+            boolean skipped = true;
+            for (MediaAsset asset: mAssets) {
+                // TODO: loop over all supported image formats
+                int imageFormat = ImageFormat.YUV_420_888;
+                int colorFormat = COLOR_FormatYUV420Flexible;
+                videoDecode(asset, imageFormat, colorFormat, mode, checkSwirl);
+                skipped = false;
+            }
+            return skipped;
+        }
+
+        private void videoDecode(
+                MediaAsset asset, int imageFormat, int colorFormat, int mode, boolean checkSwirl) {
+            int video = asset.getResource();
+            int width = asset.getWidth();
+            int height = asset.getHeight();
+
+            if (DEBUG) Log.d(TAG, "videoDecode " + mName + " " + width + "x" + height);
+
+            MediaCodec decoder = null;
+            AssetFileDescriptor vidFD = null;
+
+            MediaExtractor extractor = null;
+            File tmpFile = null;
+            InputStream is = null;
+            FileOutputStream os = null;
+            MediaFormat mediaFormat = null;
+            try {
+                extractor = new MediaExtractor();
+
+                try {
+                    vidFD = mResources.openRawResourceFd(video);
+                    extractor.setDataSource(
+                            vidFD.getFileDescriptor(), vidFD.getStartOffset(), vidFD.getLength());
+                } catch (NotFoundException e) {
+                    // resource is compressed, uncompress locally
+                    String tmpName = "tempStream";
+                    tmpFile = File.createTempFile(tmpName, null, mContext.getCacheDir());
+                    is = mResources.openRawResource(video);
+                    os = new FileOutputStream(tmpFile);
+                    byte[] buf = new byte[1024];
+                    int len;
+                    while ((len = is.read(buf, 0, buf.length)) > 0) {
+                        os.write(buf, 0, len);
+                    }
+                    os.close();
+                    is.close();
+
+                    extractor.setDataSource(tmpFile.getAbsolutePath());
+                }
+
+                mediaFormat = extractor.getTrackFormat(0);
+                mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);
+
+                // Create decoder
+                decoder = MediaCodec.createByCodecName(mName);
+                assertNotNull("couldn't create decoder" + mName, decoder);
+
+                decodeFramesToImage(
+                        decoder, extractor, mediaFormat,
+                        width, height, imageFormat, mode, checkSwirl);
+
+                decoder.stop();
+                if (vidFD != null) {
+                    vidFD.close();
+                }
+            } catch (Throwable e) {
+                throw new RuntimeException("while " + mName + " decoding "
+                        + mResources.getResourceEntryName(video) + ": " + mediaFormat, e);
+            } finally {
+                if (decoder != null) {
+                    decoder.release();
+                }
+                if (extractor != null) {
+                    extractor.release();
+                }
+                if (tmpFile != null) {
+                    tmpFile.delete();
+                }
+            }
         }
     }
 
+    private Decoder[] decoders(MediaAssets assets, boolean goog) {
+        String mime = assets.getMime();
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        ArrayList<Decoder> result = new ArrayList<Decoder>();
+
+        for (MediaCodecInfo info : mcl.getCodecInfos()) {
+            if (info.isEncoder()
+                    || info.getName().toLowerCase().startsWith("omx.google.") != goog) {
+                continue;
+            }
+            CodecCapabilities caps = null;
+            try {
+                caps = info.getCapabilitiesForType(mime);
+            } catch (IllegalArgumentException e) { // mime is not supported
+                continue;
+            }
+            assertNotNull(info.getName() + " capabilties for " + mime + " returned null", caps);
+            result.add(new Decoder(info.getName(), assets, caps));
+        }
+        return result.toArray(new Decoder[result.size()]);
+    }
+
+    private Decoder[] goog(MediaAssets assets) {
+        return decoders(assets, true /* goog */);
+    }
+
+    private Decoder[] other(MediaAssets assets) {
+        return decoders(assets, false /* goog */);
+    }
+
+    private Decoder[] googH265()  { return goog(H265_ASSETS); }
+    private Decoder[] googH264()  { return goog(H264_ASSETS); }
+    private Decoder[] googH263()  { return goog(H263_ASSETS); }
+    private Decoder[] googMpeg4() { return goog(MPEG4_ASSETS); }
+    private Decoder[] googVP8()   { return goog(VP8_ASSETS); }
+    private Decoder[] googVP9()   { return goog(VP9_ASSETS); }
+
+    private Decoder[] otherH265()  { return other(H265_ASSETS); }
+    private Decoder[] otherH264()  { return other(H264_ASSETS); }
+    private Decoder[] otherH263()  { return other(H263_ASSETS); }
+    private Decoder[] otherMpeg4() { return other(MPEG4_ASSETS); }
+    private Decoder[] otherVP8()   { return other(VP8_ASSETS); }
+    private Decoder[] otherVP9()   { return other(VP9_ASSETS); }
+
+    public void testGoogH265Image()   { swirlTest(googH265(),   MODE_IMAGE); }
+    public void testGoogH264Image()   { swirlTest(googH264(),   MODE_IMAGE); }
+    public void testGoogH263Image()   { swirlTest(googH263(),   MODE_IMAGE); }
+    public void testGoogMpeg4Image()  { swirlTest(googMpeg4(),  MODE_IMAGE); }
+    public void testGoogVP8Image()    { swirlTest(googVP8(),    MODE_IMAGE); }
+    public void testGoogVP9Image()    { swirlTest(googVP9(),    MODE_IMAGE); }
+
+    public void testOtherH265Image()  { swirlTest(otherH265(),  MODE_IMAGE); }
+    public void testOtherH264Image()  { swirlTest(otherH264(),  MODE_IMAGE); }
+    public void testOtherH263Image()  { swirlTest(otherH263(),  MODE_IMAGE); }
+    public void testOtherMpeg4Image() { swirlTest(otherMpeg4(), MODE_IMAGE); }
+    public void testOtherVP8Image()   { swirlTest(otherVP8(),   MODE_IMAGE); }
+    public void testOtherVP9Image()   { swirlTest(otherVP9(),   MODE_IMAGE); }
+
+    public void testGoogH265ImageReader()   { swirlTest(googH265(),   MODE_IMAGEREADER); }
+    public void testGoogH264ImageReader()   { swirlTest(googH264(),   MODE_IMAGEREADER); }
+    public void testGoogH263ImageReader()   { swirlTest(googH263(),   MODE_IMAGEREADER); }
+    public void testGoogMpeg4ImageReader()  { swirlTest(googMpeg4(),  MODE_IMAGEREADER); }
+    public void testGoogVP8ImageReader()    { swirlTest(googVP8(),    MODE_IMAGEREADER); }
+    public void testGoogVP9ImageReader()    { swirlTest(googVP9(),    MODE_IMAGEREADER); }
+
+    public void testOtherH265ImageReader()  { swirlTest(otherH265(),  MODE_IMAGEREADER); }
+    public void testOtherH264ImageReader()  { swirlTest(otherH264(),  MODE_IMAGEREADER); }
+    public void testOtherH263ImageReader()  { swirlTest(otherH263(),  MODE_IMAGEREADER); }
+    public void testOtherMpeg4ImageReader() { swirlTest(otherMpeg4(), MODE_IMAGEREADER); }
+    public void testOtherVP8ImageReader()   { swirlTest(otherVP8(),   MODE_IMAGEREADER); }
+    public void testOtherVP9ImageReader()   { swirlTest(otherVP9(),   MODE_IMAGEREADER); }
+
     /**
-     * Test ImageReader with 480x360 sw AVC decoding for flexible yuv format, which is mandatory
-     * to be supported by sw decoder.
+     * Test ImageReader with 480x360 non-google AVC decoding for flexible yuv format
+     */
+    public void testHwAVCDecode360pForFlexibleYuv() throws Exception {
+        Decoder[] decoders = other(new MediaAssets(
+                MediaFormat.MIMETYPE_VIDEO_AVC,
+                new MediaAsset(
+                        R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz,
+                        480 /* width */, 360 /* height */)));
+
+        decodeTest(decoders, MODE_IMAGEREADER, false /* checkSwirl */);
+    }
+
+    /**
+     * Test ImageReader with 480x360 google (SW) AVC decoding for flexible yuv format
      */
     public void testSwAVCDecode360pForFlexibleYuv() throws Exception {
+        Decoder[] decoders = goog(new MediaAssets(
+                MediaFormat.MIMETYPE_VIDEO_AVC,
+                new MediaAsset(
+                        R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz,
+                        480 /* width */, 360 /* height */)));
+
+        decodeTest(decoders, MODE_IMAGEREADER, false /* checkSwirl */);
+    }
+
+    private void swirlTest(Decoder[] decoders, int mode) {
+        decodeTest(decoders, mode, true /* checkSwirl */);
+    }
+
+    private void decodeTest(Decoder[] decoders, int mode, boolean checkSwirl) {
         try {
-            int format = ImageFormat.YUV_420_888;
-            videoDecodeToSurface(
-                    R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz,
-                    /* width */ 480, /* height */ 360, format, /* useHw */ false);
+            boolean skipped = true;
+            for (Decoder codec : decoders) {
+                if (codec.videoDecode(mode, checkSwirl)) {
+                    skipped = false;
+                }
+            }
+            if (skipped) {
+                MediaUtils.skipTest("decoder does not any of the input files");
+            }
         } finally {
             closeImageReader();
         }
@@ -153,61 +458,26 @@
         }
     }
 
-    private void videoDecodeToSurface(int video, int width,
-            int height, int imageFormat, boolean useHw) throws Exception {
-        MediaCodec decoder = null;
-        MediaExtractor extractor;
-        int outputBufferIndex;
-        ByteBuffer[] decoderInputBuffers;
-        ByteBuffer[] decoderOutputBuffers;
-
-        if (!MediaUtils.checkCodecForResource(mContext, video, 0 /* track */)) {
-            return; // skip
-        }
-
-        AssetFileDescriptor vidFD = mResources.openRawResourceFd(video);
-
-        extractor = new MediaExtractor();
-        extractor.setDataSource(vidFD.getFileDescriptor(), vidFD.getStartOffset(),
-                vidFD.getLength());
-
-        MediaFormat mediaFmt = extractor.getTrackFormat(0);
-        mediaFmt.setInteger(MediaFormat.KEY_COLOR_FORMAT,
-                            CodecCapabilities.COLOR_FormatYUV420Flexible);
-        String mime = mediaFmt.getString(MediaFormat.KEY_MIME);
-        try {
-            // Create decoder
-            decoder = createDecoder(mime, useHw);
-            assertNotNull("couldn't find decoder", decoder);
-            if (VERBOSE) Log.v(TAG, "using decoder: " + decoder.getName());
-
-            decodeFramesToImageReader(width, height, imageFormat, decoder,
-                    extractor, mediaFmt, mime);
-
-            decoder.stop();
-        } finally {
-            decoder.release();
-        }
-
-    }
-
     /**
      * Decode video frames to image reader.
      */
-    private void decodeFramesToImageReader(int width, int height, int imageFormat,
-            MediaCodec decoder, MediaExtractor extractor, MediaFormat mediaFmt, String mime)
-            throws Exception, InterruptedException {
+    private void decodeFramesToImage(
+            MediaCodec decoder, MediaExtractor extractor, MediaFormat mediaFormat,
+            int width, int height, int imageFormat, int mode, boolean checkSwirl)
+            throws InterruptedException {
         ByteBuffer[] decoderInputBuffers;
         ByteBuffer[] decoderOutputBuffers;
-        if (!imageFormatSupported(decoder, imageFormat, mime)) {
-            // TODO: SKIPPING TEST
-            return;
-        }
-        createImageReader(width, height, imageFormat, MAX_NUM_IMAGES, mImageListener);
 
         // Configure decoder.
-        if (VERBOSE) Log.v(TAG, "stream format: " + mediaFmt);
-        decoder.configure(mediaFmt, mReaderSurface, /*crypto*/null, /*flags*/0);
+        if (VERBOSE) Log.v(TAG, "stream format: " + mediaFormat);
+        if (mode == MODE_IMAGEREADER) {
+            createImageReader(width, height, imageFormat, MAX_NUM_IMAGES, mImageListener);
+            decoder.configure(mediaFormat, mReaderSurface, null /* crypto */, 0 /* flags */);
+        } else {
+            assertEquals(mode, MODE_IMAGE);
+            decoder.configure(mediaFormat, null /* surface */, null /* crypto */, 0 /* flags */);
+        }
+
         decoder.start();
         decoderInputBuffers = decoder.getInputBuffers();
         decoderOutputBuffers = decoder.getOutputBuffers();
@@ -272,65 +542,55 @@
                 // Should be decoding error.
                 fail("unexpected result from deocder.dequeueOutputBuffer: " + res);
             } else {
+                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                    sawOutputEOS = true;
+                }
+
                 // res >= 0: normal decoding case, copy the output buffer.
                 // Will use it as reference to valid the ImageReader output
                 // Some decoders output a 0-sized buffer at the end. Ignore those.
-                outputFrameCount++;
                 boolean doRender = (info.size != 0);
 
-                decoder.releaseOutputBuffer(res, doRender);
                 if (doRender) {
-                    // Read image and verify
-                    Image image = mImageListener.getImage(WAIT_FOR_IMAGE_TIMEOUT_MS);
-                    Plane[] imagePlanes = image.getPlanes();
+                    outputFrameCount++;
+                    String fileName = DEBUG_FILE_NAME_BASE + MediaUtils.getTestName()
+                            + (mode == MODE_IMAGE ? "_image_" : "_reader_")
+                            + width + "x" + height + "_" + outputFrameCount + ".yuv";
 
-                    //Verify
-                    String fileName = DEBUG_FILE_NAME_BASE + width + "x" + height + "_"
-                            + outputFrameCount + ".yuv";
-                    validateImage(image, width, height, imageFormat, fileName);
+                    Image image = null;
+                    try {
+                        if (mode == MODE_IMAGE) {
+                            image = decoder.getOutputImage(res);
+                        } else {
+                            decoder.releaseOutputBuffer(res, doRender);
+                            res = -1;
+                            // Read image and verify
+                            image = mImageListener.getImage(WAIT_FOR_IMAGE_TIMEOUT_MS);
+                        }
+                        validateImage(image, width, height, imageFormat, fileName);
 
-                    if (VERBOSE) {
-                        Log.v(TAG, "Image " + outputFrameCount + " Info:");
-                        Log.v(TAG, "first plane pixelstride " + imagePlanes[0].getPixelStride());
-                        Log.v(TAG, "first plane rowstride " + imagePlanes[0].getRowStride());
-                        Log.v(TAG, "Image timestamp:" + image.getTimestamp());
+                        if (checkSwirl) {
+                            try {
+                                validateSwirl(image);
+                            } catch (Throwable e) {
+                                dumpFile(fileName, getDataFromImage(image));
+                                throw e;
+                            }
+                        }
+                    } finally {
+                        if (image != null) {
+                            image.close();
+                        }
                     }
-                    image.close();
+                }
+
+                if (res >= 0) {
+                    decoder.releaseOutputBuffer(res, false /* render */);
                 }
             }
         }
     }
 
-    private boolean imageFormatSupported(MediaCodec decoder, int imageFormat, String mime) {
-        MediaCodecInfo codecInfo = decoder.getCodecInfo();
-        if (codecInfo == null) {
-            return false;
-        }
-        MediaCodecInfo.CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(mime);
-        for (int colorFormat : capabilities.colorFormats) {
-            if (colorFormat == CodecCapabilities.COLOR_FormatYUV420Flexible
-                    && imageFormat == ImageFormat.YUV_420_888) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private MediaCodec createDecoder(String mime, boolean useHw) throws Exception {
-        if (!useHw) {
-            if (mime.contains("avc")) {
-                return MediaCodec.createByCodecName("OMX.google.h264.decoder");
-            } else if (mime.contains("3gpp")) {
-                return MediaCodec.createByCodecName("OMX.google.h263.decoder");
-            } else if (mime.contains("mp4v")) {
-                return MediaCodec.createByCodecName("OMX.google.mpeg4.decoder");
-            } else if (mime.contains("vp8")) {
-                return MediaCodec.createByCodecName("OMX.google.vpx.decoder");
-            }
-        }
-        return MediaCodec.createDecoderByType(mime);
-    }
-
     /**
      * Validate image based on format and size.
      *
@@ -340,22 +600,103 @@
      * @param format The image format.
      * @param filePath The debug dump file path, null if don't want to dump to file.
      */
-    public static void validateImage(Image image, int width, int height, int format,
-            String filePath) {
+    public static void validateImage(
+            Image image, int width, int height, int format, String filePath) {
+        if (VERBOSE) {
+            Plane[] imagePlanes = image.getPlanes();
+            Log.v(TAG, "Image " + filePath + " Info:");
+            Log.v(TAG, "first plane pixelstride " + imagePlanes[0].getPixelStride());
+            Log.v(TAG, "first plane rowstride " + imagePlanes[0].getRowStride());
+            Log.v(TAG, "Image timestamp:" + image.getTimestamp());
+        }
+
         assertNotNull("Input image is invalid", image);
         assertEquals("Format doesn't match", format, image.getFormat());
-        assertEquals("Width doesn't match", width, image.getWidth());
-        assertEquals("Height doesn't match", height, image.getHeight());
+        assertEquals("Width doesn't match", width, image.getCropRect().width());
+        assertEquals("Height doesn't match", height, image.getCropRect().height());
 
         if(VERBOSE) Log.v(TAG, "validating Image");
         byte[] data = getDataFromImage(image);
         assertTrue("Invalid image data", data != null && data.length > 0);
 
-        validateYuvData(data, width, height, format, image.getTimestamp(), filePath);
+        validateYuvData(data, width, height, format, image.getTimestamp());
+
+        if (VERBOSE && filePath != null) {
+            dumpFile(filePath, data);
+        }
+    }
+
+    private static void validateSwirl(Image image) {
+        Rect crop = image.getCropRect();
+        final int NUM_SIDES = 4;
+        final int step = 8;      // the width of the layers
+        long[][] rawStats = new long[NUM_SIDES][10];
+        int[][] colors = new int[][] {
+            { 111, 96, 204 }, { 178, 27, 174 }, { 100, 192, 92 }, { 106, 117, 62 }
+        };
+
+        // successively accumulate statistics for each layer of the swirl
+        // by using overlapping rectangles, and the observation that
+        // layer_i = rectangle_i - rectangle_(i+1)
+        int lastLayer = 0;
+        int layer = 0;
+        boolean lastLayerValid = false;
+        for (int pos = 0; ; pos += step) {
+            Rect area = new Rect(pos - step, pos, crop.width() / 2, crop.height() + 2 * step - pos);
+            if (area.isEmpty()) {
+                break;
+            }
+            area.offset(crop.left, crop.top);
+            area.intersect(crop);
+            for (int lr = 0; lr < 2; ++lr) {
+                long[] oneStat = CodecUtils.getRawStats(image, area);
+                if (VERBOSE) Log.v(TAG, "area=" + area + ", layer=" + layer + ", last="
+                                    + lastLayer + ": " + Arrays.toString(oneStat));
+                for (int i = 0; i < oneStat.length; i++) {
+                    rawStats[layer][i] += oneStat[i];
+                    if (lastLayerValid) {
+                        rawStats[lastLayer][i] -= oneStat[i];
+                    }
+                }
+                if (VERBOSE && lastLayerValid) {
+                    Log.v(TAG, "layer-" + lastLayer + ": " + Arrays.toString(rawStats[lastLayer]));
+                    Log.v(TAG, Arrays.toString(CodecUtils.Raw2YUVStats(rawStats[lastLayer])));
+                }
+                // switch to the opposite side
+                layer ^= 2;      // NUM_SIDES / 2
+                lastLayer ^= 2;  // NUM_SIDES / 2
+                area.offset(crop.centerX() - area.left, 2 * (crop.centerY() - area.centerY()));
+            }
+
+            lastLayer = layer;
+            lastLayerValid = true;
+            layer = (layer + 1) % NUM_SIDES;
+        }
+
+        for (layer = 0; layer < NUM_SIDES; ++layer) {
+            float[] stats = CodecUtils.Raw2YUVStats(rawStats[layer]);
+            if (DEBUG) Log.d(TAG, "layer-" + layer + ": " + Arrays.toString(stats));
+            if (VERBOSE) Log.v(TAG, Arrays.toString(rawStats[layer]));
+
+            // check layer uniformity
+            for (int i = 0; i < 3; i++) {
+                assertTrue("color of layer-" + layer + " is not uniform: "
+                        + Arrays.toString(stats),
+                        stats[3 + i] < COLOR_STDEV_ALLOWANCE);
+            }
+
+            // check layer color
+            for (int i = 0; i < 3; i++) {
+                assertTrue("color of layer-" + layer + " mismatches target "
+                        + Arrays.toString(colors[layer]) + " vs "
+                        + Arrays.toString(Arrays.copyOf(stats, 3)),
+                        Math.abs(stats[i] - colors[layer][i]) < COLOR_DELTA_ALLOWANCE);
+            }
+        }
     }
 
     private static void validateYuvData(byte[] yuvData, int width, int height, int format,
-            long ts, String fileName) {
+            long ts) {
 
         assertTrue("YUV format must be one of the YUV_420_888, NV21, or YV12",
                 format == ImageFormat.YUV_420_888 ||
@@ -365,10 +706,6 @@
         if (VERBOSE) Log.v(TAG, "Validating YUV data");
         int expectedSize = width * height * ImageFormat.getBitsPerPixel(format) / 8;
         assertEquals("Yuv data doesn't match", expectedSize, yuvData.length);
-
-        if (DEBUG && fileName != null) {
-            dumpFile(fileName, yuvData);
-        }
     }
 
     private static void checkYuvFormat(int format) {
@@ -413,9 +750,10 @@
      */
     private static byte[] getDataFromImage(Image image) {
         assertNotNull("Invalid image:", image);
+        Rect crop = image.getCropRect();
         int format = image.getFormat();
-        int width = image.getWidth();
-        int height = image.getHeight();
+        int width = crop.width();
+        int height = crop.height();
         int rowStride, pixelStride;
         byte[] data = null;
 
@@ -433,6 +771,7 @@
         byte[] rowData = new byte[planes[0].getRowStride()];
         if(VERBOSE) Log.v(TAG, "get data from " + planes.length + " planes");
         for (int i = 0; i < planes.length; i++) {
+            int shift = (i == 0) ? 0 : 1;
             buffer = planes[i].getBuffer();
             assertNotNull("Fail to get bytebuffer from plane", buffer);
             rowStride = planes[i].getRowStride();
@@ -445,27 +784,32 @@
                 Log.v(TAG, "height " + height);
             }
             // For multi-planar yuv images, assuming yuv420 with 2x2 chroma subsampling.
-            int w = (i == 0) ? width : width / 2;
-            int h = (i == 0) ? height : height / 2;
+            int w = crop.width() >> shift;
+            int h = crop.height() >> shift;
+            buffer.position(rowStride * (crop.top >> shift) + pixelStride * (crop.left >> shift));
             assertTrue("rowStride " + rowStride + " should be >= width " + w , rowStride >= w);
             for (int row = 0; row < h; row++) {
                 int bytesPerPixel = ImageFormat.getBitsPerPixel(format) / 8;
+                int length;
                 if (pixelStride == bytesPerPixel) {
                     // Special case: optimized read of the entire row
-                    int length = w * bytesPerPixel;
+                    length = w * bytesPerPixel;
                     buffer.get(data, offset, length);
-                    // Advance buffer the remainder of the row stride
-                    buffer.position(buffer.position() + rowStride - length);
                     offset += length;
                 } else {
                     // Generic case: should work for any pixelStride but slower.
                     // Use intermediate buffer to avoid read byte-by-byte from
                     // DirectByteBuffer, which is very bad for performance
-                    buffer.get(rowData, 0, rowStride);
+                    length = (w - 1) * pixelStride + bytesPerPixel;
+                    buffer.get(rowData, 0, length);
                     for (int col = 0; col < w; col++) {
                         data[offset++] = rowData[col * pixelStride];
                     }
                 }
+                // Advance buffer the remainder of the row stride
+                if (row < h - 1) {
+                    buffer.position(buffer.position() + rowStride - length);
+                }
             }
             if (VERBOSE) Log.v(TAG, "Finished reading data from plane " + i);
         }
@@ -492,8 +836,9 @@
         }
     }
 
-    private void createImageReader(int width, int height, int format, int maxNumImages,
-            ImageReader.OnImageAvailableListener listener) throws Exception {
+    private void createImageReader(
+            int width, int height, int format, int maxNumImages,
+            ImageReader.OnImageAvailableListener listener)  {
         closeImageReader();
 
         mReader = ImageReader.newInstance(width, height, format, maxNumImages);