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);