am f366577f: (-s ours) am 16ce5735: Merge "DO NOT MERGE Fix knownfailures.txt. Add missing commas." into klp-modular-dev
* commit 'f366577f74e896b0c4ec8c249dc2533d43cb1708':
DO NOT MERGE Fix knownfailures.txt. Add missing commas.
diff --git a/.gitignore b/.gitignore
index b8a343c..0353c27 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@
.project
.cproject
.classpath
+/bin
diff --git a/CtsBuild.mk b/CtsBuild.mk
index 6e57086..f6d0d5c 100644
--- a/CtsBuild.mk
+++ b/CtsBuild.mk
@@ -51,3 +51,7 @@
define cts-get-test-xmls
$(foreach name,$(1),$(CTS_TESTCASES_OUT)/$(name).xml)
endef
+
+define cts-get-executable-paths
+ $(foreach executable,$(1),$(CTS_TESTCASES_OUT)/$(executable))
+endef
diff --git a/CtsTestCaseList.mk b/CtsTestCaseList.mk
index ce12fff..52456e7 100644
--- a/CtsTestCaseList.mk
+++ b/CtsTestCaseList.mk
@@ -40,7 +40,6 @@
CtsHoloDeviceApp \
CtsMonkeyApp \
CtsMonkeyApp2 \
- CtsSampleDeviceApp \
CtsSomeAccessibilityServices \
CtsTestStubs \
SignatureTest \
@@ -88,6 +87,7 @@
CtsGraphicsTestCases \
CtsGraphics2TestCases \
CtsHardwareTestCases \
+ CtsHoloTestCases \
CtsJniTestCases \
CtsKeystoreTestCases \
CtsLocationTestCases \
@@ -103,11 +103,11 @@
CtsPermission2TestCases \
CtsPreferenceTestCases \
CtsPreference2TestCases \
+ CtsPrintTestCases \
CtsProviderTestCases \
CtsRenderscriptTestCases \
CtsRenderscriptGraphicsTestCases \
CtsRsCppTestCases \
- CtsSampleDeviceTestCases \
CtsSaxTestCases \
CtsSecurityTestCases \
CtsSpeechTestCases \
@@ -115,6 +115,7 @@
CtsTextTestCases \
CtsTextureViewTestCases \
CtsThemeTestCases \
+ CtsUiAutomationTestCases \
CtsUtilTestCases \
CtsViewTestCases \
CtsWebkitTestCases \
@@ -129,10 +130,10 @@
cts_host_libraries := \
CtsAdbTests \
CtsAppSecurityTests \
+ CtsHoloHostTestCases \
CtsHostJank \
CtsHostUi \
CtsMonkeyTestCases \
- CtsSampleHostTestCases \
CtsUsbTests
# Native test executables that need to have associated test XMLs.
@@ -148,7 +149,11 @@
CtsUiAutomatorTests
cts_device_jars := \
- CtsDeviceJank
+ CtsDeviceJank \
+ CtsPrintInstrument
+
+cts_device_executables := \
+ print-instrument
# All the files that will end up under the repository/testcases
# directory of the final CTS distribution.
@@ -156,7 +161,8 @@
$(call cts-get-package-paths,$(cts_test_packages)) \
$(call cts-get-native-paths,$(cts_native_exes)) \
$(call cts-get-ui-lib-paths,$(cts_ui_tests)) \
- $(call cts-get-ui-lib-paths,$(cts_device_jars))
+ $(call cts-get-ui-lib-paths,$(cts_device_jars)) \
+ $(call cts-get-executable-paths,$(cts_device_executables))
# All the XMLs that will end up under the repository/testcases
# and that need to be created before making the final CTS distribution.
@@ -165,6 +171,5 @@
$(call cts-get-test-xmls,$(cts_native_exes)) \
$(call cts-get-test-xmls,$(cts_ui_tests))
-
# The following files will be placed in the tools directory of the CTS distribution
CTS_TOOLS_LIST :=
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 8b8c8b6..2507955 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -21,7 +21,7 @@
android:versionName="4.4_r3">
<!-- Using 10+ for more complete NFC support... -->
- <uses-sdk android:minSdkVersion="12"></uses-sdk>
+ <uses-sdk android:minSdkVersion="19"></uses-sdk>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
diff --git a/development/ide/eclipse/.classpath b/development/ide/eclipse/.classpath
index 2ddb4d8..b95d52a 100644
--- a/development/ide/eclipse/.classpath
+++ b/development/ide/eclipse/.classpath
@@ -90,6 +90,7 @@
<classpathentry kind="src" path="external/easymock/src"/>
<classpathentry kind="src" path="out/target/common/obj/APPS/CtsAccessibilityServiceTestCases_intermediates/src/src"/>
<classpathentry kind="src" path="out/target/common/obj/APPS/CtsTestStubs_intermediates/src/src"/>
+ <classpathentry kind="src" path="out/target/common/obj/APPS/CtsTestStubs_intermediates/src/renderscript/src"/>
<classpathentry kind="src" path="out/target/common/obj/APPS/CtsVerifier_intermediates/src"/>
<classpathentry kind="src" path="out/target/common/obj/APPS/CtsVerifierTests_intermediates/src"/>
</classpath>
diff --git a/hostsidetests/aadb/src/com/android/cts/aadb/TestDeviceFuncTest.java b/hostsidetests/aadb/src/com/android/cts/aadb/TestDeviceFuncTest.java
index b540048..ceb9072 100644
--- a/hostsidetests/aadb/src/com/android/cts/aadb/TestDeviceFuncTest.java
+++ b/hostsidetests/aadb/src/com/android/cts/aadb/TestDeviceFuncTest.java
@@ -30,12 +30,17 @@
import com.android.tradefed.util.RunUtil;
import com.android.tradefed.util.StreamUtil;
+import java.awt.image.BufferedImage;
+
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
+import java.io.InputStream;
import java.io.IOException;
+import javax.imageio.ImageIO;
+
/**
* Functional tests for adb connection
* <p/>
@@ -328,22 +333,21 @@
/**
* Basic test for {@link TestDevice#getScreenshot()}.
* <p/>
- * Grab a screenshot, save it to a file, and perform a cursory size check to ensure its valid.
+ * Grab a screenshot and perform a cursory size check to ensure its valid.
*/
public void testGetScreenshot() throws DeviceNotAvailableException, IOException {
- CLog.i(LOG_TAG, "testGetScreenshot");
InputStreamSource source = getDevice().getScreenshot();
assertNotNull(source);
- File tmpPngFile = FileUtil.createTempFile("screenshot", ".png");
+ InputStream inputStream = source.createInputStream();
try {
- FileUtil.writeToFile(source.createInputStream(), tmpPngFile);
- CLog.i("Created file at %s", tmpPngFile.getAbsolutePath());
- assertTrue("Saved png file is less than 10K - is it invalid?",
- tmpPngFile.length() > 10*1024);
- // TODO: add more stringent checks
+ BufferedImage screenshotImage = ImageIO.read(inputStream);
+ CLog.i(LOG_TAG, "testGetScreenshot w=%d, h=%d",
+ screenshotImage.getWidth(), screenshotImage.getHeight());
+ assertTrue(screenshotImage.getWidth() > 0);
+ assertTrue(screenshotImage.getHeight() > 0);
} finally {
- FileUtil.deleteFile(tmpPngFile);
- source.cancel();
+ StreamUtil.cancel(source);
+ StreamUtil.close(inputStream);
}
}
diff --git a/hostsidetests/appsecurity/.gitignore b/hostsidetests/appsecurity/.gitignore
new file mode 100644
index 0000000..5e56e04
--- /dev/null
+++ b/hostsidetests/appsecurity/.gitignore
@@ -0,0 +1 @@
+/bin
diff --git a/hostsidetests/appsecurity/src/com/android/cts/appsecurity/AppSecurityTests.java b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/AppSecurityTests.java
index 88b05fb..702002c 100644
--- a/hostsidetests/appsecurity/src/com/android/cts/appsecurity/AppSecurityTests.java
+++ b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/AppSecurityTests.java
@@ -40,6 +40,8 @@
*/
public class AppSecurityTests extends DeviceTestCase implements IBuildReceiver {
+ private static final String RUNNER = "android.support.test.runner.AndroidJUnitRunner";
+
// testSharedUidDifferentCerts constants
private static final String SHARED_UI_APK = "CtsSharedUidInstall.apk";
private static final String SHARED_UI_PKG = "com.android.cts.shareuidinstall";
@@ -509,7 +511,7 @@
String testMethodName) throws DeviceNotAvailableException {
RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(pkgName,
- getDevice().getIDevice());
+ RUNNER, getDevice().getIDevice());
if (testClassName != null && testMethodName != null) {
testRunner.setMethodName(testClassName, testMethodName);
}
@@ -560,7 +562,7 @@
throws DeviceNotAvailableException {
// TODO: move this to RemoteAndroidTestRunner once it supports users
final String cmd = "am instrument --user " + userId + " -w -r -e class " + testClassName
- + "#" + testMethodName + " " + pkgName + "/android.test.InstrumentationTestRunner";
+ + "#" + testMethodName + " " + pkgName + "/" + RUNNER;
Log.i(LOG_TAG, "Running " + cmd + " on " + getDevice().getSerialNumber());
CollectingTestListener listener = new CollectingTestListener();
diff --git a/hostsidetests/appsecurity/test-apps/AppAccessData/Android.mk b/hostsidetests/appsecurity/test-apps/AppAccessData/Android.mk
index 308992e..2c9c624 100644
--- a/hostsidetests/appsecurity/test-apps/AppAccessData/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/AppAccessData/Android.mk
@@ -21,6 +21,7 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_SDK_VERSION := current
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
LOCAL_PACKAGE_NAME := CtsAppAccessData
diff --git a/hostsidetests/appsecurity/test-apps/AppAccessData/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/AppAccessData/AndroidManifest.xml
index 3824ef3..ffc104e 100644
--- a/hostsidetests/appsecurity/test-apps/AppAccessData/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/AppAccessData/AndroidManifest.xml
@@ -26,7 +26,7 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.appaccessdata"
android:label="Test to create app data."/>
</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/AppWithData/Android.mk b/hostsidetests/appsecurity/test-apps/AppWithData/Android.mk
index 8bcb045..098ce9c 100644
--- a/hostsidetests/appsecurity/test-apps/AppWithData/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/AppWithData/Android.mk
@@ -21,6 +21,7 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_SDK_VERSION := current
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
LOCAL_PACKAGE_NAME := CtsAppWithData
diff --git a/hostsidetests/appsecurity/test-apps/AppWithData/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/AppWithData/AndroidManifest.xml
index 9decbcd..598ebe7 100644
--- a/hostsidetests/appsecurity/test-apps/AppWithData/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/AppWithData/AndroidManifest.xml
@@ -27,7 +27,7 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.appwithdata"
android:label="Test to create app data."/>
</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/ExternalStorageApp/Android.mk b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/Android.mk
index bc99560..afc8764 100644
--- a/hostsidetests/appsecurity/test-apps/ExternalStorageApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/Android.mk
@@ -18,6 +18,7 @@
LOCAL_MODULE_TAGS := tests
LOCAL_SDK_VERSION := current
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := CtsExternalStorageApp
diff --git a/hostsidetests/appsecurity/test-apps/ExternalStorageApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/AndroidManifest.xml
index 0ba6684..9d8f615 100644
--- a/hostsidetests/appsecurity/test-apps/ExternalStorageApp/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/AndroidManifest.xml
@@ -20,7 +20,7 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.externalstorageapp" />
</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/InstrumentationAppDiffCert/Android.mk b/hostsidetests/appsecurity/test-apps/InstrumentationAppDiffCert/Android.mk
index 268ac73..e8ce3b8 100644
--- a/hostsidetests/appsecurity/test-apps/InstrumentationAppDiffCert/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/InstrumentationAppDiffCert/Android.mk
@@ -21,6 +21,7 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_SDK_VERSION := current
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
LOCAL_PACKAGE_NAME := CtsInstrumentationAppDiffCert
diff --git a/hostsidetests/appsecurity/test-apps/InstrumentationAppDiffCert/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/InstrumentationAppDiffCert/AndroidManifest.xml
index 8102656..a958c4c 100644
--- a/hostsidetests/appsecurity/test-apps/InstrumentationAppDiffCert/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/InstrumentationAppDiffCert/AndroidManifest.xml
@@ -34,7 +34,7 @@
A self-instrumenting test runner, that will try to start the above instrumentation and
verify it fails.
-->
- <instrumentation android:name="android.test.InstrumentationTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.instrumentationdiffcertapp"
android:label="Test for instrumentation with different cert" />
</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/MultiUserStorageApp/Android.mk b/hostsidetests/appsecurity/test-apps/MultiUserStorageApp/Android.mk
index 48f88b8..1dd109a 100644
--- a/hostsidetests/appsecurity/test-apps/MultiUserStorageApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/MultiUserStorageApp/Android.mk
@@ -18,6 +18,7 @@
LOCAL_MODULE_TAGS := tests
LOCAL_SDK_VERSION := current
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
LOCAL_SRC_FILES := $(call all-java-files-under, src) \
../ExternalStorageApp/src/com/android/cts/externalstorageapp/CommonExternalStorageTest.java
diff --git a/hostsidetests/appsecurity/test-apps/MultiUserStorageApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/MultiUserStorageApp/AndroidManifest.xml
index 6ace31b..8b599f2 100644
--- a/hostsidetests/appsecurity/test-apps/MultiUserStorageApp/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/MultiUserStorageApp/AndroidManifest.xml
@@ -20,7 +20,7 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.multiuserstorageapp" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
diff --git a/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/Android.mk b/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/Android.mk
index 938b325..60d6fad 100644
--- a/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/Android.mk
@@ -21,6 +21,7 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_SDK_VERSION := current
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
LOCAL_PACKAGE_NAME := CtsPermissionDeclareApp
diff --git a/hostsidetests/appsecurity/test-apps/PermissionDeclareAppCompat/Android.mk b/hostsidetests/appsecurity/test-apps/PermissionDeclareAppCompat/Android.mk
index acdc20f..ba7285c 100644
--- a/hostsidetests/appsecurity/test-apps/PermissionDeclareAppCompat/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/PermissionDeclareAppCompat/Android.mk
@@ -21,6 +21,7 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_SDK_VERSION := 16
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
LOCAL_PACKAGE_NAME := CtsPermissionDeclareAppCompat
diff --git a/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/Android.mk b/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/Android.mk
index 44e4bef..3e392e3 100644
--- a/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/Android.mk
@@ -18,6 +18,7 @@
LOCAL_MODULE_TAGS := tests
LOCAL_SDK_VERSION := current
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
LOCAL_SRC_FILES := $(call all-java-files-under, src) \
../ExternalStorageApp/src/com/android/cts/externalstorageapp/CommonExternalStorageTest.java
diff --git a/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/AndroidManifest.xml
index f6582b9..03884c9 100644
--- a/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/AndroidManifest.xml
@@ -20,7 +20,7 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.readexternalstorageapp" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
diff --git a/hostsidetests/appsecurity/test-apps/SharedUidInstall/Android.mk b/hostsidetests/appsecurity/test-apps/SharedUidInstall/Android.mk
index 25ba1fe..76187ab 100644
--- a/hostsidetests/appsecurity/test-apps/SharedUidInstall/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/SharedUidInstall/Android.mk
@@ -21,6 +21,7 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_SDK_VERSION := current
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
LOCAL_PACKAGE_NAME := CtsSharedUidInstall
diff --git a/hostsidetests/appsecurity/test-apps/SharedUidInstallDiffCert/Android.mk b/hostsidetests/appsecurity/test-apps/SharedUidInstallDiffCert/Android.mk
index a00b009..c1422ec 100644
--- a/hostsidetests/appsecurity/test-apps/SharedUidInstallDiffCert/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/SharedUidInstallDiffCert/Android.mk
@@ -21,6 +21,7 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_SDK_VERSION := current
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
LOCAL_PACKAGE_NAME := CtsSharedUidInstallDiffCert
diff --git a/hostsidetests/appsecurity/test-apps/SimpleAppInstall/Android.mk b/hostsidetests/appsecurity/test-apps/SimpleAppInstall/Android.mk
index 3cd78cf..fb925cd 100644
--- a/hostsidetests/appsecurity/test-apps/SimpleAppInstall/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/SimpleAppInstall/Android.mk
@@ -21,6 +21,7 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_SDK_VERSION := current
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
LOCAL_PACKAGE_NAME := CtsSimpleAppInstall
diff --git a/hostsidetests/appsecurity/test-apps/SimpleAppInstallDiffCert/Android.mk b/hostsidetests/appsecurity/test-apps/SimpleAppInstallDiffCert/Android.mk
index 5fbc910..2224d72 100644
--- a/hostsidetests/appsecurity/test-apps/SimpleAppInstallDiffCert/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/SimpleAppInstallDiffCert/Android.mk
@@ -21,6 +21,7 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_SDK_VERSION := current
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
LOCAL_PACKAGE_NAME := CtsSimpleAppInstallDiffCert
diff --git a/hostsidetests/appsecurity/test-apps/TargetInstrumentationApp/Android.mk b/hostsidetests/appsecurity/test-apps/TargetInstrumentationApp/Android.mk
index cc87e29..38a0511 100644
--- a/hostsidetests/appsecurity/test-apps/TargetInstrumentationApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/TargetInstrumentationApp/Android.mk
@@ -21,6 +21,7 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_SDK_VERSION := current
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
LOCAL_PACKAGE_NAME := CtsTargetInstrumentationApp
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/Android.mk b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/Android.mk
index d836042..8878c47 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/Android.mk
@@ -22,6 +22,7 @@
../PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/GrantUriPermission.java
LOCAL_SDK_VERSION := current
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
LOCAL_PACKAGE_NAME := CtsUsePermissionDiffCert
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/AndroidManifest.xml
index b7307bb..7acf98f 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/AndroidManifest.xml
@@ -38,5 +38,5 @@
</application>
<instrumentation android:targetPackage="com.android.cts.usespermissiondiffcertapp"
- android:name="android.test.InstrumentationTestRunner"/>
+ android:name="android.support.test.runner.AndroidJUnitRunner"/>
</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java
index db26eec..8629a87 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java
@@ -591,6 +591,7 @@
grantIntent.setClass(getContext(),
service ? ReceiveUriService.class : ReceiveUriActivity.class);
Intent intent = new Intent();
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
intent.setComponent(GRANT_URI_PERM_COMP);
intent.setAction(service ? GrantUriPermission.ACTION_START_SERVICE
: GrantUriPermission.ACTION_START_ACTIVITY);
@@ -598,6 +599,27 @@
getContext().sendBroadcast(intent);
}
+ private void grantClipUriPermissionViaContext(Uri uri, int mode) {
+ Intent intent = new Intent();
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ intent.setComponent(GRANT_URI_PERM_COMP);
+ intent.setAction(GrantUriPermission.ACTION_GRANT_URI);
+ intent.putExtra(GrantUriPermission.EXTRA_PACKAGE_NAME, getContext().getPackageName());
+ intent.putExtra(GrantUriPermission.EXTRA_URI, uri);
+ intent.putExtra(GrantUriPermission.EXTRA_MODE, mode);
+ getContext().sendBroadcast(intent);
+ }
+
+ private void revokeClipUriPermissionViaContext(Uri uri, int mode) {
+ Intent intent = new Intent();
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ intent.setComponent(GRANT_URI_PERM_COMP);
+ intent.setAction(GrantUriPermission.ACTION_REVOKE_URI);
+ intent.putExtra(GrantUriPermission.EXTRA_URI, uri);
+ intent.putExtra(GrantUriPermission.EXTRA_MODE, mode);
+ getContext().sendBroadcast(intent);
+ }
+
private void assertReadingClipAllowed(ClipData clip) {
for (int i=0; i<clip.getItemCount(); i++) {
ClipData.Item item = clip.getItemAt(i);
@@ -1355,4 +1377,166 @@
getContext().sendOrderedBroadcast(intent, null, receiver, null, 0, null, null);
receiver.assertSuccess("unexpected outgoing persisted Uri status");
}
+
+ /**
+ * Validate behavior of prefix permission grants.
+ */
+ public void testGrantPrefixUriPermission() throws Exception {
+ final Uri target = Uri.withAppendedPath(PERM_URI_GRANTING, "foo1");
+ final Uri targetMeow = Uri.withAppendedPath(target, "meow");
+ final Uri targetMeowCat = Uri.withAppendedPath(targetMeow, "cat");
+
+ final ClipData clip = makeSingleClipData(target);
+ final ClipData clipMeow = makeSingleClipData(targetMeow);
+ final ClipData clipMeowCat = makeSingleClipData(targetMeowCat);
+
+ // Make sure we can't see the target
+ assertReadingClipNotAllowed(clip, "reading should have failed");
+ assertWritingClipNotAllowed(clip, "writing should have failed");
+
+ // Give ourselves prefix read access
+ ReceiveUriActivity.clearStarted();
+ grantClipUriPermission(clipMeow, Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION, false);
+ ReceiveUriActivity.waitForStart();
+
+ // Verify prefix read access
+ assertReadingClipNotAllowed(clip, "reading should have failed");
+ assertReadingClipAllowed(clipMeow);
+ assertReadingClipAllowed(clipMeowCat);
+ assertWritingClipNotAllowed(clip, "writing should have failed");
+ assertWritingClipNotAllowed(clipMeow, "writing should have failed");
+ assertWritingClipNotAllowed(clipMeowCat, "writing should have failed");
+
+ // Now give ourselves exact write access
+ ReceiveUriActivity.clearNewIntent();
+ grantClipUriPermission(clip, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, false);
+ ReceiveUriActivity.waitForNewIntent();
+
+ // Verify we have exact write access, but not prefix write
+ assertReadingClipNotAllowed(clip, "reading should have failed");
+ assertReadingClipAllowed(clipMeow);
+ assertReadingClipAllowed(clipMeowCat);
+ assertWritingClipAllowed(clip);
+ assertWritingClipNotAllowed(clipMeow, "writing should have failed");
+ assertWritingClipNotAllowed(clipMeowCat, "writing should have failed");
+
+ ReceiveUriActivity.finishCurInstanceSync();
+ }
+
+ public void testGrantPersistablePrefixUriPermission() {
+ final ContentResolver resolver = getContext().getContentResolver();
+
+ final Uri target = Uri.withAppendedPath(PERM_URI_GRANTING, "foo2");
+ final Uri targetMeow = Uri.withAppendedPath(target, "meow");
+
+ final ClipData clip = makeSingleClipData(target);
+ final ClipData clipMeow = makeSingleClipData(targetMeow);
+
+ // Make sure we can't see the target
+ assertReadingClipNotAllowed(clip, "reading should have failed");
+
+ // Give ourselves prefix read access
+ ReceiveUriActivity.clearStarted();
+ grantClipUriPermission(clip, Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+ | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION, false);
+ ReceiveUriActivity.waitForStart();
+
+ // Verify prefix read access
+ assertReadingClipAllowed(clip);
+ assertReadingClipAllowed(clipMeow);
+
+ // Verify we can persist direct grant
+ long before = System.currentTimeMillis();
+ resolver.takePersistableUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ long after = System.currentTimeMillis();
+ assertPersistedUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION, before, after);
+
+ // But we can't take anywhere under the prefix
+ try {
+ resolver.takePersistableUriPermission(targetMeow,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ fail("taking under prefix should have failed");
+ } catch (SecurityException expected) {
+ }
+
+ // Should still have access regardless of taking
+ assertReadingClipAllowed(clip);
+ assertReadingClipAllowed(clipMeow);
+
+ // And clean up our grants
+ resolver.releasePersistableUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ assertNoPersistedUriPermission();
+
+ ReceiveUriActivity.finishCurInstanceSync();
+ }
+
+ /**
+ * Validate behavior of directly granting/revoking permission grants.
+ */
+ public void testDirectGrantRevokeUriPermission() throws Exception {
+ final ContentResolver resolver = getContext().getContentResolver();
+
+ final Uri target = Uri.withAppendedPath(PERM_URI_GRANTING, "foo3");
+ final Uri targetMeow = Uri.withAppendedPath(target, "meow");
+ final Uri targetMeowCat = Uri.withAppendedPath(targetMeow, "cat");
+
+ final ClipData clip = makeSingleClipData(target);
+ final ClipData clipMeow = makeSingleClipData(targetMeow);
+ final ClipData clipMeowCat = makeSingleClipData(targetMeowCat);
+
+ // Make sure we can't see the target
+ assertReadingClipNotAllowed(clipMeow, "reading should have failed");
+ assertWritingClipNotAllowed(clipMeow, "writing should have failed");
+
+ // Give ourselves some grants:
+ // /meow/cat WRITE|PERSISTABLE
+ // /meow READ|PREFIX
+ // /meow WRITE
+ grantClipUriPermissionViaContext(targetMeowCat, Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+ | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
+ grantClipUriPermissionViaContext(targetMeow, Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
+ grantClipUriPermissionViaContext(targetMeow, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ SystemClock.sleep(2000);
+
+ long before = System.currentTimeMillis();
+ resolver.takePersistableUriPermission(targetMeowCat, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ long after = System.currentTimeMillis();
+ assertPersistedUriPermission(targetMeowCat, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, before, after);
+
+ // Verify they look good
+ assertReadingClipNotAllowed(clip, "reading should have failed");
+ assertReadingClipAllowed(clipMeow);
+ assertReadingClipAllowed(clipMeowCat);
+ assertWritingClipNotAllowed(clip, "writing should have failed");
+ assertWritingClipAllowed(clipMeow);
+ assertWritingClipAllowed(clipMeowCat);
+
+ // Revoke anyone with write under meow
+ revokeClipUriPermissionViaContext(targetMeow, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ SystemClock.sleep(2000);
+
+ // This should have nuked persisted permission at lower level, but it
+ // shoulnd't have touched our prefix read.
+ assertReadingClipNotAllowed(clip, "reading should have failed");
+ assertReadingClipAllowed(clipMeow);
+ assertReadingClipAllowed(clipMeowCat);
+ assertWritingClipNotAllowed(clip, "writing should have failed");
+ assertWritingClipNotAllowed(clipMeow, "writing should have failed");
+ assertWritingClipNotAllowed(clipMeowCat, "writing should have failed");
+ assertNoPersistedUriPermission();
+
+ // Revoking read at top of tree should nuke everything else
+ revokeClipUriPermissionViaContext(target, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ SystemClock.sleep(2000);
+ assertReadingClipNotAllowed(clip, "reading should have failed");
+ assertReadingClipNotAllowed(clipMeow, "reading should have failed");
+ assertReadingClipNotAllowed(clipMeowCat, "reading should have failed");
+ assertWritingClipNotAllowed(clip, "writing should have failed");
+ assertWritingClipNotAllowed(clipMeow, "writing should have failed");
+ assertWritingClipNotAllowed(clipMeowCat, "writing should have failed");
+ assertNoPersistedUriPermission();
+ }
}
diff --git a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/Android.mk b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/Android.mk
index 4352bfb..a98fcea 100644
--- a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/Android.mk
@@ -18,6 +18,7 @@
LOCAL_MODULE_TAGS := tests
LOCAL_SDK_VERSION := current
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
LOCAL_SRC_FILES := $(call all-java-files-under, src) \
../ExternalStorageApp/src/com/android/cts/externalstorageapp/CommonExternalStorageTest.java
diff --git a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/AndroidManifest.xml
index 82910aa..37e39e9 100644
--- a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/AndroidManifest.xml
@@ -20,7 +20,7 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.writeexternalstorageapp" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
diff --git a/hostsidetests/holo/app/Android.mk b/hostsidetests/holo/app/Android.mk
index a5a7bf1..d390418 100644
--- a/hostsidetests/holo/app/Android.mk
+++ b/hostsidetests/holo/app/Android.mk
@@ -26,7 +26,7 @@
LOCAL_PROGUARD_ENABLED := disabled
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/hostsidetests/holo/app/AndroidManifest.xml b/hostsidetests/holo/app/AndroidManifest.xml
index 70e908c..d27c266 100755
--- a/hostsidetests/holo/app/AndroidManifest.xml
+++ b/hostsidetests/holo/app/AndroidManifest.xml
@@ -34,7 +34,7 @@
</application>
<!-- self-instrumenting test package. -->
- <instrumentation android:name="android.test.InstrumentationTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="android.holo.app"
android:label="Generates Holo reference images"/>
diff --git a/hostsidetests/usb/SerialTestApp/Android.mk b/hostsidetests/usb/SerialTestApp/Android.mk
index d36b98e..a8f51ad 100644
--- a/hostsidetests/usb/SerialTestApp/Android.mk
+++ b/hostsidetests/usb/SerialTestApp/Android.mk
@@ -20,8 +20,6 @@
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/hostsidetests/usb/SerialTestApp/AndroidManifest.xml b/hostsidetests/usb/SerialTestApp/AndroidManifest.xml
index 0667d60..a75dd75 100644
--- a/hostsidetests/usb/SerialTestApp/AndroidManifest.xml
+++ b/hostsidetests/usb/SerialTestApp/AndroidManifest.xml
@@ -22,5 +22,5 @@
</application>
<instrumentation
android:targetPackage="com.android.cts.usb.serialtest"
- android:name="android.test.InstrumentationCtsTestRunner" />
+ android:name="android.support.test.runner.AndroidJUnitRunner" />
</manifest>
diff --git a/hostsidetests/usb/src/com/android/cts/usb/TestUsbTest.java b/hostsidetests/usb/src/com/android/cts/usb/TestUsbTest.java
index f53d210..a8ac3e0 100644
--- a/hostsidetests/usb/src/com/android/cts/usb/TestUsbTest.java
+++ b/hostsidetests/usb/src/com/android/cts/usb/TestUsbTest.java
@@ -51,7 +51,7 @@
public class TestUsbTest extends DeviceTestCase implements IBuildReceiver {
private static final String LOG_TAG = "TestUsbTest";
- private static final String CTS_RUNNER = "android.test.InstrumentationCtsTestRunner";
+ private static final String CTS_RUNNER = "android.support.test.runner.AndroidJUnitRunner";
private static final String PACKAGE_NAME = "com.android.cts.usb.serialtest";
private static final String APK_NAME="CtsUsbSerialTestApp.apk";
private ITestDevice mDevice;
diff --git a/libs/deviceutil/Android.mk b/libs/deviceutil/Android.mk
index 1b7db18..d5a2c57 100644
--- a/libs/deviceutil/Android.mk
+++ b/libs/deviceutil/Android.mk
@@ -20,12 +20,12 @@
$(call all-java-files-under, src) \
$(call all-java-files-under, ../commonutil/src)
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE := ctsdeviceutil
+LOCAL_SDK_VERSION := current
+
include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/libs/runner/Android.mk b/libs/runner/Android.mk
index 629c1c2..9642f53 100644
--- a/libs/runner/Android.mk
+++ b/libs/runner/Android.mk
@@ -18,10 +18,12 @@
LOCAL_SRC_FILES := $(call all-java-files-under, ../../tests/core/runner/src)
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE := ctstestrunner
+LOCAL_SDK_VERSION := current
+
include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/libs/testserver/src/android/webkit/cts/CtsTestServer.java b/libs/testserver/src/android/webkit/cts/CtsTestServer.java
index 928a68e..6416b73 100644
--- a/libs/testserver/src/android/webkit/cts/CtsTestServer.java
+++ b/libs/testserver/src/android/webkit/cts/CtsTestServer.java
@@ -85,6 +85,7 @@
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLSession;
import javax.net.ssl.X509TrustManager;
@@ -101,20 +102,20 @@
private static final String DOWNLOAD_ID_PARAMETER = "downloadId";
private static final String NUM_BYTES_PARAMETER = "numBytes";
- public static final String ASSET_PREFIX = "/assets/";
- public static final String RAW_PREFIX = "raw/";
- public static final String FAVICON_ASSET_PATH = ASSET_PREFIX + "webkit/favicon.png";
- public static final String APPCACHE_PATH = "/appcache.html";
- public static final String APPCACHE_MANIFEST_PATH = "/appcache.manifest";
- public static final String REDIRECT_PREFIX = "/redirect";
- public static final String QUERY_REDIRECT_PATH = "/alt_redirect";
- public static final String DELAY_PREFIX = "/delayed";
- public static final String BINARY_PREFIX = "/binary";
- public static final String COOKIE_PREFIX = "/cookie";
- public static final String AUTH_PREFIX = "/auth";
- public static final String SHUTDOWN_PREFIX = "/shutdown";
+ private static final String ASSET_PREFIX = "/assets/";
+ private static final String RAW_PREFIX = "raw/";
+ private static final String FAVICON_ASSET_PATH = ASSET_PREFIX + "webkit/favicon.png";
+ private static final String APPCACHE_PATH = "/appcache.html";
+ private static final String APPCACHE_MANIFEST_PATH = "/appcache.manifest";
+ private static final String REDIRECT_PREFIX = "/redirect";
+ private static final String QUERY_REDIRECT_PATH = "/alt_redirect";
+ private static final String DELAY_PREFIX = "/delayed";
+ private static final String BINARY_PREFIX = "/binary";
+ private static final String COOKIE_PREFIX = "/cookie";
+ private static final String AUTH_PREFIX = "/auth";
+ private static final String SHUTDOWN_PREFIX = "/shutdown";
public static final String NOLENGTH_POSTFIX = "nolength";
- public static final int DELAY_MILLIS = 2000;
+ private static final int DELAY_MILLIS = 2000;
public static final String AUTH_REALM = "Android CTS";
public static final String AUTH_USER = "cts";
@@ -126,6 +127,13 @@
public static final String MESSAGE_403 = "403 forbidden";
public static final String MESSAGE_404 = "404 not found";
+ public enum SslMode {
+ INSECURE,
+ NO_CLIENT_AUTH,
+ WANTS_CLIENT_AUTH,
+ NEEDS_CLIENT_AUTH,
+ }
+
private static Hashtable<Integer, String> sReasons;
private ServerThread mServerThread;
@@ -133,7 +141,7 @@
private AssetManager mAssets;
private Context mContext;
private Resources mResources;
- private boolean mSsl;
+ private SslMode mSsl;
private MimeTypeMap mMap;
private Vector<String> mQueries;
private ArrayList<HttpEntity> mRequestEntities;
@@ -168,19 +176,30 @@
* @throws Exception
*/
public CtsTestServer(Context context, boolean ssl) throws Exception {
+ this(context, ssl ? SslMode.NO_CLIENT_AUTH : SslMode.INSECURE);
+ }
+
+ /**
+ * Create and start a local HTTP server instance.
+ * @param context The application context to use for fetching assets.
+ * @param sslMode Whether to use SSL, and if so, what client auth (if any) to use.
+ * @throws Exception
+ */
+ public CtsTestServer(Context context, SslMode sslMode) throws Exception {
mContext = context;
mAssets = mContext.getAssets();
mResources = mContext.getResources();
- mSsl = ssl;
+ mSsl = sslMode;
mRequestEntities = new ArrayList<HttpEntity>();
mMap = MimeTypeMap.getSingleton();
mQueries = new Vector<String>();
mServerThread = new ServerThread(this, mSsl);
- if (mSsl) {
- mServerUri = "https://localhost:" + mServerThread.mSocket.getLocalPort();
+ if (mSsl == SslMode.INSECURE) {
+ mServerUri = "http:";
} else {
- mServerUri = "http://localhost:" + mServerThread.mSocket.getLocalPort();
+ mServerUri = "https:";
}
+ mServerUri += "//localhost:" + mServerThread.mSocket.getLocalPort();
mServerThread.start();
}
@@ -219,7 +238,9 @@
private URLConnection openConnection(URL url)
throws IOException, NoSuchAlgorithmException, KeyManagementException {
- if (mSsl) {
+ if (mSsl == SslMode.INSECURE) {
+ return url.openConnection();
+ } else {
// Install hostname verifiers and trust managers that don't do
// anything in order to get around the client not trusting
// the test server due to a lack of certificates.
@@ -228,13 +249,14 @@
connection.setHostnameVerifier(new CtsHostnameVerifier());
SSLContext context = SSLContext.getInstance("TLS");
- CtsTrustManager trustManager = new CtsTrustManager();
- context.init(null, new CtsTrustManager[] {trustManager}, null);
+ try {
+ context.init(ServerThread.getKeyManagers(), getTrustManagers(), null);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
connection.setSSLSocketFactory(context.getSocketFactory());
return connection;
- } else {
- return url.openConnection();
}
}
@@ -246,7 +268,7 @@
*/
private static class CtsTrustManager implements X509TrustManager {
public void checkClientTrusted(X509Certificate[] chain, String authType) {
- // Trust the CtSTestServer...
+ // Trust the CtSTestServer's client...
}
public void checkServerTrusted(X509Certificate[] chain, String authType) {
@@ -259,6 +281,13 @@
}
/**
+ * @returns a trust manager array configured to permit any trust decision.
+ */
+ private static CtsTrustManager[] getTrustManagers() {
+ return new CtsTrustManager[] { new CtsTrustManager() };
+ }
+
+ /**
* {@link HostnameVerifier} that verifies everybody. This permits
* the client to trust the web server and call
* {@link CtsTestServer#shutdown()}.
@@ -293,8 +322,20 @@
* @param path The path of the asset. See {@link AssetManager#open(String)}
*/
public String getDelayedAssetUrl(String path) {
+ return getDelayedAssetUrl(path, DELAY_MILLIS);
+ }
+
+ /**
+ * Return an artificially delayed absolute URL that refers to the given asset. This can be
+ * used to emulate a slow HTTP server or connection.
+ * @param path The path of the asset. See {@link AssetManager#open(String)}
+ * @param delayMs The number of milliseconds to delay the request
+ */
+ public String getDelayedAssetUrl(String path, int delayMs) {
StringBuilder sb = new StringBuilder(getBaseUri());
sb.append(DELAY_PREFIX);
+ sb.append("/");
+ sb.append(delayMs);
sb.append(ASSET_PREFIX);
sb.append(path);
return sb.toString();
@@ -520,12 +561,14 @@
path = FAVICON_ASSET_PATH;
}
if (path.startsWith(DELAY_PREFIX)) {
+ String delayPath = path.substring(DELAY_PREFIX.length() + 1);
+ String delay = delayPath.substring(0, delayPath.indexOf('/'));
+ path = delayPath.substring(delay.length());
try {
- Thread.sleep(DELAY_MILLIS);
+ Thread.sleep(Integer.valueOf(delay));
} catch (InterruptedException ignored) {
// ignore
}
- path = path.substring(DELAY_PREFIX.length());
}
if (path.startsWith(AUTH_PREFIX)) {
// authentication required
@@ -561,6 +604,8 @@
response = createResponse(HttpStatus.SC_OK);
response.setEntity(entity);
response.addHeader("Content-Disposition", "attachment; filename=test.bin");
+ response.addHeader("Content-Type", mimeType);
+ response.addHeader("Content-Length", "" + length);
} else {
// fall through, return 404 at the end
}
@@ -794,7 +839,7 @@
private static class ServerThread extends Thread {
private CtsTestServer mServer;
private ServerSocket mSocket;
- private boolean mIsSsl;
+ private SslMode mSsl;
private boolean mIsCancelled;
private SSLContext mSslContext;
private ExecutorService mExecutorService = Executors.newFixedThreadPool(20);
@@ -829,13 +874,13 @@
"1gaEjsC/0wGmmBDg1dTDH+F1p9TInzr3EFuYD0YiQ7YlAHq3cPuyGoLXJ5dXYuSBfhDXJSeddUkl" +
"k1ufZyOOcskeInQge7jzaRfmKg3U94r+spMEvb0AzDQVOKvjjo1ivxMSgFRZaDb/4qw=";
- private String PASSWORD = "android";
+ private static final String PASSWORD = "android";
/**
* Loads a keystore from a base64-encoded String. Returns the KeyManager[]
* for the result.
*/
- private KeyManager[] getKeyManagers() throws Exception {
+ private static KeyManager[] getKeyManagers() throws Exception {
byte[] bytes = Base64.decode(SERVER_KEYS_BKS.getBytes());
InputStream inputStream = new ByteArrayInputStream(bytes);
@@ -851,19 +896,24 @@
}
- public ServerThread(CtsTestServer server, boolean ssl) throws Exception {
+ public ServerThread(CtsTestServer server, SslMode sslMode) throws Exception {
super("ServerThread");
mServer = server;
- mIsSsl = ssl;
+ mSsl = sslMode;
int retry = 3;
while (true) {
try {
- if (mIsSsl) {
- mSslContext = SSLContext.getInstance("TLS");
- mSslContext.init(getKeyManagers(), null, null);
- mSocket = mSslContext.getServerSocketFactory().createServerSocket(0);
- } else {
+ if (mSsl == SslMode.INSECURE) {
mSocket = new ServerSocket(0);
+ } else { // Use SSL
+ mSslContext = SSLContext.getInstance("TLS");
+ mSslContext.init(getKeyManagers(), getTrustManagers(), null);
+ mSocket = mSslContext.getServerSocketFactory().createServerSocket(0);
+ if (mSsl == SslMode.WANTS_CLIENT_AUTH) {
+ ((SSLServerSocket) mSocket).setWantClientAuth(true);
+ } else if (mSsl == SslMode.NEEDS_CLIENT_AUTH) {
+ ((SSLServerSocket) mSocket).setNeedClientAuth(true);
+ }
}
return;
} catch (IOException e) {
diff --git a/suite/audio_quality/test_description/dut_playback_sample.xml b/suite/audio_quality/test_description/dut_playback_sample.xml
new file mode 100644
index 0000000..f78209e
--- /dev/null
+++ b/suite/audio_quality/test_description/dut_playback_sample.xml
@@ -0,0 +1,38 @@
+<?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.
+-->
+
+<case name="dut_playback_sample" version="1.0" description="Sample test which check frequency of DUT's playback">
+ <setup>
+ <!-- prepare sound source id: to be used in output, sine 1000Hz, 4000ms long -->
+ <sound id="sound1" type="sin:32000:1000:4000" preload="1" />
+ </setup>
+ <action>
+ <sequential repeat="1" index="i">
+ <output device="DUT" id="sound1" gain="100" sync="start" waitforcompletion="0" />
+ <sequential repeat="1" index="j">
+ <!-- dummy recording to compensate for possible playback latency -->
+ <input device="host" id="dummy" gain="100" time="1000" sync="complete" />
+ <input device="host" id="host_in_$j" gain="100" time="2000" sync="complete" />
+ </sequential>
+ </sequential>
+ <sequential repeat="1" index="k">
+ <!-- input: host record, signal frequency in Hz, threshold, output: frequency calculated -->
+ <process method="script:playback_sample" input="id:host_in_$k,consti:1000,constf:5.0" output="val:freq_device_$k" />
+ </sequential>
+ </action>
+ <save file="host_in_.*" report="freq_device_.*" />
+</case>
diff --git a/suite/audio_quality/test_description/processing/playback_sample.py b/suite/audio_quality/test_description/processing/playback_sample.py
new file mode 100644
index 0000000..79e8d53
--- /dev/null
+++ b/suite/audio_quality/test_description/processing/playback_sample.py
@@ -0,0 +1,63 @@
+#!/usr/bin/python
+
+# 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.
+
+from consts import *
+
+# Sample test for dut_playback_sample case
+# Input: host recording (mono),
+# frequency of sine in Hz (i64)
+# pass level threshold (double)
+# Output: device (double) frequency
+
+def playback_sample(inputData, inputTypes):
+ output = []
+ outputData = []
+ outputTypes = []
+ # basic sanity check
+ inputError = False
+ if (inputTypes[0] != TYPE_MONO):
+ inputError = True
+ if (inputTypes[1] != TYPE_I64):
+ inputError = True
+ if (inputTypes[2] != TYPE_DOUBLE):
+ inputError = True
+ if inputError:
+ output.append(RESULT_ERROR)
+ output.append(outputData)
+ output.append(outputTypes)
+ return output
+
+ hostRecording = inputData[0]
+ signalFrequency = inputData[1]
+ threshold = inputData[2]
+ samplingRate = 44100
+
+ freq = calc_freq(hostRecording, samplingRate)
+ print "Expected Freq ", signalFrequency, "Actual Freq ", freq, "Threshold % ", threshold
+ diff = abs(freq - signalFrequency)
+ if (diff < threshold):
+ output.append(RESULT_PASS)
+ else:
+ output.append(RESULT_OK)
+ outputData.append(freq)
+ outputTypes.append(TYPE_DOUBLE)
+ output.append(outputData)
+ output.append(outputTypes)
+ return output
+
+def calc_freq(recording, samplingRate):
+ #This would calculate the frequency of recording, but is skipped in this sample test for brevity
+ return 32000
\ No newline at end of file
diff --git a/suite/cts/deviceTests/browserbench/Android.mk b/suite/cts/deviceTests/browserbench/Android.mk
index 6b241a9..3696bcd 100644
--- a/suite/cts/deviceTests/browserbench/Android.mk
+++ b/suite/cts/deviceTests/browserbench/Android.mk
@@ -18,8 +18,6 @@
# don't include this package in any target
LOCAL_MODULE_TAGS := optional
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil ctstestrunner ctstestserver
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/suite/cts/deviceTests/browserbench/AndroidManifest.xml b/suite/cts/deviceTests/browserbench/AndroidManifest.xml
index 16626ad..4bf5b5e 100644
--- a/suite/cts/deviceTests/browserbench/AndroidManifest.xml
+++ b/suite/cts/deviceTests/browserbench/AndroidManifest.xml
@@ -24,6 +24,6 @@
<application>
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.browser" />
</manifest>
diff --git a/suite/cts/deviceTests/dram/Android.mk b/suite/cts/deviceTests/dram/Android.mk
index 861a313..13de747 100644
--- a/suite/cts/deviceTests/dram/Android.mk
+++ b/suite/cts/deviceTests/dram/Android.mk
@@ -18,8 +18,6 @@
# don't include this package in any target
LOCAL_MODULE_TAGS := tests
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil ctstestrunner
LOCAL_JNI_SHARED_LIBRARIES := libctsdram_jni
diff --git a/suite/cts/deviceTests/dram/AndroidManifest.xml b/suite/cts/deviceTests/dram/AndroidManifest.xml
index 70f6b11..c9aaf3d 100644
--- a/suite/cts/deviceTests/dram/AndroidManifest.xml
+++ b/suite/cts/deviceTests/dram/AndroidManifest.xml
@@ -23,7 +23,7 @@
<application>
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.dram"
android:label="DRAM bandwidth measurement" />
</manifest>
diff --git a/suite/cts/deviceTests/filesystemperf/Android.mk b/suite/cts/deviceTests/filesystemperf/Android.mk
index 5f0606e..843d21a 100644
--- a/suite/cts/deviceTests/filesystemperf/Android.mk
+++ b/suite/cts/deviceTests/filesystemperf/Android.mk
@@ -18,8 +18,6 @@
# don't include this package in any target
LOCAL_MODULE_TAGS := optional
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil ctstestrunner
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/suite/cts/deviceTests/filesystemperf/AndroidManifest.xml b/suite/cts/deviceTests/filesystemperf/AndroidManifest.xml
index dc90a94..329bf19 100644
--- a/suite/cts/deviceTests/filesystemperf/AndroidManifest.xml
+++ b/suite/cts/deviceTests/filesystemperf/AndroidManifest.xml
@@ -23,7 +23,7 @@
<application>
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.filesystemperf"
android:label="UI Latency measurement" />
</manifest>
diff --git a/suite/cts/deviceTests/opengl/Android.mk b/suite/cts/deviceTests/opengl/Android.mk
index 8617436..7e93dd7 100644
--- a/suite/cts/deviceTests/opengl/Android.mk
+++ b/suite/cts/deviceTests/opengl/Android.mk
@@ -18,8 +18,6 @@
# don't include this package in any target
LOCAL_MODULE_TAGS := tests
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil ctstestrunner
LOCAL_JNI_SHARED_LIBRARIES := libctsopengl_jni
diff --git a/suite/cts/deviceTests/opengl/AndroidManifest.xml b/suite/cts/deviceTests/opengl/AndroidManifest.xml
index a0273ec..05fb5be 100644
--- a/suite/cts/deviceTests/opengl/AndroidManifest.xml
+++ b/suite/cts/deviceTests/opengl/AndroidManifest.xml
@@ -46,7 +46,7 @@
</application>
<instrumentation
- android:name="android.test.InstrumentationCtsTestRunner"
+ android:name="android.support.test.runner.AndroidJUnitRunner"
android:label="OpenGL ES 2.0 Benchmark"
android:targetPackage="com.android.cts.opengl" />
diff --git a/suite/cts/deviceTests/opengl/src/com/android/cts/opengl/primitive/GLPrimitiveBenchmark.java b/suite/cts/deviceTests/opengl/src/com/android/cts/opengl/primitive/GLPrimitiveBenchmark.java
index 6c2c87d..c177129 100644
--- a/suite/cts/deviceTests/opengl/src/com/android/cts/opengl/primitive/GLPrimitiveBenchmark.java
+++ b/suite/cts/deviceTests/opengl/src/com/android/cts/opengl/primitive/GLPrimitiveBenchmark.java
@@ -118,9 +118,8 @@
intent.putExtra(GLActivityIntentKeys.INTENT_EXTRA_NUM_ITERATIONS, numIterations);
intent.putExtra(GLActivityIntentKeys.INTENT_EXTRA_TIMEOUT, timeout);
- GLPrimitiveActivity activity = null;
setActivityIntent(intent);
- activity = getActivity();
+ GLPrimitiveActivity activity = getActivity();
if (activity != null) {
activity.waitForCompletion();
double[] fpsValues = activity.mFpsValues;
diff --git a/suite/cts/deviceTests/simplecpu/Android.mk b/suite/cts/deviceTests/simplecpu/Android.mk
index 0cc73cc..cc25223 100644
--- a/suite/cts/deviceTests/simplecpu/Android.mk
+++ b/suite/cts/deviceTests/simplecpu/Android.mk
@@ -18,8 +18,6 @@
# don't include this package in any target
LOCAL_MODULE_TAGS := tests
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil ctstestrunner
LOCAL_JNI_SHARED_LIBRARIES := libctscpu_jni
diff --git a/suite/cts/deviceTests/simplecpu/AndroidManifest.xml b/suite/cts/deviceTests/simplecpu/AndroidManifest.xml
index 69e4ad2..e5c1c2b 100644
--- a/suite/cts/deviceTests/simplecpu/AndroidManifest.xml
+++ b/suite/cts/deviceTests/simplecpu/AndroidManifest.xml
@@ -23,7 +23,7 @@
<application>
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.simplecpu"
android:label="Very simple CPU benchmarking" />
</manifest>
diff --git a/suite/cts/deviceTests/ui/Android.mk b/suite/cts/deviceTests/ui/Android.mk
index 17287b2..ee52172 100644
--- a/suite/cts/deviceTests/ui/Android.mk
+++ b/suite/cts/deviceTests/ui/Android.mk
@@ -20,8 +20,6 @@
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil ctstestrunner
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/suite/cts/deviceTests/ui/AndroidManifest.xml b/suite/cts/deviceTests/ui/AndroidManifest.xml
index 1be3eed..b41008e 100644
--- a/suite/cts/deviceTests/ui/AndroidManifest.xml
+++ b/suite/cts/deviceTests/ui/AndroidManifest.xml
@@ -36,8 +36,12 @@
</application>
<instrumentation
- android:name="android.test.InstrumentationCtsTestRunner"
+ android:name="android.support.test.runner.AndroidJUnitRunner"
android:label="UI Latency measurement"
- android:targetPackage="com.android.cts.ui" />
+ android:targetPackage="com.android.cts.ui" >
+ <meta-data
+ android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/suite/cts/deviceTests/videoperf/Android.mk b/suite/cts/deviceTests/videoperf/Android.mk
index 6ace48f..cb398a9 100644
--- a/suite/cts/deviceTests/videoperf/Android.mk
+++ b/suite/cts/deviceTests/videoperf/Android.mk
@@ -18,8 +18,6 @@
# don't include this package in any target
LOCAL_MODULE_TAGS := optional
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil ctstestrunner
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/suite/cts/deviceTests/videoperf/AndroidManifest.xml b/suite/cts/deviceTests/videoperf/AndroidManifest.xml
index 631141d..ca01298 100644
--- a/suite/cts/deviceTests/videoperf/AndroidManifest.xml
+++ b/suite/cts/deviceTests/videoperf/AndroidManifest.xml
@@ -23,7 +23,11 @@
<application>
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.videoperf"
- android:label="UI Latency measurement" />
+ android:label="UI Latency measurement" >
+ <meta-data
+ android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/suite/cts/hostTests/uihost/appA/Android.mk b/suite/cts/hostTests/uihost/appA/Android.mk
index 48d9009..3e76fdb 100644
--- a/suite/cts/hostTests/uihost/appA/Android.mk
+++ b/suite/cts/hostTests/uihost/appA/Android.mk
@@ -20,8 +20,6 @@
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil ctstestrunner
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/suite/cts/hostTests/uihost/appB/Android.mk b/suite/cts/hostTests/uihost/appB/Android.mk
index 812637e..13af40f 100644
--- a/suite/cts/hostTests/uihost/appB/Android.mk
+++ b/suite/cts/hostTests/uihost/appB/Android.mk
@@ -20,8 +20,6 @@
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil ctstestrunner
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/suite/cts/hostTests/uihost/control/Android.mk b/suite/cts/hostTests/uihost/control/Android.mk
index 565e2c0..3770918 100644
--- a/suite/cts/hostTests/uihost/control/Android.mk
+++ b/suite/cts/hostTests/uihost/control/Android.mk
@@ -20,8 +20,6 @@
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil ctstestrunner
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/suite/cts/hostTests/uihost/control/AndroidManifest.xml b/suite/cts/hostTests/uihost/control/AndroidManifest.xml
index 9901d50..e4b10f2 100644
--- a/suite/cts/hostTests/uihost/control/AndroidManifest.xml
+++ b/suite/cts/hostTests/uihost/control/AndroidManifest.xml
@@ -24,5 +24,9 @@
</application>
<instrumentation
android:targetPackage="com.android.cts.taskswitching.control"
- android:name="android.test.InstrumentationCtsTestRunner" />
+ android:name="android.support.test.runner.AndroidJUnitRunner" >
+ <meta-data
+ android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/Android.mk b/tests/Android.mk
index a4cc38f..973b093 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -30,8 +30,7 @@
# Resource unit tests use a private locale and some densities
LOCAL_AAPT_FLAGS = -c xx_YY -c cs -c small -c normal -c large -c xlarge \
-c 320dpi -c 240dpi -c 160dpi -c 32dpi \
- --preferred-configurations 320dpi --preferred-configurations 240dpi \
- --preferred-configurations 160dpi --preferred-configurations 32dpi
+ -c kok,kok_IN,kok_419,kok_419_VARIANT,kok_Knda_419,kok_Knda_419_VARIANT,kok_VARIANT,kok_Knda
LOCAL_PACKAGE_NAME := CtsTestStubs
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index 5348861..522eb6e 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -586,6 +586,12 @@
android:configChanges="keyboardHidden|orientation|screenSize">
</activity>
+ <activity android:name="android.hardware.camera2.cts.Camera2SurfaceViewStubActivity"
+ android:label="Camera2StubActivity"
+ android:screenOrientation="landscape"
+ android:configChanges="keyboardHidden|orientation|screenSize">
+ </activity>
+
<activity android:name="android.view.inputmethod.cts.InputMethodStubActivity"
android:label="InputMethodStubActivity">
<intent-filter>
diff --git a/tests/ProcessTest/Android.mk b/tests/ProcessTest/Android.mk
index a2958fe..5611b3b 100644
--- a/tests/ProcessTest/Android.mk
+++ b/tests/ProcessTest/Android.mk
@@ -20,8 +20,6 @@
# and when built explicitly put it in the data partition
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_AAPT_FLAGS = -c xx_YY -c cs
LOCAL_SRC_FILES := $(call all-subdir-java-files)
@@ -31,6 +29,8 @@
LOCAL_DEX_PREOPT := false
LOCAL_PROGUARD_ENABLED := disabled
+LOCAL_SDK_VERSION := current
+
include $(BUILD_PACKAGE)
include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/ProcessTest/AndroidManifest.xml b/tests/ProcessTest/AndroidManifest.xml
index b860bde..c7cf635 100644
--- a/tests/ProcessTest/AndroidManifest.xml
+++ b/tests/ProcessTest/AndroidManifest.xml
@@ -18,7 +18,7 @@
android:sharedUserId="com.android.cts.process.uidpid_test">
<!-- InstrumentationTestRunner for AndroidTests -->
- <instrumentation android:name="android.test.InstrumentationTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.process"
android:label="Test process"/>
<application>
diff --git a/tests/SignatureTest/Android.mk b/tests/SignatureTest/Android.mk
index 696f99e..fc794e8 100644
--- a/tests/SignatureTest/Android.mk
+++ b/tests/SignatureTest/Android.mk
@@ -20,12 +20,12 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_PACKAGE_NAME := SignatureTest
LOCAL_SDK_VERSION := current
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
# To be passed in on command line
CTS_API_VERSION ?= current
ifeq (current,$(CTS_API_VERSION))
diff --git a/tests/SignatureTest/AndroidManifest.xml b/tests/SignatureTest/AndroidManifest.xml
index b4813e6..89d8590 100644
--- a/tests/SignatureTest/AndroidManifest.xml
+++ b/tests/SignatureTest/AndroidManifest.xml
@@ -23,7 +23,7 @@
<uses-library android:name="android.test.runner"/>
</application>
- <instrumentation android:name="android.test.InstrumentationTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="android.tests.sigtest"
android:label="API Signature Test"/>
diff --git a/tests/SignatureTest/src/android/tests/sigtest/JDiffClassDescription.java b/tests/SignatureTest/src/android/tests/sigtest/JDiffClassDescription.java
index c51c6c3..36360d6 100644
--- a/tests/SignatureTest/src/android/tests/sigtest/JDiffClassDescription.java
+++ b/tests/SignatureTest/src/android/tests/sigtest/JDiffClassDescription.java
@@ -832,19 +832,14 @@
// Nothing to check if it doesn't extend anything.
if (mExtendedClass != null) {
Class<?> superClass = mClass.getSuperclass();
- if (superClass == null) {
- // API indicates superclass, reflection doesn't.
- return false;
- }
- if (superClass.getCanonicalName().equals(mExtendedClass)) {
- return true;
+ while (superClass != null) {
+ if (superClass.getCanonicalName().equals(mExtendedClass)) {
+ return true;
+ }
+ superClass = superClass.getSuperclass();
}
-
- if (mAbsoluteClassName.equals("android.hardware.SensorManager")) {
- // FIXME: Please see Issue 1496822 for more information
- return true;
- }
+ // Couldn't find a matching superclass.
return false;
}
return true;
diff --git a/tests/SignatureTest/tests/Android.mk b/tests/SignatureTest/tests/Android.mk
index bdd0a90..0796670 100644
--- a/tests/SignatureTest/tests/Android.mk
+++ b/tests/SignatureTest/tests/Android.mk
@@ -6,8 +6,6 @@
# and when built explicitly put it in the data partition
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
# Include all test java files.
LOCAL_SRC_FILES := $(call all-java-files-under, src)
@@ -19,4 +17,8 @@
LOCAL_DEX_PREOPT := false
+LOCAL_SDK_VERSION := current
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
include $(BUILD_PACKAGE)
diff --git a/tests/SignatureTest/tests/AndroidManifest.xml b/tests/SignatureTest/tests/AndroidManifest.xml
index ab8a6d6..49b3827 100644
--- a/tests/SignatureTest/tests/AndroidManifest.xml
+++ b/tests/SignatureTest/tests/AndroidManifest.xml
@@ -20,7 +20,7 @@
<uses-library android:name="android.test.runner"/>
</application>
- <instrumentation android:name="android.test.InstrumentationTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="android.tests.sigtest"
android:label="SignatureTest Functional Testset"/>
diff --git a/tests/acceleration/Android.mk b/tests/acceleration/Android.mk
index ef96a24..a6d6022 100644
--- a/tests/acceleration/Android.mk
+++ b/tests/acceleration/Android.mk
@@ -24,8 +24,6 @@
LOCAL_PROGUARD_ENABLED := disabled
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := CtsAccelerationTestStubs
diff --git a/tests/accessibility/AndroidManifest.xml b/tests/accessibility/AndroidManifest.xml
index 0d18cef..dde1de8 100644
--- a/tests/accessibility/AndroidManifest.xml
+++ b/tests/accessibility/AndroidManifest.xml
@@ -19,8 +19,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.view.accessibility.services">
- <uses-permission android:name="android.permission.CAN_REQUEST_TOUCH_EXPLORATION_MODE"/>
-
<application>
<service android:name=".SpeakingAccessibilityService"
diff --git a/tests/tests/security/assets/selinux_policy.xml b/tests/assets/selinux_policy.xml
similarity index 100%
rename from tests/tests/security/assets/selinux_policy.xml
rename to tests/assets/selinux_policy.xml
diff --git a/tests/assets/webkit/iframe_blank_tag.html b/tests/assets/webkit/iframe_blank_tag.html
index 55ff410..fbc6dc6 100644
--- a/tests/assets/webkit/iframe_blank_tag.html
+++ b/tests/assets/webkit/iframe_blank_tag.html
@@ -16,7 +16,7 @@
<!DOCTYPE html>
<html>
<head>
- <script type='text/javascript'> window.open('test_hello_world.html'); </script>
+ <script type='text/javascript'> window.open('page_with_link.html'); </script>
</head>
</html>
diff --git a/tests/assets/webkit/page_with_link.html b/tests/assets/webkit/page_with_link.html
new file mode 100644
index 0000000..50fb78a
--- /dev/null
+++ b/tests/assets/webkit/page_with_link.html
@@ -0,0 +1,20 @@
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<html>
+ <body>
+ <a href="http://foo.com" id="link">a link</a>
+ </body>
+</html>
diff --git a/tests/core/libcore/com/AndroidManifest.xml b/tests/core/libcore/com/AndroidManifest.xml
index 4e37ef4..8790a7e 100644
--- a/tests/core/libcore/com/AndroidManifest.xml
+++ b/tests/core/libcore/com/AndroidManifest.xml
@@ -22,8 +22,12 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="android.core.tests.runner"
- android:label="cts framework tests"/>
+ android:label="cts framework tests">
+ <meta-data
+ android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/core/libcore/conscrypt/AndroidManifest.xml b/tests/core/libcore/conscrypt/AndroidManifest.xml
index 6517a0b..b299793 100644
--- a/tests/core/libcore/conscrypt/AndroidManifest.xml
+++ b/tests/core/libcore/conscrypt/AndroidManifest.xml
@@ -22,8 +22,11 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="android.core.tests.runner"
- android:label="cts framework tests"/>
+ android:label="cts framework tests">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/core/libcore/dalvik/AndroidManifest.xml b/tests/core/libcore/dalvik/AndroidManifest.xml
index 6def32c..ca34678 100644
--- a/tests/core/libcore/dalvik/AndroidManifest.xml
+++ b/tests/core/libcore/dalvik/AndroidManifest.xml
@@ -22,8 +22,12 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="android.core.tests.runner"
- android:label="cts framework tests"/>
+ android:label="cts framework tests">
+ <meta-data
+ android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/core/libcore/harmony_annotation/AndroidManifest.xml b/tests/core/libcore/harmony_annotation/AndroidManifest.xml
index 0c59b1b..c83ecf2 100644
--- a/tests/core/libcore/harmony_annotation/AndroidManifest.xml
+++ b/tests/core/libcore/harmony_annotation/AndroidManifest.xml
@@ -22,8 +22,12 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="android.core.tests.runner"
- android:label="cts framework tests"/>
+ android:label="cts framework tests">
+ <meta-data
+ android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/core/libcore/harmony_beans/AndroidManifest.xml b/tests/core/libcore/harmony_beans/AndroidManifest.xml
index b4932dd..b9b161c 100644
--- a/tests/core/libcore/harmony_beans/AndroidManifest.xml
+++ b/tests/core/libcore/harmony_beans/AndroidManifest.xml
@@ -22,8 +22,12 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="android.core.tests.runner"
- android:label="cts framework tests"/>
+ android:label="cts framework tests">
+ <meta-data
+ android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/core/libcore/harmony_java_io/AndroidManifest.xml b/tests/core/libcore/harmony_java_io/AndroidManifest.xml
index 65d64ab..e69d4b4 100644
--- a/tests/core/libcore/harmony_java_io/AndroidManifest.xml
+++ b/tests/core/libcore/harmony_java_io/AndroidManifest.xml
@@ -22,8 +22,12 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="android.core.tests.runner"
- android:label="cts framework tests"/>
+ android:label="cts framework tests">
+ <meta-data
+ android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/core/libcore/harmony_java_lang/AndroidManifest.xml b/tests/core/libcore/harmony_java_lang/AndroidManifest.xml
index a5e499a..1a5a748 100644
--- a/tests/core/libcore/harmony_java_lang/AndroidManifest.xml
+++ b/tests/core/libcore/harmony_java_lang/AndroidManifest.xml
@@ -22,8 +22,11 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="android.core.tests.runner"
- android:label="cts framework tests"/>
+ android:label="cts framework tests">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/core/libcore/harmony_java_math/AndroidManifest.xml b/tests/core/libcore/harmony_java_math/AndroidManifest.xml
index f8cd224..4cdf654 100644
--- a/tests/core/libcore/harmony_java_math/AndroidManifest.xml
+++ b/tests/core/libcore/harmony_java_math/AndroidManifest.xml
@@ -22,8 +22,11 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="android.core.tests.runner"
- android:label="cts framework tests"/>
+ android:label="cts framework tests">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/core/libcore/harmony_java_net/AndroidManifest.xml b/tests/core/libcore/harmony_java_net/AndroidManifest.xml
index 3c9fd63..db14fa9 100644
--- a/tests/core/libcore/harmony_java_net/AndroidManifest.xml
+++ b/tests/core/libcore/harmony_java_net/AndroidManifest.xml
@@ -22,8 +22,11 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="android.core.tests.runner"
- android:label="cts framework tests"/>
+ android:label="cts framework tests">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/core/libcore/harmony_java_nio/AndroidManifest.xml b/tests/core/libcore/harmony_java_nio/AndroidManifest.xml
index 27166d4..0221ebb 100644
--- a/tests/core/libcore/harmony_java_nio/AndroidManifest.xml
+++ b/tests/core/libcore/harmony_java_nio/AndroidManifest.xml
@@ -22,8 +22,11 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="android.core.tests.runner"
- android:label="cts framework tests"/>
+ android:label="cts framework tests">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/core/libcore/harmony_java_text/AndroidManifest.xml b/tests/core/libcore/harmony_java_text/AndroidManifest.xml
index 0b6beed..6818053 100644
--- a/tests/core/libcore/harmony_java_text/AndroidManifest.xml
+++ b/tests/core/libcore/harmony_java_text/AndroidManifest.xml
@@ -22,8 +22,11 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="android.core.tests.runner"
- android:label="cts framework tests"/>
+ android:label="cts framework tests">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/core/libcore/harmony_java_util/AndroidManifest.xml b/tests/core/libcore/harmony_java_util/AndroidManifest.xml
index 72fa3ef..e36468e 100644
--- a/tests/core/libcore/harmony_java_util/AndroidManifest.xml
+++ b/tests/core/libcore/harmony_java_util/AndroidManifest.xml
@@ -22,8 +22,11 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="android.core.tests.runner"
- android:label="cts framework tests"/>
+ android:label="cts framework tests">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/core/libcore/harmony_javax_security/AndroidManifest.xml b/tests/core/libcore/harmony_javax_security/AndroidManifest.xml
index b7b35f2..c927855 100644
--- a/tests/core/libcore/harmony_javax_security/AndroidManifest.xml
+++ b/tests/core/libcore/harmony_javax_security/AndroidManifest.xml
@@ -22,8 +22,11 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="android.core.tests.runner"
- android:label="cts framework tests"/>
+ android:label="cts framework tests">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/core/libcore/harmony_logging/AndroidManifest.xml b/tests/core/libcore/harmony_logging/AndroidManifest.xml
index 94ee60e..8a669e2 100644
--- a/tests/core/libcore/harmony_logging/AndroidManifest.xml
+++ b/tests/core/libcore/harmony_logging/AndroidManifest.xml
@@ -22,8 +22,11 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="android.core.tests.runner"
- android:label="cts framework tests"/>
+ android:label="cts framework tests">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/core/libcore/harmony_prefs/AndroidManifest.xml b/tests/core/libcore/harmony_prefs/AndroidManifest.xml
index f8fdea2..ebcb4ef 100644
--- a/tests/core/libcore/harmony_prefs/AndroidManifest.xml
+++ b/tests/core/libcore/harmony_prefs/AndroidManifest.xml
@@ -22,8 +22,11 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="android.core.tests.runner"
- android:label="cts framework tests"/>
+ android:label="cts framework tests">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/core/libcore/harmony_sql/AndroidManifest.xml b/tests/core/libcore/harmony_sql/AndroidManifest.xml
index c6c31b2..7cd86da 100644
--- a/tests/core/libcore/harmony_sql/AndroidManifest.xml
+++ b/tests/core/libcore/harmony_sql/AndroidManifest.xml
@@ -22,8 +22,11 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="android.core.tests.runner"
- android:label="cts framework tests"/>
+ android:label="cts framework tests">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/core/libcore/jsr166/AndroidManifest.xml b/tests/core/libcore/jsr166/AndroidManifest.xml
index 3a0150e..fb4a648 100644
--- a/tests/core/libcore/jsr166/AndroidManifest.xml
+++ b/tests/core/libcore/jsr166/AndroidManifest.xml
@@ -22,8 +22,11 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="android.core.tests.runner"
- android:label="cts framework tests"/>
+ android:label="cts framework tests">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/core/libcore/libcore/AndroidManifest.xml b/tests/core/libcore/libcore/AndroidManifest.xml
index e4a5d1e..67a3023 100644
--- a/tests/core/libcore/libcore/AndroidManifest.xml
+++ b/tests/core/libcore/libcore/AndroidManifest.xml
@@ -22,8 +22,11 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="android.core.tests.runner"
- android:label="cts framework tests"/>
+ android:label="cts framework tests">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/core/libcore/okhttp/Android.mk b/tests/core/libcore/okhttp/Android.mk
new file mode 100644
index 0000000..4fe50ff
--- /dev/null
+++ b/tests/core/libcore/okhttp/Android.mk
@@ -0,0 +1,24 @@
+# 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)
+
+ifeq ($(BUILD_CTSCORE_PACKAGE),)
+ $(error BUILD_CTSCORE_PACKAGE must be defined)
+endif
+
+include $(CLEAR_VARS)
+LOCAL_PACKAGE_NAME := android.core.tests.libcore.package.okhttp
+LOCAL_STATIC_JAVA_LIBRARIES := okhttp-nojarjar junit4-target bouncycastle-nojarjar okhttp-tests-nojarjar
+include $(BUILD_CTSCORE_PACKAGE)
diff --git a/tests/core/libcore/okhttp/AndroidManifest.xml b/tests/core/libcore/okhttp/AndroidManifest.xml
new file mode 100644
index 0000000..f69bc83
--- /dev/null
+++ b/tests/core/libcore/okhttp/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?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="android.core.tests.libcore.package.okhttp">
+ <uses-permission android:name="android.permission.INTERNET" />
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.core.tests.runner"
+ android:label="cts framework tests">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
+
+</manifest>
diff --git a/tests/core/libcore/org/AndroidManifest.xml b/tests/core/libcore/org/AndroidManifest.xml
index d5b77bd..d705f65 100644
--- a/tests/core/libcore/org/AndroidManifest.xml
+++ b/tests/core/libcore/org/AndroidManifest.xml
@@ -22,8 +22,11 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="android.core.tests.runner"
- android:label="cts framework tests"/>
+ android:label="cts framework tests">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/core/libcore/sun/AndroidManifest.xml b/tests/core/libcore/sun/AndroidManifest.xml
index cc1a853..9888af3 100644
--- a/tests/core/libcore/sun/AndroidManifest.xml
+++ b/tests/core/libcore/sun/AndroidManifest.xml
@@ -22,8 +22,11 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="android.core.tests.runner"
- android:label="cts framework tests"/>
+ android:label="cts framework tests">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/core/libcore/tests/AndroidManifest.xml b/tests/core/libcore/tests/AndroidManifest.xml
index 02f8b4a..f7dab9c 100644
--- a/tests/core/libcore/tests/AndroidManifest.xml
+++ b/tests/core/libcore/tests/AndroidManifest.xml
@@ -22,8 +22,11 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="android.core.tests.runner"
- android:label="cts framework tests"/>
+ android:label="cts framework tests">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/core/runner/Android.mk b/tests/core/runner/Android.mk
index fb548fc..649b3b4 100644
--- a/tests/core/runner/Android.mk
+++ b/tests/core/runner/Android.mk
@@ -27,6 +27,6 @@
LOCAL_PACKAGE_NAME := android.core.tests.runner
-LOCAL_STATIC_JAVA_LIBRARIES := core-tests
+LOCAL_STATIC_JAVA_LIBRARIES := core-tests android-support-test
include $(BUILD_CTSCORE_PACKAGE)
diff --git a/tests/core/runner/AndroidManifest.xml b/tests/core/runner/AndroidManifest.xml
index e179710..05d210b 100644
--- a/tests/core/runner/AndroidManifest.xml
+++ b/tests/core/runner/AndroidManifest.xml
@@ -22,7 +22,7 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="android.core.tests.runner"
android:label="cts framework tests"/>
diff --git a/tests/core/runner/src/android/test/InstrumentationCtsTestRunner.java b/tests/core/runner/src/android/test/InstrumentationCtsTestRunner.java
deleted file mode 100644
index 96aeb51..0000000
--- a/tests/core/runner/src/android/test/InstrumentationCtsTestRunner.java
+++ /dev/null
@@ -1,238 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.test;
-
-import com.android.internal.util.Predicate;
-import com.android.internal.util.Predicates;
-
-import dalvik.annotation.BrokenTest;
-import dalvik.annotation.SideEffect;
-
-import android.app.KeyguardManager;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.os.Bundle;
-import android.test.suitebuilder.TestMethod;
-import android.test.suitebuilder.annotation.HasAnnotation;
-import android.util.Log;
-
-import java.io.File;
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
-import java.net.Authenticator;
-import java.net.CookieHandler;
-import java.net.ResponseCache;
-import java.util.List;
-import java.util.Locale;
-import java.util.TimeZone;
-import javax.net.ssl.HostnameVerifier;
-import javax.net.ssl.HttpsURLConnection;
-import javax.net.ssl.SSLSocketFactory;
-
-import junit.framework.AssertionFailedError;
-import junit.framework.Test;
-import junit.framework.TestCase;
-import junit.framework.TestListener;
-
-/**
- * This test runner extends the default InstrumentationTestRunner. It overrides
- * the {@code onCreate(Bundle)} method and sets the system properties necessary
- * for many core tests to run. This is needed because there are some core tests
- * that need writing access to the file system. We also need to set the harness
- * Thread's context ClassLoader. Otherwise some classes and resources will not
- * be found. Finally, we add a means to free memory allocated by a TestCase
- * after its execution.
- *
- * @hide
- */
-public class InstrumentationCtsTestRunner extends InstrumentationTestRunner {
-
- private static final String TAG = "InstrumentationCtsTestRunner";
-
- /**
- * True if (and only if) we are running in single-test mode (as opposed to
- * batch mode).
- */
- private boolean mSingleTest = false;
-
- private TestEnvironment mEnvironment;
-
- @Override
- public void onCreate(Bundle arguments) {
- // We might want to move this to /sdcard, if is is mounted/writable.
- File cacheDir = getTargetContext().getCacheDir();
- System.setProperty("java.io.tmpdir", cacheDir.getAbsolutePath());
-
- mEnvironment = new TestEnvironment();
-
- if (arguments != null) {
- String classArg = arguments.getString(ARGUMENT_TEST_CLASS);
- mSingleTest = classArg != null && classArg.contains("#");
- }
-
- // attempt to disable keyguard, if current test has permission to do so
- // TODO: move this to a better place, such as InstrumentationTestRunner ?
- if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DISABLE_KEYGUARD)
- == PackageManager.PERMISSION_GRANTED) {
- Log.i(TAG, "Disabling keyguard");
- KeyguardManager keyguardManager =
- (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
- keyguardManager.newKeyguardLock("cts").disableKeyguard();
- } else {
- Log.i(TAG, "Test lacks permission to disable keyguard. " +
- "UI based tests may fail if keyguard is up");
- }
-
- super.onCreate(arguments);
- }
-
- @Override
- protected AndroidTestRunner getAndroidTestRunner() {
- AndroidTestRunner runner = super.getAndroidTestRunner();
-
- runner.addTestListener(new TestListener() {
- /**
- * The last test class we executed code from.
- */
- private Class<?> lastClass;
-
- @Override
- public void startTest(Test test) {
- if (test.getClass() != lastClass) {
- lastClass = test.getClass();
- printMemory(test.getClass());
- }
-
- Thread.currentThread().setContextClassLoader(
- test.getClass().getClassLoader());
-
- mEnvironment.reset();
- }
-
- @Override
- public void endTest(Test test) {
- if (test instanceof TestCase) {
- cleanup((TestCase)test);
- }
- }
-
- @Override
- public void addError(Test test, Throwable t) {
- // This space intentionally left blank.
- }
-
- @Override
- public void addFailure(Test test, AssertionFailedError t) {
- // This space intentionally left blank.
- }
-
- /**
- * Dumps some memory info.
- */
- private void printMemory(Class<? extends Test> testClass) {
- Runtime runtime = Runtime.getRuntime();
-
- long total = runtime.totalMemory();
- long free = runtime.freeMemory();
- long used = total - free;
-
- Log.d(TAG, "Total memory : " + total);
- Log.d(TAG, "Used memory : " + used);
- Log.d(TAG, "Free memory : " + free);
- Log.d(TAG, "Now executing : " + testClass.getName());
- }
-
- /**
- * Nulls all non-static reference fields in the given test class.
- * This method helps us with those test classes that don't have an
- * explicit tearDown() method. Normally the garbage collector should
- * take care of everything, but since JUnit keeps references to all
- * test cases, a little help might be a good idea.
- */
- private void cleanup(TestCase test) {
- Class<?> clazz = test.getClass();
-
- while (clazz != TestCase.class) {
- Field[] fields = clazz.getDeclaredFields();
- for (int i = 0; i < fields.length; i++) {
- Field f = fields[i];
- if (!f.getType().isPrimitive() &&
- !Modifier.isStatic(f.getModifiers())) {
- try {
- f.setAccessible(true);
- f.set(test, null);
- } catch (Exception ignored) {
- // Nothing we can do about it.
- }
- }
- }
-
- clazz = clazz.getSuperclass();
- }
- }
-
- });
-
- return runner;
- }
-
- // http://code.google.com/p/vogar/source/browse/trunk/src/vogar/target/TestEnvironment.java
- static class TestEnvironment {
- private final Locale mDefaultLocale;
- private final TimeZone mDefaultTimeZone;
- private final String mJavaIoTmpDir;
- private final HostnameVerifier mHostnameVerifier;
- private final SSLSocketFactory mSslSocketFactory;
-
- TestEnvironment() {
- mDefaultLocale = Locale.getDefault();
- mDefaultTimeZone = TimeZone.getDefault();
- mJavaIoTmpDir = System.getProperty("java.io.tmpdir");
- mHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
- mSslSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory();
- }
-
- void reset() {
- System.setProperties(null);
- System.setProperty("java.io.tmpdir", mJavaIoTmpDir);
- Locale.setDefault(mDefaultLocale);
- TimeZone.setDefault(mDefaultTimeZone);
- Authenticator.setDefault(null);
- CookieHandler.setDefault(null);
- ResponseCache.setDefault(null);
- HttpsURLConnection.setDefaultHostnameVerifier(mHostnameVerifier);
- HttpsURLConnection.setDefaultSSLSocketFactory(mSslSocketFactory);
- }
- }
-
- @Override
- List<Predicate<TestMethod>> getBuilderRequirements() {
- List<Predicate<TestMethod>> builderRequirements =
- super.getBuilderRequirements();
-
- Predicate<TestMethod> brokenTestPredicate =
- Predicates.not(new HasAnnotation(BrokenTest.class));
- builderRequirements.add(brokenTestPredicate);
-
- if (!mSingleTest) {
- Predicate<TestMethod> sideEffectPredicate =
- Predicates.not(new HasAnnotation(SideEffect.class));
- builderRequirements.add(sideEffectPredicate);
- }
- return builderRequirements;
- }
-}
diff --git a/tests/core/runner/src/com/android/cts/runner/CtsTestRunListener.java b/tests/core/runner/src/com/android/cts/runner/CtsTestRunListener.java
new file mode 100644
index 0000000..5196df1
--- /dev/null
+++ b/tests/core/runner/src/com/android/cts/runner/CtsTestRunListener.java
@@ -0,0 +1,177 @@
+/*
+ * 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.runner;
+
+import android.app.Instrumentation;
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.support.test.internal.runner.listener.InstrumentationRunListener;
+import android.util.Log;
+
+import junit.framework.TestCase;
+
+import org.junit.runner.Description;
+import org.junit.runner.notification.RunListener;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.net.Authenticator;
+import java.net.CookieHandler;
+import java.net.ResponseCache;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLSocketFactory;
+
+/**
+ * A {@link RunListener} for CTS. Sets the system properties necessary for many
+ * core tests to run. This is needed because there are some core tests that need
+ * writing access to the file system.
+ * Finally, we add a means to free memory allocated by a TestCase after its
+ * execution.
+ */
+public class CtsTestRunListener extends InstrumentationRunListener {
+
+ private static final String TAG = "CtsTestRunListener";
+
+ private TestEnvironment mEnvironment;
+ private Class<?> lastClass;
+
+ @Override
+ public void testRunStarted(Description description) throws Exception {
+ mEnvironment = new TestEnvironment();
+
+ // We might want to move this to /sdcard, if is is mounted/writable.
+ File cacheDir = getInstrumentation().getTargetContext().getCacheDir();
+ System.setProperty("java.io.tmpdir", cacheDir.getAbsolutePath());
+
+ // attempt to disable keyguard, if current test has permission to do so
+ // TODO: move this to a better place, such as InstrumentationTestRunner
+ // ?
+ if (getInstrumentation().getContext().checkCallingOrSelfPermission(
+ android.Manifest.permission.DISABLE_KEYGUARD)
+ == PackageManager.PERMISSION_GRANTED) {
+ Log.i(TAG, "Disabling keyguard");
+ KeyguardManager keyguardManager =
+ (KeyguardManager) getInstrumentation().getContext().getSystemService(
+ Context.KEYGUARD_SERVICE);
+ keyguardManager.newKeyguardLock("cts").disableKeyguard();
+ } else {
+ Log.i(TAG, "Test lacks permission to disable keyguard. " +
+ "UI based tests may fail if keyguard is up");
+ }
+ }
+
+ @Override
+ public void testStarted(Description description) throws Exception {
+ if (description.getTestClass() != lastClass) {
+ lastClass = description.getTestClass();
+ printMemory(description.getTestClass());
+ }
+
+ mEnvironment.reset();
+ }
+
+ @Override
+ public void testFinished(Description description) {
+ // no way to implement this in JUnit4...
+ // offending test cases that need this logic should probably be cleaned
+ // up individually
+ // if (test instanceof TestCase) {
+ // cleanup((TestCase) test);
+ // }
+ }
+
+ /**
+ * Dumps some memory info.
+ */
+ private void printMemory(Class<?> testClass) {
+ Runtime runtime = Runtime.getRuntime();
+
+ long total = runtime.totalMemory();
+ long free = runtime.freeMemory();
+ long used = total - free;
+
+ Log.d(TAG, "Total memory : " + total);
+ Log.d(TAG, "Used memory : " + used);
+ Log.d(TAG, "Free memory : " + free);
+ Log.d(TAG, "Now executing : " + testClass.getName());
+ }
+
+ /**
+ * Nulls all non-static reference fields in the given test class. This
+ * method helps us with those test classes that don't have an explicit
+ * tearDown() method. Normally the garbage collector should take care of
+ * everything, but since JUnit keeps references to all test cases, a little
+ * help might be a good idea.
+ */
+ private void cleanup(TestCase test) {
+ Class<?> clazz = test.getClass();
+
+ while (clazz != TestCase.class) {
+ Field[] fields = clazz.getDeclaredFields();
+ for (int i = 0; i < fields.length; i++) {
+ Field f = fields[i];
+ if (!f.getType().isPrimitive() &&
+ !Modifier.isStatic(f.getModifiers())) {
+ try {
+ f.setAccessible(true);
+ f.set(test, null);
+ } catch (Exception ignored) {
+ // Nothing we can do about it.
+ }
+ }
+ }
+
+ clazz = clazz.getSuperclass();
+ }
+ }
+
+ // http://code.google.com/p/vogar/source/browse/trunk/src/vogar/target/TestEnvironment.java
+ static class TestEnvironment {
+ private final Locale mDefaultLocale;
+ private final TimeZone mDefaultTimeZone;
+ private final String mJavaIoTmpDir;
+ private final HostnameVerifier mHostnameVerifier;
+ private final SSLSocketFactory mSslSocketFactory;
+
+ TestEnvironment() {
+ mDefaultLocale = Locale.getDefault();
+ mDefaultTimeZone = TimeZone.getDefault();
+ mJavaIoTmpDir = System.getProperty("java.io.tmpdir");
+ mHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
+ mSslSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory();
+ }
+
+ void reset() {
+ System.setProperties(null);
+ System.setProperty("java.io.tmpdir", mJavaIoTmpDir);
+ Locale.setDefault(mDefaultLocale);
+ TimeZone.setDefault(mDefaultTimeZone);
+ Authenticator.setDefault(null);
+ CookieHandler.setDefault(null);
+ ResponseCache.setDefault(null);
+ HttpsURLConnection.setDefaultHostnameVerifier(mHostnameVerifier);
+ HttpsURLConnection.setDefaultSSLSocketFactory(mSslSocketFactory);
+ }
+ }
+
+}
diff --git a/tests/deviceadmin/Android.mk b/tests/deviceadmin/Android.mk
index bcc23fc..9ab9cb8 100644
--- a/tests/deviceadmin/Android.mk
+++ b/tests/deviceadmin/Android.mk
@@ -20,7 +20,7 @@
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_JAVA_LIBRARIES := android.test.runner guava
+LOCAL_JAVA_LIBRARIES := guava
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/deviceadmin/AndroidManifest.xml b/tests/deviceadmin/AndroidManifest.xml
index 2395d99..f70a677 100644
--- a/tests/deviceadmin/AndroidManifest.xml
+++ b/tests/deviceadmin/AndroidManifest.xml
@@ -107,7 +107,7 @@
</application>
- <instrumentation android:name="android.test.InstrumentationTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.admin"
android:label="Tests for the device admin APIs."/>
</manifest>
diff --git a/tests/expectations/knownfailures.txt b/tests/expectations/knownfailures.txt
index 2b32869..9b089df 100644
--- a/tests/expectations/knownfailures.txt
+++ b/tests/expectations/knownfailures.txt
@@ -8,10 +8,6 @@
bug: 8148617
},
{
- name: "android.holo.cts.HoloHostTest",
- bug: 15343612
-},
-{
name: "android.nativeopengl.EGLCleanupTest#TestCorrect",
name: "android.nativeopengl.EGLCreateContextTest#BadAttributeFails",
bug: 11652564
@@ -33,88 +29,5 @@
name: "android.hardware.cts.SensorIntegrationTests#testSensorsWithSeveralClients",
name: "android.hardware.cts.SensorIntegrationTests#testSensorsMovingRates",
bug: 11352697
-},
-{
- name: "android.app.cts.DownloadManagerTest#testDownloadManager",
- name: "android.app.cts.DownloadManagerTest#testDownloadManagerDestination",
- name: "android.app.cts.DownloadManagerTest#testDownloadManagerDestinationExtension",
- name: "android.app.cts.DownloadManagerTest#testMinimumDownload",
- name: "android.content.cts.ContentResolverSyncTestCase#testCallMultipleAccounts",
- name: "android.content.cts.ContentResolverSyncTestCase#testCancelSync",
- name: "android.content.cts.ContentResolverSyncTestCase#testRequestSync",
- bug: 14657953
-},
-{
- name: "android.database.sqlite.cts.SQLiteQueryBuilderTest#testSetProjectionMap",
- bug: 12475524
-},
-{
- name: "android.keystore.cts.KeyChainTest#testIsBoundKeyAlgorithm_RequiredAlgorithmsSupported",
- bug: 15314696
-},
-{
- name: "android.media.cts.AudioEffectTest",
- bug: 15081808
-},
-{
- name: "android.media.cts.AudioManagerTest#testMusicActive",
- name: "android.media.cts.AudioManagerTest#testVolume",
- bug: 15319269
-},
-{
- name: "android.media.cts.AudioTrackTest#testGetTimestamp",
- bug: 15320704
-},
-{
- name: "android.media.cts.BassBoostTest",
- bug: 15163233
-},
-{
- name: "android.media.cts.DecoderTest",
- bug: 15163143
-},
-{
- name: "android.media.cts.EnvReverbTest",
- bug: 15163233
-},
-{
- name: "android.media.cts.MediaPlayerFlakyNetworkTest",
- bug: 15350295
-},
-{
- name: "android.media.cts.MediaPlayerTest",
- bug: 15321806
-},
-{
- name: "android.media.cts.PresetReverbTest",
- bug: 15338282
-},
-{
- name: "android.media.cts.RingtoneManagerTest",
- bug: 15343572
-},
-{
- name: "android.media.cts.SoundPoolAacTest",
- name: "android.media.cts.SoundPoolOggTest",
- bug: 15350147
-},
-{
- name: "android.media.cts.StreamingMediaPlayerTest",
- bug: 15321207
-},
-{
- name: "android.media.cts.VirtualizerTest",
- bug: 15381765
-},
-{
- name: "android.mediastress.cts.H263QcifLongPlayerTest",
- name: "android.mediastress.cts.H264R480x360AacShortPlayerTest",
- name: "android.mediastress.cts.Vp8R480x360LongPlayerTest",
- bug: 15081769
-},
-{
- name: "android.net.cts.ConnectivityManagerTest#testGetActiveNetworkInfo",
- bug: 15087784
}
-
]
diff --git a/tests/plans/CTS-flaky.xml b/tests/plans/CTS-flaky.xml
deleted file mode 100644
index 5317d96..0000000
--- a/tests/plans/CTS-flaky.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<TestPlan version="1.0">
- <Entry uri="android.app" exclude="android.app.cts.SystemFeaturesTest#testTelephonyFeatures;android.app.cts.InstrumentationTest#testCallActivityOnResume;android.app.cts.ActivityManagerRecentTaskInfoTest#testWriteToParcel;android.app.cts.InstrumentationTest#testMisc;android.app.cts.DialogTest#testSetContentView;android.app.cts.ServiceTest#testLocalBindAutoClassPermissionGranted;android.app.cts.ActivityManager_RunningAppProcessInfoTest#testAndroidTestCaseSetupProperly;android.app.cts.AlertDialog_BuilderTest#testSetSingleChoiceItemsWithParamInt;android.app.cts.LocalActivityManagerTest#testConstructor;android.app.cts.ActivityManager_RunningServiceInfoTest#testDescribeContents;android.app.cts.DialogTest#testTakeKeyEvents;android.app.cts.LifecycleTest#testBasic;android.app.cts.NotificationTest#testConstructor;android.app.cts.ActivityManagerMemoryInfoTest#testWriteToParcel;android.app.cts.DialogTest#testConstructor_protectedCancellable;android.app.cts.ProgressDialogTest#testIncrementProgressBy;android.app.cts.ApplicationTest#testApplication;android.app.cts.InstrumentationTest#testSendKeySync;android.app.cts.LocalActivityManagerTest#testDispatchCreate;android.app.cts.AlertDialogTest#testAlertDialogDeprecatedAPI;android.app.cts.AlertDialog_BuilderTest#testSetSingleChoiceItemsWithParamCursor;android.app.cts.InstrumentationTest#testCallActivityOnRestart;android.app.backup.cts.BackupAgentTest#testBackupAgent;android.app.cts.AlertDialog_BuilderTest#testSetSingleChoiceItemsWithParamCharSequence;android.app.cts.ActivityManager_RunningServiceInfoTest#testAndroidTestCaseSetupProperly;android.app.cts.ServiceTest#testLocalBindAutoAction;android.app.cts.DialogTest#testSetFeatureDrawableUri;android.app.cts.KeyguardManagerKeyguardLockTest#testDisableKeyguard;android.app.cts.LaunchTest#testClearTopWhilResumed;android.app.cts.WallpaperManagerTest#testSuggestDesiredDimensions;android.app.cts.SystemFeaturesTest#testLiveWallpaperFeature;android.app.cts.InstrumentationTest#testCallActivityOnUserLeaving;android.app.cts.AlertDialog_BuilderTest#testSetCancelable;android.app.cts.AlertDialogTest#testAlertDialogTheme;android.app.cts.PendingIntentTest#testGetBroadcast;android.app.cts.ActivityManager_RunningServiceInfoTest#testWriteToParcel;android.app.cts.InstrumentationTest#testGetComponentName;android.app.backup.cts.FileBackupHelperTest#testAndroidTestCaseSetupProperly;android.app.cts.DownloadManagerTest#testAndroidTestCaseSetupProperly;android.app.cts.SystemFeaturesTest#testTouchScreenFeatures;android.app.cts.AlarmManagerTest#testAlarmTriggersImmediatelyIfSetTimeIsNegative;android.app.cts.ActivityManagerMemoryInfoTest#testReadFromParcel;android.app.cts.NotificationTest#testAndroidTestCaseSetupProperly;android.app.cts.AlertDialog_BuilderTest#testSetOnCancelListener;android.app.cts.ServiceTest#testLocalBindClass;android.app.cts.ServiceTest#testLocalStartClass;android.app.cts.LauncherActivity_ListItemTest#testConstructor;android.app.cts.InstrumentationTest#testCallActivityOnCreate;android.app.cts.PendingIntentTest#testSendWithParamContextIntIntent;android.app.cts.InstrumentationTest#testSendPointerSync;android.app.cts.DialogTest#testRequestWindowFeature;android.app.cts.LocalActivityManagerTest#testDispatchPauseFalse;android.app.cts.Instrumentation_ActivityResultTest#testActivityResultOp;android.app.cts.ProgressDialogTest#testOnStartCreateStop;android.app.cts.AlertDialog_BuilderTest#testSetNegativeButtonWithParamInt;android.app.cts.KeyguardManagerKeyguardLockTest#testReenableKeyguard;android.app.cts.DialogTest#testShow;android.app.cts.ActivityManagerMemoryInfoTest#testDescribeContents;android.app.cts.SystemFeaturesTest#testFeatureNamespaces;android.app.cts.PendingIntent_CanceledExceptionTest#testConstructor;android.app.cts.AlertDialog_BuilderTest#testSetPositiveButtonWithParamInt;android.app.cts.PendingIntentTest#testSendWithParamInt;android.app.cts.PendingIntentTest#testGetActivity;android.app.cts.AlertDialog_BuilderTest#testSetCursor;android.app.cts.ActivityManagerProcessErrorStateInfoTest#testWriteToParcel;android.app.cts.AlertDialog_BuilderTest#testConstructor;android.app.cts.LocalActivityManagerTest#testDispatchDestroy;android.app.cts.DownloadManagerTest#testDownloadManager;android.app.cts.ServiceTest#testLocalBindAction;android.app.cts.LocalActivityManagerTest#testSaveInstanceState;android.app.cts.LifecycleTest#testTabDialog;android.app.cts.LaunchTest#testLocalActivity;android.app.cts.AlertDialog_BuilderTest#testSetInverseBackgroundForced;android.app.cts.SystemFeaturesTest#testLocationFeatures;android.app.cts.DialogTest#testOnStartCreateStop;android.app.cts.ActivityManagerRecentTaskInfoTest#testReadFromParcel;android.app.cts.ServiceTest#testLocalStartClassPermissions;android.app.cts.ServiceTest#testLocalUnbindTwice;android.app.cts.ActivityManagerProcessErrorStateInfoTest#testConstructor;android.app.cts.DialogTest#testSetFeatureDrawableResource;android.app.cts.ProgressDialogTest#testAccessProgress;android.app.cts.DialogTest#testTrackballEvent;android.app.cts.AlertDialog_BuilderTest#testSetSingleChoiceItems;android.app.cts.LaunchTest#testColdActivity;android.app.cts.ActivityManagerTest#testGetMemoryInfo;android.app.cts.PendingIntentTest#testAndroidTestCaseSetupProperly;android.app.cts.DialogTest#testOnContentChanged;android.app.cts.SearchManagerTest#testSetOnDismissListener;android.app.cts.InstrumentationTest#testContext;android.app.cts.LauncherActivity_IconResizerTest#testIconResizer;android.app.cts.DialogTest#testCancel_listener;android.app.cts.ActivityManagerTest#testIsUserAMonkey;android.app.cts.NotificationManagerTest#testCancelAll;android.app.cts.ActivityGroupTest#testAndroidTestCaseSetupProperly;android.app.cts.AlertDialog_BuilderTest#testCreate;android.app.cts.SystemFeaturesTest#testUsbAccessory;android.app.cts.ActivityManagerRecentTaskInfoTest#testDescribeContents;android.app.cts.ActivityManagerRunningTaskInfoTest#testDescribeContents;android.app.cts.AlertDialog_BuilderTest#testDisableCancelable;android.app.cts.InstrumentationTest#testSendKeyDownUpSync;android.app.cts.ActivityManagerRunningTaskInfoTest#testConstructor;android.app.cts.TimePickerDialogTest#testUpdateTime;android.app.cts.ActivityManagerRunningTaskInfoTest#testReadFromParcel;android.app.cts.ProgressDialogTest#testSetProgressStyle;android.app.cts.InstrumentationTest#testMonitor;android.app.cts.AliasActivityTest#testAliasActivity;android.app.cts.InstrumentationTest#testSendTrackballEventSync;android.app.cts.LifecycleTest#testScreen;android.app.cts.AlarmManagerTest#testCancel;android.app.cts.ActivityGroupTest#testTabDialog;android.app.cts.PendingIntentTest#testDescribeContents;android.app.cts.DialogTest#testOnSearchRequested;android.app.cts.SearchManagerTest#testSetOnCancelListener;android.app.cts.ProgressDialogTest#testAccessMax;android.app.cts.PendingIntentTest#testGetTargetPackage;android.app.cts.SystemFeaturesTest#testWifiFeature;android.app.cts.ActivityManagerRecentTaskInfoTest#testAndroidTestCaseSetupProperly;android.app.cts.DialogTest#testSetFeatureDrawableAlpha;android.app.cts.DialogTest#testSetTitle;android.app.cts.FragmentTest#testInstantiateNonFragment;android.app.cts.DialogTest#testSetDismissMessage;android.app.cts.PendingIntentTest#testWriteToParcel;android.app.cts.ActivityManagerMemoryInfoTest#testAndroidTestCaseSetupProperly;android.app.cts.AlertDialog_BuilderTest#testSetNeutralButtonWithParamCharSequence;android.app.cts.NotificationTest#testToString;android.app.cts.LaunchTest#testColdScreen;android.app.cts.AlertDialogTest#testAlertDialog;android.app.cts.SystemFeaturesTest#testSensorFeatures;android.app.cts.ProgressDialogTest#testShow1;android.app.cts.AlertDialog_BuilderTest#testSetNeutralButtonWithParamInt;android.app.cts.ActivityManagerRunningTaskInfoTest#testWriteToParcel;android.app.cts.ProgressDialogTest#testShow2;android.app.cts.ProgressDialogTest#testShow3;android.app.cts.ProgressDialogTest#testShow4;android.app.cts.InstrumentationTest#testCallActivityOnStart;android.app.cts.PendingIntentTest#testCancel;android.app.cts.InstrumentationTest#testConstructor;android.app.cts.ProgressDialogTest#testSetIndeterminate;android.app.cts.AlertDialog_BuilderTest#testShow;android.app.cts.AlertDialog_BuilderTest#testSetIconWithParamDrawable;android.app.cts.AlarmManagerTest#testSetTypes;android.app.cts.AlarmManagerTest#testSetInexactRepeating;android.app.cts.PendingIntentTest#testSendWithParamIntOnFinishedHandler;android.app.backup.cts.FileBackupHelperTest#testConstructor;android.app.cts.Instrumentation_ActivityResultTest#testAndroidTestCaseSetupProperly;android.app.cts.DownloadManagerTest#testDownloadManagerDestinationExtension;android.app.cts.LaunchTest#testClearTopInCreate;android.app.cts.InstrumentationTest#testRunOnMainSync;android.app.cts.DialogTest#testOnKeyDownKeyUp;android.app.cts.ServiceTest#testLocalStartActionPermissions;android.app.cts.KeyguardManagerTest#testInKeyguardRestrictedInputMode;android.app.cts.DialogTest#testConstructor;android.app.cts.AlertDialogTest#testCallback;android.app.cts.ActivityManagerTest#testGetRunningTasks;android.app.cts.ProgressDialogTest#testIncrementSecondaryProgressBy;android.app.cts.DialogTest#testAccessOwnerActivity;android.app.cts.LifecycleTest#testTabScreen;android.app.cts.TimePickerDialogTest#testOnRestoreInstanceState;android.app.cts.ActivityGroupTest#testTabBasic;android.app.cts.ServiceTest#testAndroidTestCaseSetupProperly;android.app.cts.PendingIntentTest#testSendWithParamContextIntIntentOnFinishedHandler;android.app.backup.cts.BackupManagerTest#testBackupManager;android.app.cts.ActivityManagerRecentTaskInfoTest#testConstructor;android.app.cts.DialogTest#testOnWindowFocusChanged;android.app.cts.DialogTest#testGetCurrentFocus;android.app.cts.TabActivityTest#testChildTitleCallback;android.app.cts.DialogTest#testSetOnDismissListener_listener;android.app.cts.ServiceTest#testLocalStartAction;android.app.cts.AlertDialog_BuilderTest#testSetMultiChoiceItemsWithParamInt;android.app.cts.DialogTest#testGetLayoutInflater;android.app.cts.InstrumentationTest#testInvokeContextMenuAction;android.app.cts.InstrumentationTest#testProfiling;android.app.cts.IntentServiceTest#testIntentServiceLifeCycle;android.app.cts.ProgressDialogTest#testAccessSecondaryProgress;android.app.cts.NotificationManagerTest#testAndroidTestCaseSetupProperly;android.app.backup.cts.BackupAgentTest#testAndroidTestCaseSetupProperly;android.app.cts.ActivityManagerTest#testGetProcessInErrorState;android.app.cts.SearchManagerTest#testStopSearch;android.app.cts.NotificationTest#testSetLatestEventInfo;android.app.cts.DialogTest#testSetCancellable_false;android.app.cts.PendingIntentTest#testSend;android.app.cts.LocalActivityManagerTest#testDispatchResume;android.app.cts.AlertDialog_BuilderTest#testSetViewCustom;android.app.cts.InstrumentationTest#testInvokeMenuActionSync;android.app.cts.DialogTest#testOnSaveInstanceState;android.app.cts.DialogTest#testOnKeyMultiple;android.app.cts.ActivityGroupTest#testTabScreen;android.app.cts.InstrumentationTest#testCallActivityOnSaveInstanceState;android.app.cts.ActivityManagerTest#testGetDeviceConfigurationInfo;android.app.cts.NotificationTest#testWriteToParcel;android.app.cts.InstrumentationTest#testWaitForIdle;android.app.cts.InstrumentationTest#testCallActivityOnStop;android.app.cts.InstrumentationTest#testSendCharacterSync;android.app.cts.TimePickerDialogTest#testOnTimeChanged;android.app.cts.PendingIntentTest#testEquals;android.app.cts.AlertDialog_BuilderTest#testSetIconWithParamInt;android.app.cts.DialogTest#testContextMenu;android.app.cts.SystemFeaturesTest#testBluetoothFeature;android.app.cts.ExpandableListActivityTest#testView;android.app.cts.AlarmManagerTest#testSetRepeating;android.app.cts.ActivityManager_RunningServiceInfoTest#testReadFromParcel;android.app.cts.ServiceTest#testLocalBindActionPermissions;android.app.cts.LocalActivityManagerTest#testDispatchStop;android.app.cts.LauncherActivity_ListItemTest#testAndroidTestCaseSetupProperly;android.app.cts.InstrumentationTest#testNewApplication;android.app.cts.LaunchTest#testForwardResult;android.app.cts.ProgressDialogTest#testSetMessage;android.app.cts.ActivityManagerTest#testGetRunningServices;android.app.cts.AlertDialogTest#testCustomAlertDialogView;android.app.backup.cts.SharedPreferencesBackupHelperTest#testConstructor;android.app.cts.LifecycleTest#testTabBasic;android.app.cts.FragmentTest#testInstantiateFragment;android.app.cts.ActivityManager_RunningAppProcessInfoTest#testRunningAppProcessInfo;android.app.cts.AlertDialogTest#testAlertDialogNotCancelable;android.app.cts.AlertDialog_BuilderTest#testSetItemsWithParamCharSequence;android.app.cts.ActivityManagerProcessErrorStateInfoTest#testDescribeContents;android.app.cts.DialogTest#testConstructor_protectedNotCancellable;android.app.cts.ExpandableListActivityTest#testSelect;android.app.cts.SystemFeaturesTest#testScreenFeatures;android.app.cts.LauncherActivityTest#testLaunchActivity;android.app.cts.LaunchTest#testAndroidTestCaseSetupProperly;android.app.cts.LifecycleTest#testAndroidTestCaseSetupProperly;android.app.cts.NotificationTest#testDescribeContents;android.app.cts.DialogTest#testOnWindowAttributesChanged;android.app.cts.ServiceTest#testLocalBindAutoActionPermissionGranted;android.app.cts.InstrumentationTest#testCallActivityOnPostCreate;android.app.cts.AlertDialogTest#testAlertDialogDeprecatedAPIWithMessage;android.app.cts.AlertDialog_BuilderTest#testSetPositiveButtonWithParamCharSequence;android.app.cts.AlarmManagerTest#testAndroidTestCaseSetupProperly;android.app.cts.AlertDialog_BuilderTest#testSetView;android.app.cts.ActivityManager_RunningServiceInfoTest#testConstructor;android.app.cts.InstrumentationTest#testPerformanceSnapshot;android.app.cts.InstrumentationTest#testCallApplicationOnCreate;android.app.cts.DialogTest#testSetFeatureDrawable;android.app.cts.AliasActivityTest#testAndroidTestCaseSetupProperly;android.app.cts.PendingIntentTest#testGetService;android.app.cts.IntentServiceTest#testIntents;android.app.cts.AlertDialog_BuilderTest#testSetOnItemSelectedListener;android.app.cts.LifecycleTest#testDialog;android.app.cts.DownloadManagerTest#testDownloadManagerDestination;android.app.cts.WallpaperManagerTest#testAndroidTestCaseSetupProperly;android.app.cts.ExpandableListActivityTest#testAndroidTestCaseSetupProperly;android.app.cts.InstrumentationTest#testNewActivity;android.app.cts.ProgressDialogTest#testSetProgressDrawable;android.app.backup.cts.BackupManagerTest#testAndroidTestCaseSetupProperly;android.app.cts.InstrumentationTest#testAllocCounting;android.app.cts.LocalActivityManagerTest#testRemoveAllActivities;android.app.cts.ServiceTest#testLocalBindAutoClass;android.app.backup.cts.BackupAgentHelperTest#testBackupAgentHelper;android.app.cts.ActivityManagerProcessErrorStateInfoTest#testReadFromParcel;android.app.cts.PendingIntent_CanceledExceptionTest#testAndroidTestCaseSetupProperly;android.app.cts.TimePickerDialogTest#testOnClick;android.app.cts.AlertDialog_BuilderTest#testSetOnKeyListener;android.app.cts.DialogTest#testTouchEvent;android.app.cts.ProgressDialogTest#testProgressDialog1;android.app.cts.InstrumentationTest#testCallActivityOnRestoreInstanceState;android.app.cts.ProgressDialogTest#testProgressDialog2;android.app.cts.InstrumentationTest#testCallActivityOnPause;android.app.cts.AlertDialog_BuilderTest#testSetMultiChoiceItemsWithParamCharSequence;android.app.cts.SystemFeaturesTest#testCameraFeatures;android.app.cts.ServiceTest#testLocalBindClassPermissions;android.app.cts.ActivityManagerTest#testGetRecentTasks;android.app.cts.SystemFeaturesTest#testNfcFeatures;android.app.cts.AlertDialogTest#testCustomAlertDialog;android.app.cts.SystemFeaturesTest#testSipFeatures;android.app.cts.InstrumentationTest#testSendStringSync;android.app.cts.ActivityManagerRunningTaskInfoTest#testAndroidTestCaseSetupProperly;android.app.cts.TabActivityTest#testTabActivity;android.app.cts.KeyguardManagerTest#testNewKeyguardLock;android.app.cts.NotificationManagerTest#testCancel;android.app.cts.AlertDialog_BuilderTest#testSetMultiChoiceItemsWithParamCursor;android.app.cts.ActivityManagerMemoryClassTest#testGetMemoryClass;android.app.cts.ActivityManagerTest#testGetRunningAppProcesses;android.app.cts.AlertDialog_BuilderTest#testSetAdapter;android.app.cts.AlertDialog_BuilderTest#testSetItemsWithParamInt;android.app.cts.DialogTest#testCancel_noListener;android.app.cts.DialogTest#testDispatchKeyEvent;android.app.cts.LaunchTest#testLocalScreen;android.app.cts.ActivityManagerTest#testRestartPackage;android.app.cts.FragmentTest#testAndroidTestCaseSetupProperly;android.app.cts.InstrumentationTest#testCallActivityOnNewIntent;android.app.cts.NotificationManagerTest#testNotify;android.app.cts.LocalActivityManagerTest#testDispatchPauseTrue;android.app.cts.ActivityManagerProcessErrorStateInfoTest#testAndroidTestCaseSetupProperly;android.app.backup.cts.BackupAgentHelperTest#testAndroidTestCaseSetupProperly;android.app.cts.ProgressDialogTest#testSetIndeterminateDrawable;android.app.backup.cts.SharedPreferencesBackupHelperTest#testAndroidTestCaseSetupProperly;android.app.cts.DownloadManagerTest#testMinimumDownload;android.app.cts.PendingIntentTest#testReadAndWritePendingIntentOrNullToParcel;android.app.cts.DialogTest#testSetOnDismissListener_noListener;android.app.cts.KeyguardManagerTest#testExitKeyguardSecurely;android.app.cts.AlertDialog_BuilderTest#testSetNegativeButtonWithParamCharSequence;android.app.cts.DialogTest#testSetCancelable_true;android.app.cts.Instrumentation_ActivityMonitorTest#testActivityMonitor;android.app.cts.LocalActivityManagerTest#testStartActivity;android.app.cts.IntentServiceTest#testAndroidTestCaseSetupProperly;android.app.cts.ActionBarTest#testAddTab;android.app.cts.DialogTest#testSetCancelMessage;android.app.cts.TimePickerDialogTest#testSaveInstanceState" />
- <Entry uri="android.hardware" exclude="android.hardware.consumerir.cts.ConsumerIrTest#test_hasIrEmitter;android.hardware.consumerir.cts.ConsumerIrTest#test_getCarrierFrequencies;android.hardware.consumerir.cts.ConsumerIrTest#test_transmit;android.hardware.camera2.cts.CameraDeviceTest#testCameraDeviceCaptureBurst;android.hardware.camera2.cts.CameraDeviceTest#testCameraDeviceRepeatingBurst;android.hardware.cts.CameraGLTest#testSetPreviewTextureTextureCallback;android.hardware.cts.CameraTest#test3ALockInteraction;android.hardware.cts.CameraTest#testAutoExposureLock;android.hardware.cts.CameraTest#testAutoWhiteBalanceLock;android.hardware.cts.CameraTest#testCancelAutofocus;android.hardware.cts.CameraTest#testDisplayOrientation;android.hardware.cts.CameraTest#testEnableShutterSound;android.hardware.cts.CameraTest#testFaceDetection;android.hardware.cts.CameraTest#testFocusAreas;android.hardware.cts.CameraTest#testFocusDistances;android.hardware.cts.CameraTest#testGetParameterDuringFocus;android.hardware.cts.CameraTest#testInvalidParameters;android.hardware.cts.CameraTest#testJpegCallbackStartPreview;android.hardware.cts.CameraTest#testJpegExif;android.hardware.cts.CameraTest#testJpegThumbnailSize;android.hardware.cts.CameraTest#testLockUnlock;android.hardware.cts.CameraTest#testMeteringAreas;android.hardware.cts.CameraTest#testMultiCameraRelease;android.hardware.cts.CameraTest#testMultipleCameras;android.hardware.cts.CameraTest#testParameters;android.hardware.cts.CameraTest#testPreviewCallbackWithBuffer;android.hardware.cts.CameraTest#testPreviewCallbackWithPicture;android.hardware.cts.CameraTest#testPreviewFormats;android.hardware.cts.CameraTest#testPreviewFpsRange;android.hardware.cts.CameraTest#testPreviewPictureSizesCombination;android.hardware.cts.CameraTest#testRecordingHint;android.hardware.cts.CameraTest#testSceneMode;android.hardware.cts.CameraTest#testSetOneShotPreviewCallback;android.hardware.cts.CameraTest#testSetPreviewDisplay;android.hardware.cts.CameraTest#testTakePicture;android.hardware.camera2.cts.ImageReaderTest#testImageReaderInvalidAccessTest;android.hardware.cts.SensorAccelerometerTest#testEventJittering;android.hardware.cts.SensorGyroscopeTest#testEventsArriveInOrder;android.hardware.camera2.cts.CameraCharacteristicsTest#testCameraCharacteristicsAndroidScalerAvailableJpegSizes;android.hardware.cts.SensorAccelerometerTest#testCanReceiveBatchEvents;android.hardware.camera2.cts.CameraCharacteristicsTest#testCameraCharacteristicsAndroidSensorOrientation;android.hardware.camera2.cts.CameraCharacteristicsTest#testAndroidTestCaseSetupProperly;android.hardware.cts.SensorMagneticFieldTest#testUpdateRate;android.hardware.cts.SensorAccelerometerTest#testNotTriggerSensor;android.hardware.cts.SensorAccelerometerTest#testAndroidTestCaseSetupProperly;android.hardware.cts.SensorAccelerometerTest#testCanReceiveEvents;android.hardware.cts.SensorMagneticFieldTest#testVarianceWhileStatic;android.hardware.camera2.cts.CameraCharacteristicsTest#testCameraCharacteristicsAndroidLensInfoHyperfocalDistance;android.hardware.camera2.cts.CameraManagerTest#testCameraManagerOpenCamerasSerially;android.hardware.camera2.cts.CameraDeviceTest#testCameraDeviceCreateCaptureBuilder;android.hardware.camera2.cts.CameraCharacteristicsTest#testCameraCharacteristicsAndroidScalerAvailableProcessedMinDurations;android.hardware.camera2.cts.CameraCharacteristicsTest#testCameraCharacteristicsAndroidFlashInfoAvailable;android.hardware.camera2.cts.CameraCharacteristicsTest#testCameraCharacteristicsAndroidLensInfoShadingMapSize;android.hardware.cts.LowRamDeviceTest#testAndroidTestCaseSetupProperly;android.hardware.cts.SensorTest#testSensorOperations;android.hardware.camera2.cts.CameraCharacteristicsTest#testCameraCharacteristicsAndroidControlAwbAvailableModes;android.hardware.camera2.cts.CameraCharacteristicsTest#testCameraCharacteristicsAndroidSensorBaseGainFactor;android.hardware.camera2.cts.CameraCharacteristicsTest#testCameraCharacteristicsAndroidSensorInfoPhysicalSize;android.hardware.camera2.cts.CameraCharacteristicsTest#testCameraCharacteristicsAndroidInfoSupportedHardwareLevel;android.hardware.camera2.cts.CameraCharacteristicsTest#testCameraCharacteristicsAndroidJpegAvailableThumbnailSizes;android.hardware.cts.SensorGyroscopeTest#testNotTriggerSensor;android.hardware.camera2.cts.CameraCharacteristicsTest#testCameraCharacteristicsAndroidLensFacing;android.hardware.cts.Camera_ParametersTest#testAccessMethods;android.hardware.cts.SensorMagneticFieldTest#testCanRegisterListener;android.hardware.camera2.cts.CameraCharacteristicsTest#testCameraCharacteristicsAndroidScalerAvailableFormats;android.hardware.cts.SensorTest#testBatchAndFlush;android.hardware.cts.SensorGyroscopeTest#testAndroidTestCaseSetupProperly;android.hardware.cts.SensorMagneticFieldTest#testCanReceiveEvents;android.hardware.camera2.cts.CameraCharacteristicsTest#testCameraCharacteristicsAndroidControlAvailableEffects;android.hardware.camera2.cts.CameraManagerTest#testAndroidTestCaseSetupProperly;android.hardware.cts.SensorGyroscopeTest#testSeveralClientsBatching;android.hardware.camera2.cts.CameraCharacteristicsTest#testCameraCharacteristicsAndroidControlAeAvailableAntibandingModes;android.hardware.cts.GeomagneticFieldTest#testAndroidTestCaseSetupProperly;android.hardware.cts.SensorAccelerometerTest#testStartStopRepeatedly;android.hardware.camera2.cts.CameraCharacteristicsTest#testCameraCharacteristicsAndroidControlAfAvailableModes;android.hardware.camera2.cts.CameraCharacteristicsTest#testCameraCharacteristicsAndroidSensorMaxAnalogSensitivity;android.hardware.cts.SensorTest#testRequestTriggerWithNonTriggerSensor;android.hardware.cts.SensorMagneticFieldTest#testBatchEventsArriveInOrder;android.hardware.camera2.cts.CameraCharacteristicsTest#testCameraCharacteristicsAndroidLensInfoMinimumFocusDistance;android.hardware.cts.SensorTest#testValuesForAllSensors;android.hardware.cts.SensorMagneticFieldTest#testCanReceiveBatchEvents;android.hardware.cts.SensorAccelerometerTest#testUpdateRate;android.hardware.cts.SensorMagneticFieldTest#testStartStopRepeatedly;android.hardware.camera2.cts.CameraCharacteristicsTest#testCameraCharacteristicsAndroidControlMaxRegions;android.hardware.cts.SensorMagneticFieldTest#testAndroidTestCaseSetupProperly;android.hardware.camera2.cts.CameraCharacteristicsTest#testCameraCharacteristicsAndroidControlAeCompensationRange;android.hardware.camera2.cts.CameraCharacteristicsTest#testCameraCharacteristicsAndroidRequestMaxNumOutputStreams;android.hardware.camera2.cts.CameraCharacteristicsTest#testCameraCharacteristicsAndroidSensorInfoSensitivityRange;android.hardware.cts.SensorMagneticFieldTest#testNotTriggerSensor;android.hardware.cts.SensorGyroscopeTest#testBatchEventsArriveInOrder;android.hardware.camera2.cts.CameraManagerTest#testCameraManagerInvalidDevice;android.hardware.cts.LowRamDeviceTest#testLowRamProductProperty;android.hardware.cts.SensorTest#testCancelTriggerWithNonTriggerSensor;android.hardware.cts.SensorMagneticFieldTest#testEventsArriveInOrder;android.hardware.cts.SensorMagneticFieldTest#testMaxFrequency;android.hardware.cts.SensorAccelerometerTest#testSeveralClientsBatching;android.hardware.cts.SensorMagneticFieldTest#testEventJittering;android.hardware.cts.SensorGyroscopeTest#testEventValidity;android.hardware.camera2.cts.ImageReaderTest#testImageReaderFromCameraRaw;android.hardware.camera2.cts.CameraCharacteristicsTest#testCameraCharacteristicsAndroidControlAeCompensationStep;android.hardware.cts.SensorMagneticFieldTest#testSeveralClientsBatching;android.hardware.camera2.cts.CameraManagerTest#testCameraManagerGetCameraCharacteristics;android.hardware.cts.SensorAccelerometerTest#testCanRegisterListener;android.hardware.camera2.cts.ImageReaderTest#testImageReaderFromCameraFlexibleYuv;android.hardware.camera2.cts.CameraCharacteristicsTest#testCameraCharacteristicsAndroidControlAvailableVideoStabilizationModes;android.hardware.cts.SensorGyroscopeTest#testStartStopRepeatedly;android.hardware.cts.SensorAccelerometerTest#testEventsArriveInOrder;android.hardware.cts.SensorMagneticFieldTest#testRegisterForBatchingZeroReport;android.hardware.cts.SensorGyroscopeTest#testCanRegisterListener;android.hardware.cts.SensorGyroscopeTest#testMaxFrequency;android.hardware.camera2.cts.CameraCharacteristicsTest#testCameraCharacteristicsAndroidScalerAvailableProcessedSizes;android.hardware.camera2.cts.CameraCharacteristicsTest#testCameraCharacteristicsAndroidControlAeAvailableTargetFpsRanges;android.hardware.camera2.cts.CameraCharacteristicsTest#testCameraCharacteristicsAndroidTonemapMaxCurvePoints;android.hardware.cts.SensorGyroscopeTest#testCanReceiveEvents;android.hardware.cts.SensorTest#testRegisterTwiceWithSameSensor;android.hardware.camera2.cts.CameraCharacteristicsTest#testCameraCharacteristicsAndroidLensInfoAvailableOpticalStabilization;android.hardware.camera2.cts.ImageReaderTest#testAndroidTestCaseSetupProperly;android.hardware.cts.SensorMagneticFieldTest#testSeveralClients;android.hardware.camera2.cts.CameraManagerTest#testCameraManagerOpenCameraTwice;android.hardware.cts.SensorMagneticFieldTest#testStartStopBatchingRepeatedly;android.hardware.camera2.cts.CameraCharacteristicsTest#testCameraCharacteristicsAndroidScalerAvailableMaxDigitalZoom;android.hardware.cts.SensorAccelerometerTest#testBatchEventsArriveInOrder;android.hardware.camera2.cts.CameraDeviceTest#testAndroidTestCaseSetupProperly;android.hardware.cts.SensorAccelerometerTest#testRegisterForBatchingZeroReport;android.hardware.camera2.cts.CameraCharacteristicsTest#testCameraCharacteristicsAndroidStatisticsInfoMaxFaceCount;android.hardware.camera2.cts.CameraCharacteristicsTest#testCameraCharacteristicsAndroidControlAvailableSceneModes;android.hardware.camera2.cts.CameraCharacteristicsTest#testCameraCharacteristicsAndroidSensorInfoActiveArraySize;android.hardware.camera2.cts.CameraCharacteristicsTest#testCameraCharacteristicsAndroidSensorInfoMaxFrameDuration;android.hardware.cts.SensorAccelerometerTest#testStartStopBatchingRepeatedly;android.hardware.cts.SensorGyroscopeTest#testEventJittering;android.hardware.camera2.cts.CameraDeviceTest#testCameraDeviceCapture;android.hardware.camera2.cts.CameraCharacteristicsTest#testCameraCharacteristicsAndroidSensorInfoExposureTimeRange;android.hardware.camera2.cts.CameraCharacteristicsTest#testCameraCharacteristicsAndroidLensInfoAvailableFocalLengths;android.hardware.cts.SensorGyroscopeTest#testCanReceiveBatchEvents;android.hardware.cts.SensorTest#testLegacySensorOperations;android.hardware.camera2.cts.CameraManagerTest#testCameraManagerListener;android.hardware.cts.GeomagneticFieldTest#testGeomagneticField;android.hardware.cts.SensorGyroscopeTest#testSeveralClients;android.hardware.cts.SensorGyroscopeTest#testStartStopBatchingRepeatedly;android.hardware.camera2.cts.CameraCharacteristicsTest#testCameraCharacteristicsAndroidLensInfoAvailableFilterDensities;android.hardware.camera2.cts.CameraDeviceTest#testCameraDeviceSetErrorListener;android.hardware.cts.SensorGyroscopeTest#testRegisterForBatchingZeroReport;android.hardware.camera2.cts.CameraManagerTest#testCameraManagerGetDeviceIdList;android.hardware.cts.SensorAccelerometerTest#testMaxFrequency;android.hardware.camera2.cts.CameraManagerTest#testCameraManagerOpenAllCameras;android.hardware.cts.SensorAccelerometerTest#testSeveralClients;android.hardware.cts.SensorGyroscopeTest#testVarianceWhileStatic;android.hardware.camera2.cts.CameraCharacteristicsTest#testCameraCharacteristicsAndroidScalerAvailableJpegMinDurations;android.hardware.cts.SensorTest#testAndroidTestCaseSetupProperly;android.hardware.camera2.cts.CameraCharacteristicsTest#testCameraCharacteristicsAndroidStatisticsInfoAvailableFaceDetectModes;android.hardware.camera2.cts.CameraCharacteristicsTest#testCameraCharacteristicsAndroidLensInfoAvailableApertures;android.hardware.cts.SensorAccelerometerTest#testEventValidity;android.hardware.cts.SensorAccelerometerTest#testUpdateBatchRate;android.hardware.cts.SensorAccelerometerTest#testVarianceWhileStatic;android.hardware.cts.SensorGyroscopeTest#testUpdateBatchRate;android.hardware.cts.SensorGyroscopeTest#testUpdateRate;android.hardware.cts.SensorMagneticFieldTest#testEventValidity;android.hardware.cts.SensorMagneticFieldTest#testUpdateBatchRate;android.hardware.cts.SensorTest#testRegisterWithTriggerSensor;android.hardware.cts.Camera_SizeTest#testConstructor" />
- <Entry uri="android.media" exclude="android.media.cts.MediaPlayerTest#testLocalVideo_3gp_H263_176x144_300kbps_12fps_AAC_Stereo_24kbps_22050Hz;android.media.cts.AudioPreProcessingTest#test2_1AecCreateAndRelease;android.media.cts.MediaCodecTest#testConcurrentAudioVideoEncodings;android.media.cts.EnvReverbTest#test2_0SetEnabledGetEnabled;android.media.cts.EncodeDecodeTest#testEncodeDecodeVideoFromBufferToBufferQVGA;android.media.cts.AudioTrackTest#testSetLoopPointsStartTooFar;android.media.cts.RingtoneManagerTest#testStopPreviousRingtone;android.media.cts.EnvReverbTest#test1_3Reflections;android.media.cts.MediaPlayerTest#testGapless3;android.media.cts.MediaPlayerTest#testGapless2;android.media.cts.MediaPlayerTest#testGapless1;android.media.cts.BassBoostTest#testAndroidTestCaseSetupProperly;android.media.cts.EnvReverbTest#test3_1EnableStatusListener;android.media.cts.ExtractDecodeEditEncodeMuxTest#testExtractDecodeEditEncodeMuxQVGA;android.media.cts.MediaRecorderTest#testRecorderCamera;android.media.cts.MediaPlayerTest#testGetTrackInfo;android.media.cts.MediaDrmMockTest#testIsCryptoSchemeNotSupported;android.media.cts.AudioPreProcessingTest#testAndroidTestCaseSetupProperly;android.media.cts.DecodeEditEncodeTest#testVideoEditQCIF;android.media.cts.MediaPlayerTest#testDeselectTrack;android.media.cts.AudioEffectTest#test3_7SetParameterAfterRelease;android.media.cts.MediaPlayerTest#testLocalVideo_3gp_H263_176x144_56kbps_12fps_AAC_Mono_24kbps_22050Hz;android.media.cts.AudioEffectTest#test4_2ControlStatusListener;android.media.cts.StreamingMediaPlayerTest#testHTTP_MPEG4SP_AAC_Video1;android.media.cts.StreamingMediaPlayerTest#testHTTP_MPEG4SP_AAC_Video2;android.media.cts.JetPlayerTest#testLoadJetFromFd;android.media.cts.MediaDrmMockTest#testProvideProvisionResponse;android.media.cts.JetPlayerTest#testQueueJetSegmentMuteArray;android.media.cts.StreamingMediaPlayerTest#testPlayOggStream;android.media.cts.MediaPlayerTest#testLocalVideo_MP4_H264_480x360_1350kbps_25fps_AAC_Stereo_128kbps_44110Hz;android.media.cts.SoundPoolAacTest#testAndroidTestCaseSetupProperly;android.media.cts.PresetReverbTest#test3_2ParameterChangedListener;android.media.cts.MediaDrmMockTest#testNullPropertyString;android.media.cts.AudioEffectTest#test3_2SetParameterIntShort;android.media.cts.AudioRecord_BufferSizeTest#testGetMinBufferSize;android.media.cts.AudioEffectTest#test3_0SetParameterByteArrayByteArray;android.media.cts.MediaScannerConnectionTest#testAndroidTestCaseSetupProperly;android.media.cts.MediaPlayerTest#testLocalVideo_MP4_H264_480x360_1000kbps_30fps_AAC_Stereo_128kbps_44110Hz;android.media.cts.AudioTrackTest#testWriteShortSizeTooBig;android.media.cts.AudioTrackTest#testGetMinBufferSizeTooLowSR;android.media.cts.AudioEffectTest#test1_1ConstructorFromUuid;android.media.cts.ToneGeneratorTest#testSyncGenerate;android.media.cts.AudioEffectTest#test1_3GetEnabledAfterRelease;android.media.cts.BassBoostTest#test1_1Properties;android.media.cts.SoundPoolOggTest#testLoadMore;android.media.cts.AudioEffectTest#test1_7AuxiliaryOnAudioTrack;android.media.cts.MediaPlayerTest#testLocalVideo_3gp_H263_176x144_300kbps_12fps_AAC_Stereo_128kbps_11025Hz;android.media.cts.MediaCodecListTest#testMediaCodecXmlFileExist;android.media.cts.MediaPlayerTest#testLocalVideo_MP4_H264_480x360_1000kbps_25fps_AAC_Stereo_128kbps_44110Hz;android.media.cts.VisualizerTest#test2_1ListenerCapture;android.media.cts.MediaPlayerTest#testLocalVideo_3gp_H263_176x144_56kbps_25fps_AAC_Stereo_24kbps_11025Hz;android.media.cts.StreamingMediaPlayerTest#testPlayMp3Stream2;android.media.cts.StreamingMediaPlayerTest#testPlayMp3Stream1;android.media.cts.MediaPlayerTest#testPlayNullSource;android.media.cts.VirtualizerTest#testAndroidTestCaseSetupProperly;android.media.cts.EncodeVirtualDisplayWithCompositionTest#testSingleVirtualDisplay;android.media.cts.DecoderTest#testDecodeOgg;android.media.cts.MediaRecorderTest#testOnErrorListener;android.media.cts.EnvReverbTest#test2_1SetEnabledAfterRelease;android.media.cts.AudioTrackTest#testSetPlaybackRate;android.media.cts.AudioManagerTest#testVibrateNotification;android.media.cts.AudioManagerTest#testAccessMode;android.media.cts.EqualizerTest#test1_4SetBandLevelAfterRelease;android.media.cts.MediaPlayerTest#testPlayVideo;android.media.cts.MediaCodecTest#testSignalSurfaceEOS;android.media.cts.VisualizerTest#test0_0ConstructorAndRelease;android.media.cts.DecoderTest#testDecodeFlac;android.media.cts.MediaDrmMockTest#testEventWithSessionAndData;android.media.cts.MediaScannerConnectionTest#testMediaScannerConnection;android.media.cts.AudioTrackTest#testConstructorMono8MusicStatic;android.media.cts.AudioManagerTest#testMicrophoneMute;android.media.cts.MediaDrmMockTest#testMissingPropertyString;android.media.cts.MediaPlayerTest#testLocalVideo_3gp_H263_176x144_300kbps_25fps_AAC_Stereo_128kbps_22050Hz;android.media.cts.AudioTrackTest#testConstructorStereo16MusicStream;android.media.cts.Vp8EncoderTest#testAndroidTestCaseSetupProperly;android.media.cts.MediaDrmMockTest#testQueryKeyStatus;android.media.cts.BassBoostTest#test3_2ParameterChangedListener;android.media.cts.AudioManagerTest#testVibrateRinger;android.media.cts.AudioManagerTest#testMusicActive;android.media.cts.DecoderTest#testCodecResetsVP8WithoutSurface;android.media.cts.MediaPlayerTest#testRecordedVideoPlayback90;android.media.cts.MediaPlayerFlakyNetworkTest#test_S3P00001;android.media.cts.AudioTrackTest#testGetMinBufferSizeTooHighSR;android.media.cts.CamcorderProfileTest#testAndroidTestCaseSetupProperly;android.media.cts.MediaPlayerTest#testLocalVideo_3gp_H263_176x144_56kbps_25fps_AAC_Stereo_128kbps_11025Hz;android.media.cts.AudioManagerTest#testVolume;android.media.cts.MediaMuxerTest#testAudioOnly;android.media.cts.MediaPlayerTest#testLocalVideo_3gp_H263_176x144_56kbps_12fps_AAC_Stereo_128kbps_22050Hz;android.media.cts.AudioEffectTest#test1_6AuxiliaryOnMediaPlayerFailure;android.media.cts.DecoderTest#testCodecResetsMpeg4WithSurface;android.media.cts.AudioEffectTest#test4_1setParameterLowerPriority;android.media.cts.MediaPlayerTest#testLocalVideo_MP4_H264_480x360_500kbps_30fps_AAC_Stereo_128kbps_44110Hz;android.media.cts.DecoderTest#testCodecResetsVP8WithSurface;android.media.cts.CamcorderProfileTest#testGet;android.media.cts.DecoderTest#testDecodeMonoM4a;android.media.cts.MediaRecorderTest#testSetCamera;android.media.cts.AudioTrackTest#testSetGetPlaybackRate;android.media.cts.EncodeDecodeTest#testEncodeDecodeVideoFromSurfaceToSurfaceQVGA;android.media.cts.EnvReverbTest#testAndroidTestCaseSetupProperly;android.media.cts.DecoderTest#testFlush;android.media.cts.AudioEffectTest#testAndroidTestCaseSetupProperly;android.media.cts.MediaPlayerTest#testLocalVideo_3gp_H263_176x144_300kbps_12fps_AAC_Mono_24kbps_11025Hz;android.media.cts.DecoderTest#testDecodeMp3Lame;android.media.cts.EncoderTest#testAMRNBEncoders;android.media.cts.MediaScannerTest#testAndroidTestCaseSetupProperly;android.media.cts.MediaCodecListTest#testRequiredMediaCodecList;android.media.cts.DecoderTest#testCodecResetsMpeg4WithoutSurface;android.media.cts.MediaRecorderTest#testGetAudioSourceMax;android.media.cts.MediaPlayerTest#testLocalVideo_MP4_H264_480x360_500kbps_25fps_AAC_Stereo_128kbps_44110Hz;android.media.cts.AudioTrackTest#testSetLoopPointsLoopTooLong;android.media.cts.EqualizerTest#test1_1BandFrequency;android.media.cts.EncoderTest#testAMRWBEncoders;android.media.cts.AudioTrackTest#testWriteShortNegativeSize;android.media.cts.AudioTrackTest#testConstructorStreamType;android.media.cts.MediaPlayerTest#testCallback;android.media.cts.BassBoostTest#test2_0SetEnabledGetEnabled;android.media.cts.MediaPlayerFlakyNetworkTest#test_S2P00001;android.media.cts.EncodeVirtualDisplayTest#testEncodeVirtualDisplay;android.media.cts.EncodeDecodeTest#testEncodeDecodeVideoFromSurfaceToSurfaceQCIF;android.media.cts.DecoderTest#testCodecEarlyEOSVP9;android.media.cts.MediaPlayerTest#testLocalVideo_MP4_H264_480x360_1350kbps_30fps_AAC_Stereo_192kbps_44110Hz;android.media.cts.DecoderTest#testCodecEarlyEOSVP8;android.media.cts.MediaPlayerFlakyNetworkTest#test_S5P00001;android.media.cts.MediaRandomTest#testRecorderRandomAction;android.media.cts.MediaDrmMockTest#testBadSession;android.media.cts.MediaPlayerTest#testLocalVideo_3gp_H263_176x144_56kbps_12fps_AAC_Stereo_128kbps_11025Hz;android.media.cts.CameraProfileTest#testGetWithId;android.media.cts.EnvReverbTest#test1_0Room;android.media.cts.StreamingMediaPlayerTest#testPlayMp3StreamNoLength;android.media.cts.SoundPoolAacTest#testMultiSound;android.media.cts.AudioTrackTest#testReloadStaticData;android.media.cts.MediaDrmMockTest#testAndroidTestCaseSetupProperly;android.media.cts.MediaPlayerTest#testPlayAudio;android.media.cts.MediaDrmMockTest#testGetKeyRequestNoOptionalParameters;android.media.cts.MediaRecorderTest#testSetMaxDuration;android.media.cts.AudioPreProcessingTest#test2_2AecSetEnabledGetEnabled;android.media.cts.AsyncPlayerTest#testAsyncPlayer;android.media.cts.DecoderTest#testCodecBasicVP8;android.media.cts.DecoderTest#testCodecBasicVP9;android.media.cts.AudioTrackTest#testSetLoopPointsStream;android.media.cts.AudioTrackTest#testConstructorStereo8MusicStatic;android.media.cts.MediaCodecTest#testDequeueSurface;android.media.cts.EncodeDecodeTest#testEncodeDecodeVideoFromBufferToBuffer720p;android.media.cts.MediaRecorderTest#testRecorderTimelapsedVideo;android.media.cts.MediaCodecTest#testAndroidTestCaseSetupProperly;android.media.cts.AudioTrackTest#testSetPlaybackHeadPositionStopped;android.media.cts.AudioTrackTest#testWriteByteSizeTooBig;android.media.cts.MediaMuxerTest#testAndroidTestCaseSetupProperly;android.media.cts.EnvReverbTest#test1_1Decay;android.media.cts.MediaPlayerTest#testLocalVideo_3gp_H263_176x144_300kbps_12fps_AAC_Stereo_128kbps_22050Hz;android.media.cts.DecoderTest#testDecodeWav;android.media.cts.DecoderTest#testCodecEarlyEOSH263;android.media.cts.SoundPoolOggTest#testSoundPoolOp;android.media.cts.DecoderTest#testCodecEarlyEOSH264;android.media.cts.MediaPlayerFlakyNetworkTest#test_S6P00002;android.media.cts.EnvReverbTest#test1_2Reverb;android.media.cts.AudioManagerTest#testSoundEffects;android.media.cts.ToneGeneratorTest#testAndroidTestCaseSetupProperly;android.media.cts.MediaCodecTest#testReconfigureWithoutSurface;android.media.cts.RingtoneManagerTest#testConstructors;android.media.cts.MediaCodecListTest#testGetCapabilities;android.media.cts.MediaDrmMockTest#testIsMimeTypeSupported;android.media.cts.DecoderTest#testDecodeMonoMp3;android.media.cts.DecoderTest#testCodecResetsH263WithoutSurface;android.media.cts.MediaScannerNotificationTest#testMediaScannerNotification;android.media.cts.VisualizerTest#testAndroidTestCaseSetupProperly;android.media.cts.VirtualizerTest#test0_0ConstructorAndRelease;android.media.cts.MediaPlayerTest#testLocalVideo_3gp_H263_176x144_56kbps_25fps_AAC_Mono_24kbps_22050Hz;android.media.cts.AudioTrackTest#testWriteShort;android.media.cts.AudioRecordTest#testAndroidTestCaseSetupProperly;android.media.cts.ExtractDecodeEditEncodeMuxTest#testExtractDecodeEditEncodeMux720p;android.media.cts.EqualizerTest#test3_0ControlStatusListener;android.media.cts.MediaDrmMockTest#testRestoreKeys;android.media.cts.MediaMuxerTest#testVideoAudio;android.media.cts.MediaPlayerTest#testLocalVideo_3gp_H263_176x144_56kbps_25fps_AAC_Mono_24kbps_11025Hz;android.media.cts.AudioTrackTest#testConstructorStereo8MusicStream;android.media.cts.MediaPlayerTest#testSetNextMediaPlayerWithRelease;android.media.cts.VisualizerTest#test1_0CaptureRates;android.media.cts.CameraProfileTest#testAndroidTestCaseSetupProperly;android.media.cts.EncodeVirtualDisplayWithCompositionTest#testAndroidTestCaseSetupProperly;android.media.cts.EncodeDecodeTest#testEncodeDecodeVideoFromBufferToSurfaceQVGA;android.media.cts.VirtualizerTest#test1_1Properties;android.media.cts.AudioManagerTest#testRouting;android.media.cts.EqualizerTest#test3_1EnableStatusListener;android.media.cts.PresetReverbTest#test3_1EnableStatusListener;android.media.cts.EnvReverbTest#test0_0ConstructorAndRelease;android.media.cts.MediaPlayerTest#testChangeSubtitleTrack;android.media.cts.JetPlayerTest#testClone;android.media.cts.AudioEffectTest#test1_5AuxiliaryOnMediaPlayer;android.media.cts.MediaPlayerTest#testSetNextMediaPlayerWithReset;android.media.cts.AudioEffectTest#test2_0SetEnabledGetEnabled;android.media.cts.VisualizerTest#test1_1CaptureSize;android.media.cts.AudioTrackTest#testPlaybackHeadPositionAfterInit;android.media.cts.EqualizerTest#test1_3Properties;android.media.cts.AudioTrackTest#testWriteByte;android.media.cts.VirtualizerTest#test2_1SetEnabledAfterRelease;android.media.cts.AudioTrackTest#testSetStereoVolumeMin;android.media.cts.MediaRecorderTest#testRecorderVideo;android.media.cts.ExtractDecodeEditEncodeMuxTest#testExtractDecodeEditEncodeMuxAudioVideo;android.media.cts.EqualizerTest#test2_0SetEnabledGetEnabled;android.media.cts.MediaPlayerTest#testSetNextMediaPlayer;android.media.cts.AudioTrackTest#testSetStereoVolumeMid;android.media.cts.VirtualizerTest#test1_0Strength;android.media.cts.EncodeDecodeTest#testAndroidTestCaseSetupProperly;android.media.cts.AudioTrackTest#testAudioTrackProperties;android.media.cts.AudioTrackTest#testSetPlaybackHeadPositionPaused;android.media.cts.SoundPoolAacTest#testLoadMore;android.media.cts.ExtractDecodeEditEncodeMuxTest#testExtractDecodeEditEncodeMuxQCIF;android.media.cts.EncodeVirtualDisplayTest#testAndroidTestCaseSetupProperly;android.media.cts.RingtoneManagerTest#testSetType;android.media.cts.MediaDrmMockTest#testMultipleSessions;android.media.cts.MediaPlayerFlakyNetworkTest#test_S0P0;android.media.cts.SoundPoolOggTest#testLoad;android.media.cts.PresetReverbTest#test0_0ConstructorAndRelease;android.media.cts.RingtoneManagerTest#testAccessMethods;android.media.cts.EnvReverbTest#test1_4DiffusionAndDensity;android.media.cts.MediaPlayerTest#testLocalVideo_3gp_H263_176x144_56kbps_12fps_AAC_Stereo_24kbps_22050Hz;android.media.cts.MediaPlayerTest#testLocalVideo_3gp_H263_176x144_300kbps_25fps_AAC_Stereo_24kbps_22050Hz;android.media.cts.MediaDrmMockTest#testGetProvisionRequest;android.media.cts.CamcorderProfileTest#testGetWithId;android.media.cts.AudioEffectTest#test1_2ConstructorUnknownType;android.media.cts.SoundPoolOggTest#testAndroidTestCaseSetupProperly;android.media.cts.AudioTrackTest#testWriteByte8bit;android.media.cts.PresetReverbTest#testAndroidTestCaseSetupProperly;android.media.cts.EncoderTest#testAACEncoders;android.media.cts.ExtractDecodeEditEncodeMuxTest#testExtractDecodeEditEncodeMuxAudio;android.media.cts.DecoderTest#testCodecBasicH263;android.media.cts.DecoderTest#testCodecBasicH264;android.media.cts.AudioTrackTest#testSetLoopPointsSuccess;android.media.cts.MediaDrmMockTest#testRemoveKeys;android.media.cts.FaceDetectorTest#testFindFaces;android.media.cts.MediaDrmMockTest#testGetKeyRequest;android.media.cts.MediaCodecListTest#testIsAVCBaselineProfileSupported;android.media.cts.AudioEffectTest#test3_4SetParameterIntArrayIntArray;android.media.cts.MediaCodecListTest#testIsH263BaselineProfileSupported;android.media.cts.EqualizerTest#test0_0ConstructorAndRelease;android.media.cts.EqualizerTest#test3_2ParameterChangedListener;android.media.cts.AudioTrackTest#testSetPlaybackRateZero;android.media.cts.MediaDrmMockTest#testGetSecureStops;android.media.cts.EnvReverbTest#test3_2ParameterChangedListener;android.media.cts.StreamingMediaPlayerTest#testHTTP_H263_AMR_Video2;android.media.cts.MediaDrmMockTest#testGetKeyRequestRelease;android.media.cts.StreamingMediaPlayerTest#testHTTP_H263_AMR_Video1;android.media.cts.EqualizerTest#testAndroidTestCaseSetupProperly;android.media.cts.ExtractDecodeEditEncodeMuxTest#testAndroidTestCaseSetupProperly;android.media.cts.MediaDrmMockTest#testNullSession;android.media.cts.PresetReverbTest#test1_1Properties;android.media.cts.BassBoostTest#test3_1EnableStatusListener;android.media.cts.AudioTrack_ListenerTest#testAudioTrackCallbackWithHandler;android.media.cts.VirtualizerTest#test3_2ParameterChangedListener;android.media.cts.MediaPlayerTest#testLocalVideo_MP4_H264_480x360_1350kbps_30fps_AAC_Stereo_128kbps_44110Hz;android.media.cts.BassBoostTest#test1_0Strength;android.media.cts.AudioTrackTest#testPlaybackHeadPositionAfterFlushAndPlay;android.media.cts.PresetReverbTest#test2_1SetEnabledAfterRelease;android.media.cts.AudioTrackTest#testWriteShortNegativeOffset;android.media.cts.DecoderTest#testCodecResetsMp3;android.media.cts.AudioTrackTest#testPlaybackHeadPositionAfterFlush;android.media.cts.MediaScannerTest#testCanonicalize;android.media.cts.MediaDrmMockTest#testMediaDrmConstructorFails;android.media.cts.PresetReverbTest#test2_0SetEnabledGetEnabled;android.media.cts.MediaDrmMockTest#testCryptoSession;android.media.cts.DecoderTest#testCodecResetsH263WithSurface;android.media.cts.AudioPreProcessingTest#test1_1NsCreateAndRelease;android.media.cts.DecodeEditEncodeTest#testAndroidTestCaseSetupProperly;android.media.cts.AudioEffectTest#test3_6SetParameterIntArrayByteArray;android.media.cts.AudioTrackTest#testPlaybackHeadPositionAfterStop;android.media.cts.AsyncPlayerTest#testAndroidTestCaseSetupProperly;android.media.cts.AudioPreProcessingTest#test3_2AgcSetEnabledGetEnabled;android.media.cts.MediaDrmMockTest#testGetKeyRequestOffline;android.media.cts.MediaDrmMockTest#testReleaseSecureStops;android.media.cts.MediaPlayerFlakyNetworkTest#test_S4P00001;android.media.cts.MediaDrmMockTest#testNullPropertyByteArray;android.media.cts.EqualizerTest#test1_2Presets;android.media.cts.EnvReverbTest#test1_5Properties;android.media.cts.CameraProfileTest#testGetImageEncodingQualityParameter;android.media.cts.VirtualizerTest#test3_0ControlStatusListener;android.media.cts.AudioTrackTest#testWriteByteNegativeOffset;android.media.cts.StreamingMediaPlayerTest#testPlayMp3StreamRedirect;android.media.cts.DecoderTest#testCodecEarlyEOSMpeg4;android.media.cts.EnvReverbTest#test3_0ControlStatusListener;android.media.cts.EncodeDecodeTest#testEncodeDecodeVideoFromBufferToSurfaceQCIF;android.media.cts.EncodeDecodeTest#testEncodeDecodeVideoFromBufferToSurface720p;android.media.cts.MediaDrmMockTest#testCryptoSessionEncrypt;android.media.cts.EqualizerTest#test1_0BandLevel;android.media.cts.EncodeVirtualDisplayWithCompositionTest#testMultipleVirtualDisplays;android.media.cts.AudioTrack_ListenerTest#testAudioTrackCallback;android.media.cts.DecoderTest#testCodecResetsVP9WithoutSurface;android.media.cts.DecodeEditEncodeTest#testVideoEdit720p;android.media.cts.AudioEffectTest#test1_0ConstructorFromType;android.media.cts.AudioTrackTest#testPlayStreamData;android.media.cts.MediaDrmMockTest#testCryptoSessionSign;android.media.cts.MediaDrmMockTest#testMediaDrmConstructor;android.media.cts.AudioTrackTest#testSetStereoVolumeMax;android.media.cts.MediaMuxerTest#testIllegalStateExceptions;android.media.cts.MediaCodecTest#testCreateInputSurfaceErrors;android.media.cts.AudioTrackTest#testConstructorMono16MusicStatic;android.media.cts.MediaDrmMockTest#testOpenCloseSession;android.media.cts.AudioManagerTest#testAndroidTestCaseSetupProperly;android.media.cts.DecoderTest#testEOSBehaviorMpeg4;android.media.cts.SoundPoolOggTest#testMultiSound;android.media.cts.AudioTrackTest#testConstructorMono8MusicStream;android.media.cts.MediaCodecListTest#testIsM4VSimpleProfileSupported;android.media.cts.VirtualizerTest#test1_2SetStrengthAfterRelease;android.media.cts.MediaDrmMockTest#testCryptoSessionVerify;android.media.cts.MediaMuxerTest#testVideoOnly;android.media.cts.AudioTrackTest#testWriteShortOffsetTooBig;android.media.cts.AudioManagerTest#testSetInvalidRingerMode;android.media.cts.MediaDrmMockTest#testIsMimeTypeNotSupported;android.media.cts.StreamingMediaPlayerTest#testPlayOggStreamNoLength;android.media.cts.MediaPlayerFlakyNetworkTest#test_S1P000005;android.media.cts.MediaRandomTest#testPlayerRandomAction;android.media.cts.EncoderTest#testAndroidTestCaseSetupProperly;android.media.cts.MediaPlayerTest#testRecordedVideoPlayback0;android.media.cts.AudioRecordTest#testAudioRecordProperties;android.media.cts.AudioTrackTest#testWriteByteOffsetTooBig;android.media.cts.PresetReverbTest#test3_0ControlStatusListener;android.media.cts.DecodeEditEncodeTest#testVideoEditQVGA;android.media.cts.MediaDrmMockTest#testMissingPropertyByteArray;android.media.cts.MediaPlayerTest#testRecordAndPlay;android.media.cts.PresentationSyncTest#testThroughput;android.media.cts.MediaPlayerTest#testVideoSurfaceResetting;android.media.cts.AudioTrack_ListenerTest#testAndroidTestCaseSetupProperly;android.media.cts.EncodeDecodeTest#testEncodeDecodeVideoFromBufferToBufferQCIF;android.media.cts.MediaPlayerTest#testRecordedVideoPlayback180;android.media.cts.MediaRecorderTest#testRecorderAudio;android.media.cts.EqualizerTest#test2_1SetEnabledAfterRelease;android.media.cts.AudioTrackTest#testPlaybackHeadPositionAfterPause;android.media.cts.MediaPlayerTest#testLocalVideo_3gp_H263_176x144_300kbps_25fps_AAC_Stereo_128kbps_11025Hz;android.media.cts.MediaRecorderTest#testSetMaxFileSize;android.media.cts.Vp8EncoderTest#testBasic;android.media.cts.AudioTrackTest#testPlaybackHeadPositionIncrease;android.media.cts.AudioTrackTest#testAndroidTestCaseSetupProperly;android.media.cts.MediaDrmMockTest#testEventNoSessionNoData;android.media.cts.MediaDrmMockTest#testByteArrayProperties;android.media.cts.DecoderTest#testCodecResetsM4a;android.media.cts.RingtoneTest#testAndroidTestCaseSetupProperly;android.media.cts.AudioPreProcessingTest#test3_1AgcCreateAndRelease;android.media.cts.AudioTrackTest#testSetPlaybackRateTwiceOutputSR;android.media.cts.PresetReverbTest#test1_0Presets;android.media.cts.StreamingMediaPlayerTest#testPlayOggStreamRedirect;android.media.cts.MediaCodecListTest#testAndroidTestCaseSetupProperly;android.media.cts.AudioTrackTest#testSetLoopPointsStartAfterEnd;android.media.cts.MediaRecorderTest#testOnInfoListener;android.media.cts.VirtualizerTest#test3_1EnableStatusListener;android.media.cts.DecoderTest#testCodecResetsH264WithoutSurface;android.media.cts.MediaPlayerTest#testLocalVideo_3gp_H263_176x144_300kbps_12fps_AAC_Stereo_24kbps_11025Hz;android.media.cts.MediaPlayerTest#testRecordedVideoPlayback270;android.media.cts.BassBoostTest#test3_0ControlStatusListener;android.media.cts.EncodeDecodeTest#testEncodeDecodeVideoFromSurfaceToSurface720p;android.media.cts.AudioTrackTest#testSetPlaybackRateUninit;android.media.cts.BassBoostTest#test2_1SetEnabledAfterRelease;android.media.cts.AudioEffectTest#test0_0QueryEffects;android.media.cts.VisualizerTest#test2_0PollingCapture;android.media.cts.MediaDrmMockTest#testStringProperties;android.media.cts.BassBoostTest#test0_0ConstructorAndRelease;android.media.cts.MediaScannerNotificationTest#testAndroidTestCaseSetupProperly;android.media.cts.AudioTrackTest#testConstructorMono16MusicStream;android.media.cts.MediaRecorderTest#testRecordingAudioInRawFormats;android.media.cts.AudioEffectTest#test3_1SetParameterIntInt;android.media.cts.DecoderTest#testEOSBehaviorVP8;android.media.cts.StreamingMediaPlayerTest#testHTTP_H264Base_AAC_Video2;android.media.cts.AudioEffectTest#test1_4InsertOnMediaPlayer;android.media.cts.StreamingMediaPlayerTest#testHTTP_H264Base_AAC_Video1;android.media.cts.DecoderTest#testCodecResetsVP9WithSurface;android.media.cts.JetPlayerTest#testLoadJetFromPath;android.media.cts.DecoderTest#testEOSBehaviorVP9;android.media.cts.MediaDrmMockTest#testProvideKeyResponse;android.media.cts.DecoderTest#testDecodeM4a;android.media.cts.AudioTrackTest#testWriteByteNegativeSize;android.media.cts.AudioEffectTest#test4_0setEnabledLowerPriority;android.media.cts.MediaPlayerTest#testLocalVideo_3gp_H263_176x144_300kbps_12fps_AAC_Mono_24kbps_22050Hz;android.media.cts.DecoderTest#testEOSBehaviorH264;android.media.cts.DecoderTest#testEOSBehaviorH263;android.media.cts.DecoderTest#testDecodeMonoOgg;android.media.cts.MediaPlayerTest#testLocalVideo_3gp_H263_176x144_56kbps_12fps_AAC_Stereo_24kbps_11025Hz;android.media.cts.MediaPlayerTest#testLocalVideo_3gp_H263_176x144_56kbps_25fps_AAC_Stereo_24kbps_22050Hz;android.media.cts.AudioTrackTest#testSetPlaybackHeadPositionTooFar;android.media.cts.AudioRecord_BufferSizeTest#testAndroidTestCaseSetupProperly;android.media.cts.MediaPlayerTest#testLocalVideo_3gp_H263_176x144_56kbps_12fps_AAC_Mono_24kbps_11025Hz;android.media.cts.MediaCodecListTest#testComponentInstantiation;android.media.cts.BassBoostTest#test1_2SetStrengthAfterRelease;android.media.cts.MediaPlayerTest#testLocalVideo_3gp_H263_176x144_300kbps_25fps_AAC_Stereo_24kbps_11025Hz;android.media.cts.AudioTrackTest#testSetLoopPointsEndTooFar;android.media.cts.MediaPlayerTest#testLocalVideo_3gp_H263_176x144_300kbps_25fps_AAC_Mono_24kbps_11025Hz;android.media.cts.MediaScannerTest#testMediaScanner;android.media.cts.RingtoneTest#testRingtone;android.media.cts.AudioEffectTest#test5_0Command;android.media.cts.DecoderTest#testDecodeMp3Smpb;android.media.cts.AudioTrackTest#testWriteShort8bit;android.media.cts.AudioEffectTest#test3_3SetParameterIntByteArray;android.media.cts.AudioEffectTest#test4_3EnableStatusListener;android.media.cts.MediaPlayerTest#testLocalVideo_3gp_H263_176x144_300kbps_25fps_AAC_Mono_24kbps_22050Hz;android.media.cts.JetPlayerTest#testAndroidTestCaseSetupProperly;android.media.cts.Vp8EncoderTest#testSyncFrame;android.media.cts.VirtualizerTest#test2_0SetEnabledGetEnabled;android.media.cts.MediaPlayerSurfaceTest#testSetSurface;android.media.cts.SoundPoolAacTest#testLoad;android.media.cts.AudioEffectTest#test3_5SetParameterIntArrayShortArray;android.media.cts.MediaDrmMockTest#testBadCryptoSession;android.media.cts.AudioTrackTest#testConstructorStereo16MusicStatic;android.media.cts.AudioPreProcessingTest#test1_2NsSetEnabledGetEnabled;android.media.cts.AudioEffectTest#test2_1SetEnabledAfterRelease;android.media.cts.AudioManagerTest#testAccessRingMode;android.media.cts.SoundPoolAacTest#testSoundPoolOp;android.media.cts.AudioEffectTest#test4_4ParameterChangedListener;android.media.cts.DecoderTest#testCodecBasicMpeg4;android.media.cts.AudioTrackTest#testSetPlaybackHeadPositionPlaying;android.media.cts.AudioRecordTest#testAudioRecordOP;android.media.cts.MediaScannerTest#testWildcardPaths;android.media.cts.AudioEffectTest#test3_8GetParameterAfterRelease;android.media.cts.FaceDetector_FaceTest#testFaceProperties;android.media.cts.MediaPlayerTest#testLocalVideo_3gp_H263_176x144_56kbps_25fps_AAC_Stereo_128kbps_22050Hz;android.media.cts.MediaDrmMockTest#testCryptoSessionDecrypt" />
- <Entry uri="android.mediastress" exclude="android.mediastress.cts.H264R720pAacShortPlayerTest#testPlay00;android.mediastress.cts.Vp8R480x360ShortPlayerTest#testPlay10;android.mediastress.cts.Vp8R480x360ShortPlayerTest#testPlay11;android.mediastress.cts.H264R720pAacShortPlayerTest#testPlay07;android.mediastress.cts.H264R720pAacShortPlayerTest#testPlay08;android.mediastress.cts.H264R480pAacShortPlayerTest#testPlay10;android.mediastress.cts.H264R720pAacShortPlayerTest#testPlay05;android.mediastress.cts.H264R480pAacShortPlayerTest#testPlay11;android.mediastress.cts.H264R720pAacShortPlayerTest#testPlay06;android.mediastress.cts.H264R720pAacShortPlayerTest#testPlay03;android.mediastress.cts.H264R720pAacShortPlayerTest#testPlay04;android.mediastress.cts.MediaRecorderStressTest#testStressRecorder;android.mediastress.cts.H264R720pAacShortPlayerTest#testPlay01;android.mediastress.cts.H264R720pAacShortPlayerTest#testPlay02;android.mediastress.cts.H263QcifShortPlayerTest#testPlay07;android.mediastress.cts.MediaRecorderStressTest#testStressCamera;android.mediastress.cts.H263QcifShortPlayerTest#testPlay08;android.mediastress.cts.H263QcifShortPlayerTest#testPlay05;android.mediastress.cts.H263QcifShortPlayerTest#testPlay06;android.mediastress.cts.H263QcifShortPlayerTest#testPlay03;android.mediastress.cts.H263QcifShortPlayerTest#testPlay04;android.mediastress.cts.H263QcifShortPlayerTest#testPlay01;android.mediastress.cts.H263QcifShortPlayerTest#testPlay02;android.mediastress.cts.H264R720pAacLongPlayerTest#testPlay01;android.mediastress.cts.H264R720pAacLongPlayerTest#testPlay00;android.mediastress.cts.MediaRecorderStressTest#testStressCameraSwitchRecorder;android.mediastress.cts.H263QcifShortPlayerTest#testPlay09;android.mediastress.cts.H264R480x360AacShortPlayerTest#testPlay06;android.mediastress.cts.NativeMediaTest#test1080pPlay;android.mediastress.cts.H264R480x360AacShortPlayerTest#testPlay05;android.mediastress.cts.NativeMediaTest#testDefaultPlay;android.mediastress.cts.H264R480x360AacShortPlayerTest#testPlay08;android.mediastress.cts.H264R480x360AacShortPlayerTest#testPlay07;android.mediastress.cts.Vp8R480x360ShortPlayerTest#testPlay02;android.mediastress.cts.Vp8R480x360ShortPlayerTest#testPlay03;android.mediastress.cts.H264R480x360AacShortPlayerTest#testPlay09;android.mediastress.cts.Vp8R480x360ShortPlayerTest#testPlay00;android.mediastress.cts.Vp8R480x360ShortPlayerTest#testPlay01;android.mediastress.cts.Vp8R480x360ShortPlayerTest#testPlay06;android.mediastress.cts.H263QcifShortPlayerTest#testPlay00;android.mediastress.cts.H264R1080pAacShortPlayerTest#testPlay00;android.mediastress.cts.Vp8R480x360ShortPlayerTest#testPlay07;android.mediastress.cts.Vp8R480x360ShortPlayerTest#testPlay04;android.mediastress.cts.H264R480x360AacShortPlayerTest#testPlay00;android.mediastress.cts.Vp8R480x360ShortPlayerTest#testPlay05;android.mediastress.cts.H264R480x360AacShortPlayerTest#testPlay02;android.mediastress.cts.H264R480x360AacShortPlayerTest#testPlay01;android.mediastress.cts.Vp8R480x360ShortPlayerTest#testPlay08;android.mediastress.cts.H264R1080pAacShortPlayerTest#testPlay02;android.mediastress.cts.H264R480x360AacShortPlayerTest#testPlay04;android.mediastress.cts.Vp8R480x360ShortPlayerTest#testPlay09;android.mediastress.cts.H264R1080pAacShortPlayerTest#testPlay01;android.mediastress.cts.H264R480x360AacShortPlayerTest#testPlay03;android.mediastress.cts.H264R1080pAacRepeatedPlayerTest#testPlay00;android.mediastress.cts.H263QcifShortPlayerTest#testPlay13;android.mediastress.cts.H263QcifShortPlayerTest#testPlay12;android.mediastress.cts.H263QcifShortPlayerTest#testPlay15;android.mediastress.cts.H263QcifShortPlayerTest#testPlay14;android.mediastress.cts.H263QcifLongPlayerTest#testPlay00;android.mediastress.cts.H263QcifShortPlayerTest#testPlay17;android.mediastress.cts.H264R480pAacLongPlayerTest#testPlay00;android.mediastress.cts.H263QcifShortPlayerTest#testPlay16;android.mediastress.cts.H263QcifShortPlayerTest#testPlay19;android.mediastress.cts.H263QcifShortPlayerTest#testPlay18;android.mediastress.cts.H263QcifShortPlayerTest#testPlay10;android.mediastress.cts.H263QcifShortPlayerTest#testPlay11;android.mediastress.cts.H264R480x360AacShortPlayerTest#testPlay11;android.mediastress.cts.H264R480x360AacShortPlayerTest#testPlay10;android.mediastress.cts.NativeMediaTest#test720pPlay;android.mediastress.cts.H264R1080pAacLongPlayerTest#testPlay00;android.mediastress.cts.H264R720pAacShortPlayerTest#testPlay12;android.mediastress.cts.H264R480pAacShortPlayerTest#testPlay01;android.mediastress.cts.H264R480pAacShortPlayerTest#testPlay00;android.mediastress.cts.H263QcifShortPlayerTest#testPlay23;android.mediastress.cts.H264R720pAacShortPlayerTest#testPlay11;android.mediastress.cts.H264R720pAacShortPlayerTest#testPlay10;android.mediastress.cts.H263QcifShortPlayerTest#testPlay21;android.mediastress.cts.H263QcifShortPlayerTest#testPlay22;android.mediastress.cts.H263QcifShortPlayerTest#testPlay20;android.mediastress.cts.H264R480pAacShortPlayerTest#testPlay02;android.mediastress.cts.H264R480pAacShortPlayerTest#testPlay03;android.mediastress.cts.MediaRecorderStressTest#testStressRecordVideoAndPlayback;android.mediastress.cts.H264R720pAacShortPlayerTest#testPlay09;android.mediastress.cts.H264R480pAacShortPlayerTest#testPlay04;android.mediastress.cts.H264R480pAacShortPlayerTest#testPlay05;android.mediastress.cts.Vp8R480x360LongPlayerTest#testPlay00;android.mediastress.cts.H264R480pAacShortPlayerTest#testPlay06;android.mediastress.cts.H264R480pAacShortPlayerTest#testPlay07;android.mediastress.cts.H264R480pAacShortPlayerTest#testPlay08;android.mediastress.cts.H264R480pAacShortPlayerTest#testPlay09" />
- <Entry uri="android.net" exclude="android.net.wifi.cts.ScanResultTest#testScanResultProperties;android.net.wifi.cts.WifiManagerTest#testWifiManagerActions;android.net.cts.UriTest#testStringUri;android.net.cts.UrlQuerySanitizer_IllegalCharacterValueSanitizerTest#testAndroidTestCaseSetupProperly;android.net.rtp.cts.AudioGroupTest#testAdd;android.net.rtp.cts.AudioGroupTest#testAndroidTestCaseSetupProperly;android.net.rtp.cts.AudioGroupTest#testRemove;android.net.cts.LocalSocketTest#testAccessors;android.net.http.cts.SslErrorTest#testConstructorIgnoresInvalidValues;android.net.cts.UriTest#testCompareTo;android.net.http.cts.SslErrorTest#testAddErrorIgnoresInvalidValues;android.net.cts.VpnServiceTest#testProtect_DatagramSocket;android.net.wifi.cts.WifiConfigurationTest#testAndroidTestCaseSetupProperly;android.net.rtp.cts.AudioCodecTest#testGetCodecs;android.net.http.cts.ApacheHttpClientTest#testExecute_withWifi;android.net.cts.DnsTest#testAndroidTestCaseSetupProperly;android.net.cts.VpnServiceTest#testAndroidTestCaseSetupProperly;android.net.wifi.cts.SupplicantStateTest#testAndroidTestCaseSetupProperly;android.net.cts.NetworkInfo_StateTest#testValues;android.net.cts.UriTest#testAndroidTestCaseSetupProperly;android.net.cts.LocalSocketTest#testLocalConnections;android.net.wifi.cts.WifiManager_WifiLockTest#testAndroidTestCaseSetupProperly;android.net.wifi.cts.WifiManagerTest#testWifiManagerNetWork;android.net.cts.ConnectivityManagerTest#testGetAllNetworkInfo;android.net.rtp.cts.AudioGroupTest#testTraffic;android.net.cts.Uri_BuilderTest#testBuilderOperations;android.net.cts.UriTest#testBuildUpon;android.net.cts.TrafficStatsTest#testAndroidTestCaseSetupProperly;android.net.cts.ProxyTest#testConstructor;android.net.rtp.cts.AudioCodecTest#testGetCodec;android.net.cts.DhcpInfoTest#testConstructor;android.net.cts.LocalSocketAddress_NamespaceTest#testValues;android.net.cts.ConnectivityManagerTest#testRequestRouteToHost;android.net.cts.ConnectivityManagerTest#testSetNetworkPreference;android.net.http.cts.SslCertificateTest#testState;android.net.cts.NetworkInfo_DetailedStateTest#testAndroidTestCaseSetupProperly;android.net.rtp.cts.AudioCodecTest#testConstants;android.net.http.cts.SslCertificateTest#testSslCertificate;android.net.cts.ProxyTest#testAccessProperties;android.net.cts.CredentialsTest#testCredentials;android.net.rtp.cts.AudioStreamTest#testDoubleRelease;android.net.cts.TrafficStatsTest#testValidMobileStats;android.net.http.cts.SslErrorTest#testGetUrlWithDeprecatedConstructor;android.net.cts.VpnServiceTest#testProtect_int;android.net.cts.ConnectivityManagerTest#testGetNetworkInfo;android.net.cts.MailToTest#testAndroidTestCaseSetupProperly;android.net.cts.LocalSocketTest#testAndroidTestCaseSetupProperly;android.net.cts.LocalSocketAddress_NamespaceTest#testAndroidTestCaseSetupProperly;android.net.cts.NetworkInfo_DetailedStateTest#testValueOf;android.net.cts.DhcpInfoTest#testAndroidTestCaseSetupProperly;android.net.cts.UriTest#testPathOperations;android.net.rtp.cts.AudioStreamTest#testAndroidTestCaseSetupProperly;android.net.http.cts.SslCertificate_DNameTest#testDName;android.net.wifi.cts.WifiManagerTest#testSignal;android.net.cts.UrlQuerySanitizerTest#testUrlQuerySanitizer;android.net.wifi.cts.WifiManager_WifiLockTest#testWifiLock;android.net.rtp.cts.AudioStreamTest#testSetCodec;android.net.cts.MailToTest#testParseMailToURI;android.net.cts.UriTest#testParcelling;android.net.cts.UrlQuerySanitizerTest#testAndroidTestCaseSetupProperly;android.net.wifi.cts.WifiConfigurationTest#testWifiConfiguration;android.net.cts.NetworkInfoTest#testAccessNetworkInfoProperties;android.net.cts.ConnectivityManagerTest#testTest;android.net.http.cts.SslErrorTest#testGetPrimaryError;android.net.cts.NetworkInfo_StateTest#testValueOf;android.net.cts.UriTest#testFromFile;android.net.cts.VpnServiceTest#testTunDevice;android.net.rtp.cts.AudioStreamTest#testSetDtmfType;android.net.cts.SSLCertificateSocketFactoryTest#testAndroidTestCaseSetupProperly;android.net.http.cts.SslCertificateTest#testConstructor;android.net.http.cts.SslErrorTest#testAddError;android.net.cts.UriTest#testNormalizeScheme;android.net.wifi.cts.WifiInfoTest#testAndroidTestCaseSetupProperly;android.net.cts.ConnectivityManagerTest#testStartUsingNetworkFeature;android.net.cts.UriTest#testHierarchicalUris;android.net.cts.DhcpInfoTest#testToString;android.net.rtp.cts.AudioStreamTest#testV6Stream;android.net.cts.LocalSocketAddressTest#testAndroidTestCaseSetupProperly;android.net.cts.UrlQuerySanitizer_IllegalCharacterValueSanitizerTest#testSanitize;android.net.rtp.cts.AudioStreamTest#testV4Stream;android.net.wifi.cts.ConcurrencyTest#testConcurrency;android.net.cts.LocalSocketAddress_NamespaceTest#testValueOf;android.net.wifi.cts.WifiManagerTest#testWifiWatchdog;android.net.cts.UriTest#testEqualsAndHashCode;android.net.cts.VpnServiceTest#testPrepare;android.net.cts.UriTest#testOpaqueUri;android.net.cts.UriTest#testQueryParameters;android.net.cts.TrafficStatsTest#testThreadStatsTag;android.net.wifi.cts.SupplicantStateTest#testIsValidState;android.net.http.cts.SslErrorTest#testHasError;android.net.cts.SSLCertificateSocketFactoryTest#testAccessProperties;android.net.cts.VpnServiceTest#testEstablish;android.net.ipv6.cts.PingTest#testAndroidTestCaseSetupProperly;android.net.cts.NetworkInfoTest#testAndroidTestCaseSetupProperly;android.net.cts.ConnectivityManagerTest#testAndroidTestCaseSetupProperly;android.net.cts.UriTest#testEncodeAndDecode;android.net.rtp.cts.AudioGroupTest#testClear;android.net.cts.NetworkInfo_StateTest#testAndroidTestCaseSetupProperly;android.net.cts.UrlQuerySanitizer_ParameterValuePairTest#testAndroidTestCaseSetupProperly;android.net.cts.ProxyTest#testAndroidTestCaseSetupProperly;android.net.cts.LocalServerSocketTest#testLocalServerSocket;android.net.cts.CredentialsTest#testAndroidTestCaseSetupProperly;android.net.rtp.cts.AudioCodecTest#testAndroidTestCaseSetupProperly;android.net.cts.UrlQuerySanitizer_ParameterValuePairTest#testConstructor;android.net.cts.NetworkInfo_DetailedStateTest#testValues;android.net.http.cts.SslErrorTest#testGetPrimaryErrorWithEmptySet;android.net.rtp.cts.AudioGroupTest#testSetMode;android.net.wifi.cts.WifiInfoTest#testWifiInfoProperties;android.net.wifi.cts.ConcurrencyTest#testAndroidTestCaseSetupProperly;android.net.http.cts.ApacheHttpClientTest#testAndroidTestCaseSetupProperly;android.net.wifi.cts.WifiManagerTest#testWifiManagerProperties;android.net.rtp.cts.AudioGroupTest#testDoubleClear;android.net.wifi.cts.WifiManagerTest#testAndroidTestCaseSetupProperly;android.net.cts.ConnectivityManagerTest#testGetActiveNetworkInfo;android.net.cts.LocalSocketAddressTest#testNewLocalSocketAddressWithDefaultNamespace;android.net.http.cts.SslErrorTest#testGetUrl;android.net.ipv6.cts.PingTest#testLoopbackPing;android.net.cts.TrafficStatsTest#testValidTotalStats;android.net.wifi.cts.WifiEnterpriseConfigTest#testAddEapNetwork;android.net.cts.VpnServiceTest#testProtect_Socket;android.net.wifi.cts.WifiEnterpriseConfigTest#testAndroidTestCaseSetupProperly;android.net.cts.ConnectivityManagerTest#testIsNetworkTypeValid;android.net.cts.ConnectivityManagerTest#testIsNetworkSupported;android.net.cts.LocalServerSocketTest#testAndroidTestCaseSetupProperly;android.net.wifi.cts.WifiEnterpriseConfigTest#testSettersAndGetters;android.net.http.cts.ApacheHttpClientTest#testExecute_withMobile" />
- <Entry uri="android.provider" exclude="android.provider.cts.ContactsContract_CommonDataKinds_EventTest#testGetTypeLabel;android.provider.cts.MediaStore_Audio_Genres_MembersTest#testStoreAudioGenresMembersInternal;android.provider.cts.ContactsContract_DataTest#testDataInsert_updatesContactLastUpdatedTimestamp;android.provider.cts.ContactsContract_CommonDataKinds_EmailTest#testGetTypeLabel;android.provider.cts.Settings_NameValueTableTest#testPutString;android.provider.cts.CalendarTest#testCalendarEntityQuery;android.provider.cts.ContactsContract_DataUsageTest#testSingleDataUsageFeedback_incrementsCorrectDataItems;android.provider.cts.BrowserTest#testSendString;android.provider.cts.MediaStore_Video_MediaTest#testStoreVideoMediaInternal;android.provider.cts.UserDictionary_WordsTest#testAddWord_deprecated;android.provider.cts.MediaStoreIntentsTest#testAndroidTestCaseSetupProperly;android.provider.cts.ContactsContract_DeletedContacts#testQuerySinceTimestamp;android.provider.cts.BrowserTest#testRequestAllIcons;android.provider.cts.ContactsContract_CommonDataKinds_EmailTest#testAndroidTestCaseSetupProperly;android.provider.cts.CalendarTest#testReminders;android.provider.cts.MediaStore_Audio_ArtistsTest#testStoreAudioArtistsExternal;android.provider.cts.ContactsContractIntentsTest#testViewContactDir;android.provider.cts.CalendarTest#testBulkUpdate;android.provider.cts.ContactsContract_PhotoTest#testAddPhoto;android.provider.cts.Settings_NameValueTableTest#testAndroidTestCaseSetupProperly;android.provider.cts.ContactsProvider2_AccountRemovalTest#testAccountRemovalWithMergedContact_hasDeleteLogsForContacts;android.provider.cts.ContactsContract_PhotoTest#testAddEmptyPhoto;android.provider.cts.Settings_SecureTest#testGetPutFloat;android.provider.cts.MediaStore_Images_MediaTest#testGetContentUri;android.provider.cts.ContactsContract_DeletedContacts#testDelete_isUnsupported;android.provider.cts.CalendarTest#testCalendarCreationAndDeletion;android.provider.cts.BrowserTest#testBookmarksTable;android.provider.cts.Settings_SecureTest#testGetUriFor;android.provider.cts.ContactsContract_StatusUpdatesTest#testGetPresenceIconresourceId;android.provider.cts.MediaStore_Audio_Playlists_MembersTest#testStoreAudioPlaylistsMembersInternal;android.provider.cts.ContactsContract_CommonDataKinds_EventTest#testAndroidTestCaseSetupProperly;android.provider.cts.Contacts_PhonesTest#testGetDisplayLabel;android.provider.cts.ContactsContract_DeletedContacts#testInsert_isUnsupported;android.provider.cts.MediaStore_Audio_AlbumsTest#testStoreAudioAlbumsExternal;android.provider.cts.ContactsContract_DeletedContacts#testAndroidTestCaseSetupProperly;android.provider.cts.BrowserTest#testGetAllVisitedUrls;android.provider.cts.Contacts_ContactMethodsTest#testEncodeAndDecodeProtocol;android.provider.cts.ContactsContract_DeletedContacts#testQueryAll;android.provider.cts.MediaStore_Audio_MediaTest#testStoreAudioMediaInternal;android.provider.cts.ContactsContract_RawContactsTest#testRawContactPsuedoDelete_hasDeleteLogForContact;android.provider.cts.MediaStore_FilesTest#testAndroidTestCaseSetupProperly;android.provider.cts.MediaStore_Images_MediaTest#testStoreImagesMediaInternal;android.provider.cts.MediaStore_Audio_AlbumsTest#testAlbumArt;android.provider.cts.ContactsContract_ContactsTest#testLookupUri;android.provider.cts.MediaStoreIntentsTest#testPickVideoDir;android.provider.cts.MediaStore_Images_MediaTest#testInsertImageWithBitmap;android.provider.cts.MediaStoreIntentsTest#testViewAudioFile;android.provider.cts.MediaStore_Audio_Genres_MembersTest#testStoreAudioGenresMembersExternal;android.provider.cts.ContactsContract_CommonDataKinds_StructuredPostalTest#testAndroidTestCaseSetupProperly;android.provider.cts.CalendarTest#testInstanceSearch;android.provider.cts.VoicemailContractTest#testStatusTablePermissions;android.provider.cts.ContactsContract_CommonDataKinds_OrganizationTest#testAndroidTestCaseSetupProperly;android.provider.cts.SettingsTest#testAndroidTestCaseSetupProperly;android.provider.cts.MediaStore_Audio_PlaylistsTest#testStoreAudioPlaylistsInternal;android.provider.cts.ContactsContract_DumpFileProviderTest#testQuery_worksWithValidFileName;android.provider.cts.Settings_SecureTest#testAndroidTestCaseSetupProperly;android.provider.cts.MediaStore_Video_MediaTest#testAndroidTestCaseSetupProperly;android.provider.cts.BrowserTest#testAccessHistory;android.provider.cts.ContactsContract_DataTest#testContactablesFilterByPhonePrefix_returnsCorrectDataRows;android.provider.cts.MediaStore_Images_ThumbnailsTest#testStoreImagesMediaInternal;android.provider.cts.ContactsTest#testCallsTable;android.provider.cts.CalendarTest#testEventCreationAndDeletion;android.provider.cts.VoicemailContractTest#testVoicemailTablePermissions;android.provider.cts.CalendarTest#testEventsUid2445;android.provider.cts.MediaStore_VideoTest#testQuery;android.provider.cts.CalendarTest#testDefaultProjections;android.provider.cts.MediaStoreIntentsTest#testViewVideoDir;android.provider.cts.MediaStore_FilesTest#testGetContentUri;android.provider.cts.ContactsContract_ContactsTest#testInsert_isUnsupported;android.provider.cts.ContactsContract_CommonDataKinds_PhoneTest#testGetTypeLabel;android.provider.cts.VoicemailContractTest#testVoicemailsTable;android.provider.cts.ContactsContract_CommonDataKinds_RelationTest#testGetTypeLabel;android.provider.cts.Settings_SystemTest#testGetDefaultValues;android.provider.cts.ContactsContract_RawContactsTest#testAndroidTestCaseSetupProperly;android.provider.cts.MediaStoreTest#testGetMediaScannerUri;android.provider.cts.ContactsContract_RawContactsTest#testRawContactDelete_removesRecord;android.provider.cts.Settings_SecureTest#testGetPutString;android.provider.cts.ContactsTest#testGroupMembershipTable;android.provider.cts.MediaStore_Audio_MediaTest#testGetContentUri;android.provider.cts.CalendarTest#testFullRecurrenceUpdate;android.provider.cts.MediaStore_FilesTest#testCaseSensitivity;android.provider.cts.MediaStore_Audio_AlbumsTest#testAndroidTestCaseSetupProperly;android.provider.cts.MediaStore_Audio_ArtistsTest#testStoreAudioArtistsInternal;android.provider.cts.ContactsContract_ContactsTest#testContactDelete_hasDeleteLog;android.provider.cts.ContactsContract_DataUsageTest#testMultiIdDataUsageFeedback_incrementsCorrectDataItems;android.provider.cts.CalendarTest#testSyncOnlyInsertEnforcement;android.provider.cts.VoicemailContractTest#testDataColumnUpdate_throwsIllegalArgumentException;android.provider.cts.CalendarTest#testColorWriteRequirements;android.provider.cts.CalendarTest#testWhenByDayQuery;android.provider.cts.ContactsContract_StreamItemsTest#testAndroidTestCaseSetupProperly;android.provider.cts.ContactsContract_CommonDataKinds_ImTest#testAndroidTestCaseSetupProperly;android.provider.cts.MediaStore_AudioTest#testKeyFor;android.provider.cts.ContactsContract_ContactsTest#testAndroidTestCaseSetupProperly;android.provider.cts.MediaStore_Images_ThumbnailsTest#testQueryInternalThumbnails;android.provider.cts.ContactsContract_DumpFileProviderTest#testOpenFileDescriptor_throwsErrorWithIllegalFileName;android.provider.cts.CalendarTest#testEventColors;android.provider.cts.SettingsTest#testBluetoothDevicesTable;android.provider.cts.ContactsContract_RawContactsTest#testRawContactUpdate_updatesContactUpdatedTimestamp;android.provider.cts.ContactsContract_CommonDataKinds_RelationTest#testAndroidTestCaseSetupProperly;android.provider.cts.MediaStoreTest#testGetVersion;android.provider.cts.MediaStore_Audio_GenresTest#testGetContentUri;android.provider.cts.ContactsContract_DataTest#testDataDelete_updatesContactLastUpdatedTimestamp;android.provider.cts.ContactsContract_CommonDataKinds_SipAddressTest#testGetTypeLabel;android.provider.cts.BrowserTest#testSaveBookmark;android.provider.cts.ContactsProvider2_AccountRemovalTest#testAndroidTestCaseSetupProperly;android.provider.cts.MediaStore_Video_MediaTest#testGetContentUri;android.provider.cts.CalendarTest#testExtendedProperties;android.provider.cts.Settings_SystemTest#testAndroidTestCaseSetupProperly;android.provider.cts.CalendarTest#testNonAdapterRecurrenceExceptions;android.provider.cts.CalendarTest#testOutOfOrderRecurrenceExceptions;android.provider.cts.CalendarTest#testConversionToRecurring;android.provider.cts.MediaStore_Audio_Playlists_MembersTest#testStoreAudioPlaylistsMembersExternal;android.provider.cts.CalendarTest#testMultiRuleRecurrence;android.provider.cts.MediaStoreIntentsTest#testPickAudioDir;android.provider.cts.MediaStore_Audio_GenresTest#testStoreAudioGenresExternal;android.provider.cts.MediaStoreIntentsTest#testViewVideoFile;android.provider.cts.ContactsProvider2_AccountRemovalTest#testAccountRemoval_deletesContacts;android.provider.cts.Contacts_ContactMethodsTest#testAndroidTestCaseSetupProperly;android.provider.cts.ContactsContract_GroupMembershipTest#testAddGroupMembershipWithGroupRowId;android.provider.cts.TelephonyProviderTest#testOpeningAnyFile;android.provider.cts.MediaStore_Audio_GenresTest#testGetContentUriForAudioId;android.provider.cts.ContactsContract_CommonDataKinds_PhoneTest#testAndroidTestCaseSetupProperly;android.provider.cts.Contacts_PeopleTest#testMarkAsContacted;android.provider.cts.MediaStore_FilesTest#testAccess;android.provider.cts.ContactsContract_CommonDataKinds_ImTest#testGetTypeLabel;android.provider.cts.SearchRecentSuggestionsTest#testSuggestionsTable;android.provider.cts.CalendarTest#testAttendees;android.provider.cts.SettingsTest#testAccessNonTable;android.provider.cts.MediaStoreIntentsTest#testPickImageDir;android.provider.cts.BrowserTest#testSearchesTable;android.provider.cts.Contacts_SettingsTest#testAccessSetting;android.provider.cts.ContactsContract_StreamItemPhotosTest#testContentPhotoUri;android.provider.cts.ContactsContract_DataTest#testGetLookupUriByDisplayName;android.provider.cts.ContactsContract_StatusUpdatesTest#testInsertStatus;android.provider.cts.MediaStore_Video_ThumbnailsTest#testGetContentUri;android.provider.cts.MediaStore_Audio_GenresTest#testStoreAudioGenresInternal;android.provider.cts.MediaStore_Images_MediaTest#testInsertImageWithImagePath;android.provider.cts.CalendarTest#testForwardRecurrenceExceptions;android.provider.cts.Settings_SecureTest#testUnknownSourcesOffByDefault;android.provider.cts.CalendarTest#testBadRequests;android.provider.cts.ContactsTest#testSettingsTable;android.provider.cts.VoicemailContractTest#testInsert_doesNotUpdateDataColumn;android.provider.cts.ContactsContract_RawContactsTest#testRawContactCreate_updatesContactUpdatedTimestamp;android.provider.cts.MediaStoreIntentsTest#testViewImageDir;android.provider.cts.ContactsContract_ContactsTest#testContactDelete_removesContactRecord;android.provider.cts.MediaStore_Images_ThumbnailsTest#testQueryExternalMiniThumbnails;android.provider.cts.ContactsContract_ContactsTest#testContactUpdate_updatesContactUpdatedTimestamp;android.provider.cts.Settings_SettingNotFoundExceptionTest#testAndroidTestCaseSetupProperly;android.provider.cts.ContactsContract_RawContactsTest#testGetLookupUriByDisplayName;android.provider.cts.BrowserTest#testAccessSearches;android.provider.cts.ContactsContract_CommonDataKinds_SipAddressTest#testAndroidTestCaseSetupProperly;android.provider.cts.ContactsContract_GroupMembershipTest#testAddGroupMembershipWithUnknownGroupSourceId;android.provider.cts.MediaStore_Audio_MediaTest#testStoreAudioMediaExternal;android.provider.cts.MediaStore_Audio_PlaylistsTest#testGetContentUri;android.provider.cts.ContactsTest#testGroupsTable;android.provider.cts.MediaStore_Audio_AlbumsTest#testGetContentUri;android.provider.cts.Settings_SystemTest#testGetUriFor;android.provider.cts.ContactsContract_StreamItemPhotosTest#testContentDirectoryUri;android.provider.cts.SearchRecentSuggestionsTest#testSearchRecentSuggestions;android.provider.cts.Contacts_ContactMethodsTest#testAddPostalLocation;android.provider.cts.MediaStore_Audio_Artists_AlbumsTest#testGetContentUri;android.provider.cts.SearchRecentSuggestionsTest#testConstructor;android.provider.cts.ContactsContract_ContactsTest#testMarkAsContacted;android.provider.cts.ContactsTest#testPeopleTable;android.provider.cts.CalendarTest#testCalendarColors;android.provider.cts.CalendarTest#testCalendarIsPrimary;android.provider.cts.ContactsContract_RawContactsTest#testRawContactDelete_hasDeleteLogForContact;android.provider.cts.Settings_SystemTest#testSystemSettings;android.provider.cts.CalendarTest#testRecurrence;android.provider.cts.ContactsContract_GroupMembershipTest#testAddGroupMembershipWithGroupSourceId;android.provider.cts.MediaStore_Audio_Genres_MembersTest#testGetContentUri;android.provider.cts.VoicemailContractTest#testStatusTable;android.provider.cts.ContactsContract_DataTest#testContactablesFilterByFirstName_returnsCorrectDataRows;android.provider.cts.Settings_NameValueTableTest#testGetUriFor;android.provider.cts.ContactsContract_DumpFileProviderTest#testQuery_throwsErrorWithIllegalFileName;android.provider.cts.ContactsTest#testContactMethodsTable;android.provider.cts.ContactsContractIntentsTest#testPickContactDir;android.provider.cts.MediaStore_Audio_AlbumsTest#testStoreAudioAlbumsInternal;android.provider.cts.Contacts_PeopleTest#testAddToGroup;android.provider.cts.ContactsContract_DataTest#testContactablesFilterByEmailPrefix_returnsCorrectDataRows;android.provider.cts.ContactsContract_StatusUpdatesTest#testGetPresencePrecedence;android.provider.cts.BrowserTest#testUpdateVisitedHistory;android.provider.cts.SettingsTest#testSecureTable;android.provider.cts.ContactsContract_StreamItemsTest#testContentUri;android.provider.cts.MediaStore_Audio_Artists_AlbumsTest#testStoreAudioArtistsAlbumsInternal;android.provider.cts.MediaStore_Video_ThumbnailsTest#testGetThumbnail;android.provider.cts.ContactsContractIntentsTest#testGetContentContactDir;android.provider.cts.SettingsTest#testSystemTable;android.provider.cts.CalendarTest#testEventUpdateAsApp;android.provider.cts.MediaStore_Images_MediaTest#testStoreImagesMediaExternal;android.provider.cts.CalendarTest#testEventsEntityQuery;android.provider.cts.ContactsContractIntentsTest#testAndroidTestCaseSetupProperly;android.provider.cts.CalendarTest#testSyncState;android.provider.cts.MediaStore_Video_ThumbnailsTest#testAndroidTestCaseSetupProperly;android.provider.cts.ContactsContract_DataTest#testGetLookupUriBySourceId;android.provider.cts.UserDictionary_WordsTest#testAndroidTestCaseSetupProperly;android.provider.cts.ContactsTest#testExtensionsTable;android.provider.cts.ContactsContract_DataTest#testContactablesUri;android.provider.cts.ContactsContract_CommonDataKinds_ImTest#testGetProtocolLabel;android.provider.cts.Settings_SettingNotFoundExceptionTest#testConstructor;android.provider.cts.CalendarTest#testEventsIsOrganizer;android.provider.cts.ContactsContract_StreamItemsTest#testContentDirectoryUri;android.provider.cts.Contacts_PeopleTest#testAccessPhotoData;android.provider.cts.UserDictionary_WordsTest#testAddWord;android.provider.cts.CalendarTest#testSingleRecurrenceExceptions;android.provider.cts.BrowserTest#testGetAllBookmarks;android.provider.cts.ContactsContract_RawContactsTest#testRawContactDelete_setsDeleteFlag;android.provider.cts.MediaStoreIntentsTest#testViewImageFile;android.provider.cts.Settings_SecureTest#testGetPutLong;android.provider.cts.ContactsContract_DataTest#testContactablesFilterByLastName_returnsCorrectDataRows;android.provider.cts.SearchRecentSuggestionsTest#testAndroidTestCaseSetupProperly;android.provider.cts.Contacts_PhonesTest#testGetDisplayLabelCharSequenceArray;android.provider.cts.SettingsTest#testUserDictionarySettingsExists;android.provider.cts.Contacts_OrganizationsTest#testGetDisplayLabel;android.provider.cts.Settings_SecureTest#testGetDefaultValues;android.provider.cts.ContactsContract_ContactsTest#testContactDelete_marksRawContactsForDeletion;android.provider.cts.ContactsTest#testPhotosTable;android.provider.cts.ContactsContract_ContactsTest#testContentUri;android.provider.cts.MediaStore_Audio_Artists_AlbumsTest#testStoreAudioArtistsAlbumsExternal;android.provider.cts.Contacts_PhonesTest#testAndroidTestCaseSetupProperly;android.provider.cts.ContactsProvider2_AccountRemovalTest#testAccountRemovalWithMergedContact_doesNotDeleteContactAndTimestampUpdated;android.provider.cts.ContactsTest#testOrganizationsTable;android.provider.cts.MediaStore_Audio_Playlists_MembersTest#testGetContentUri;android.provider.cts.MediaStore_Audio_PlaylistsTest#testStoreAudioPlaylistsExternal;android.provider.cts.Contacts_OrganizationsTest#testAndroidTestCaseSetupProperly;android.provider.cts.ContactsTest#testPhonesTable;android.provider.cts.Settings_SecureTest#testGetPutInt;android.provider.cts.ContactsContract_StatusUpdatesTest#testAndroidTestCaseSetupProperly;android.provider.cts.MediaStore_Audio_MediaTest#testGetContentUriForPath;android.provider.cts.CalendarTest#testCalendarUpdateAsApp;android.provider.cts.ContactsContract_DeletedContacts#testQueryByContactId;android.provider.cts.ContactsProvider2_AccountRemovalTest#testAccountRemoval_hasDeleteLogsForContacts;android.provider.cts.ContactsContract_StreamItemPhotosTest#testAndroidTestCaseSetupProperly;android.provider.cts.MediaStore_Audio_ArtistsTest#testGetContentUri;android.provider.cts.ContactsContract_DumpFileProviderTest#testAndroidTestCaseSetupProperly;android.provider.cts.ContactsContract_DataTest#testDataUpdate_updatesContactLastUpdatedTimestamp;android.provider.cts.ContactsProvider2_AccountRemovalTest#testAccountRemovalWithMergedContact_deletesContacts;android.provider.cts.ContactsContract_RawContactsTest#testGetLookupUriBySourceId;android.provider.cts.MediaStore_Images_ThumbnailsTest#testGetContentUri;android.provider.cts.ContactsContract_DumpFileProviderTest#testOpenFileDescriptor_worksWithValidFileName;android.provider.cts.ContactsContract_CommonDataKinds_StructuredPostalTest#testGetTypeLabel;android.provider.cts.ContactsContract_DataUsageTest#testAndroidTestCaseSetupProperly;android.provider.cts.Contacts_ContactMethodsTest#test;android.provider.cts.MediaStore_Video_MediaTest#testStoreVideoMediaExternal;android.provider.cts.ContactsContract_CommonDataKinds_OrganizationTest#testGetTypeLabel;android.provider.cts.MediaStore_Images_ThumbnailsTest#testStoreImagesMediaExternal;android.provider.cts.ContactsContract_DataTest#testContactablesFilter_doesNotExist_returnsCorrectDataRows;android.provider.cts.ContactsContract_DeletedContacts#testQuery_returnsProperColumns" />
- <Entry uri="android.security" exclude="android.security.cts.KeystoreExploitTest#testAndroidTestCaseSetupProperly;android.security.cts.CharDeviceTest#testExynosKernelMemoryRead;android.security.cts.VoldExploitTest#testAndroidTestCaseSetupProperly;android.security.cts.LoadEffectLibraryTest#testLoadLibrary;android.security.cts.BrowserTest#testAndroidTestCaseSetupProperly;android.security.cts.LinuxRngTest#testDevUrandomMajorMinor;android.security.cts.ServicePermissionsTest#testDumpProtected;android.security.cts.BannedFilesTest#testNoSetuidTcpdump;android.security.cts.SqliteJournalLeakTest#testShm;android.security.cts.LinuxRngTest#testDevRandomMajorMinor;android.security.cts.ClonedSecureRandomTest#testCheckForDuplicateOutput;android.security.cts.BrowserTest#testBrowserPrivateDataAccess;android.security.cts.BrowserTest#testTabExhaustion;android.security.cts.KeystoreExploitTest#testKeystoreCrash;android.security.cts.SqliteJournalLeakTest#testJournal;android.security.cts.BannedFilesTest#testNoCmdClient;android.security.cts.BannedFilesTest#testNoSetuidIp;android.security.cts.BannedFilesTest#testNoSyncAgent;android.security.cts.BannedFilesTest#testNoRootCmdSocket;android.security.cts.ListeningPortsTest#testNoListeningLoopbackTcp6Ports;android.security.cts.KernelSettingsTest#testKptrRestrict;android.security.cts.AslrTest#testVaRandomize;android.security.cts.KernelSettingsTest#testMmapMinAddr;android.security.cts.CertificateTest#testBlockCertificates;android.security.cts.BrowserTest#testTabReuse;android.security.cts.KernelSettingsTest#testSELinuxEnforcing;android.security.cts.ListeningPortsTest#testNoListeningLoopbackUdp6Ports;android.security.cts.NativeCodeTest#testPerfEvent;android.security.cts.KernelSettingsTest#testSetuidDumpable;android.security.cts.PackageSignatureTest#testAndroidTestCaseSetupProperly;android.security.cts.ServicePermissionsTest#testAndroidTestCaseSetupProperly;android.security.cts.CertificateTest#testCertificates;android.security.cts.KernelSettingsTest#testNoConfigGz;android.security.cts.KernelSettingsTest#testDmesgRestrict;android.security.cts.ListeningPortsTest#testAndroidTestCaseSetupProperly;android.security.cts.SqliteJournalLeakTest#testAndroidTestCaseSetupProperly;android.security.cts.ListeningPortsTest#testNoRemotelyAccessibleListeningTcp6Ports;android.security.cts.ListeningPortsTest#testNoListeningLoopbackTcpPorts;android.security.cts.ClonedSecureRandomTest#testAndroidTestCaseSetupProperly;android.security.cts.VoldExploitTest#testTryCommandInjection;android.security.cts.VoldExploitTest#testZergRushCrash;android.security.cts.AslrTest#testOneExecutableIsPie;android.security.cts.VoldExploitTest#testTryToCrashVold;android.security.cts.ListeningPortsTest#testNoListeningLoopbackUdpPorts;android.security.cts.CertificateTest#testAndroidTestCaseSetupProperly;android.security.cts.SqliteJournalLeakTest#testWal;android.security.cts.ListeningPortsTest#testNoRemotelyAccessibleListeningTcpPorts;android.security.cts.CharDeviceTest#testExynosRootingVuln" />
- <Entry uri="android.webkit" exclude="android.webkit.cts.WebViewTest#testDocumentHasImages;android.webkit.cts.WebViewTest#testScrollBarOverlay;android.webkit.cts.WebViewTest#testGoBackAndForward;android.webkit.cts.WebViewTest#testGetContentHeight;android.webkit.cts.WebChromeClientTest#testOnJsConfirm;android.webkit.cts.WebChromeClientTest#testOnProgressChanged;android.webkit.cts.WebViewTest#testAddJavascriptInterfaceNullObject;android.webkit.cts.WebViewTest#testConstructor;android.webkit.cts.WebViewTest#testInvokeZoomPicker;android.webkit.cts.WebSettingsTest#testAccessMinimumLogicalFontSize;android.webkit.cts.WebViewStartupTest#testCookieManagerBlockingUiThread;android.webkit.cts.WebViewTest#testInternals;android.webkit.cts.WebViewClientTest#testShouldOverrideUrlLoading;android.webkit.cts.GeolocationTest#testSimpleGeolocationRequestAcceptOnce;android.webkit.cts.WebSettingsTest#testDatabaseDisabled;android.webkit.cts.WebView_WebViewTransportTest#testAccessWebView;android.webkit.cts.WebSettingsTest#testAccessJavaScriptCanOpenWindowsAutomatically;android.webkit.cts.WebSettingsTest#testAppCacheEnabled;android.webkit.cts.WebSettingsTest#testAccessUserAgentString;android.webkit.cts.WebViewTest#testClearHistory;android.webkit.cts.WebSettingsTest#testAccessSerifFontFamily;android.webkit.cts.WebSettingsTest#testAccessLayoutAlgorithm;android.webkit.cts.WebSettingsTest#testAccessFantasyFontFamily;android.webkit.cts.WebViewTest#testAddJavascriptInterface;android.webkit.cts.WebSettingsTest#testAccessCacheMode;android.webkit.cts.WebViewTest#testDebugDump;android.webkit.cts.WebViewTest#testSslErrorProceedResponseNotReusedForDifferentHost;android.webkit.cts.WebSettingsTest#testLocalImageLoads;android.webkit.cts.WebViewTest#testSslErrorProceedResponseReusedForSameHost;android.webkit.cts.HttpAuthHandlerTest#testUseHttpAuthUsernamePassword;android.webkit.cts.WebViewTest#testSetLayoutParams;android.webkit.cts.WebViewTest#testAppInjectedXRequestedWithHeaderIsNotOverwritten;android.webkit.cts.WebSettingsTest#testAccessUseDoubleTree;android.webkit.cts.WebViewTest#testOnReceivedSslErrorCancel;android.webkit.cts.URLUtilTest#testIsHttpUrl;android.webkit.cts.DateSorterTest#testConstants;android.webkit.cts.WebSettingsTest#testAccessFixedFontFamily;android.webkit.cts.WebSettingsTest#testSetRenderPriority;android.webkit.cts.WebViewTest#testRemoveJavascriptInterface;android.webkit.cts.WebViewTest#testAndroidAssetAnchor;android.webkit.cts.WebViewTest#testOnReceivedSslError;android.webkit.cts.CookieTest#testEmptyValue;android.webkit.cts.WebViewTest#testPauseResumeTimers;android.webkit.cts.URLUtilTest#testIsContentUrl;android.webkit.cts.WebChromeClientTest#testBlockWindowsAsync;android.webkit.cts.WebViewTest#testGetVisibleTitleHeight;android.webkit.cts.WebBackForwardListTest#testClone;android.webkit.cts.WebSettingsTest#testAccessDefaultTextEncodingName;android.webkit.cts.URLUtilTest#testGuessUrl;android.webkit.cts.MimeTypeMapTest#testAndroidTestCaseSetupProperly;android.webkit.cts.WebChromeClientTest#testOnReceivedIcon;android.webkit.cts.CookieTest#testAndroidTestCaseSetupProperly;android.webkit.cts.CookieManagerTest#testRemoveCookies;android.webkit.cts.WebSettingsTest#testAccessPluginsPath;android.webkit.cts.WebSettingsTest#testAccessAllowFileAccess;android.webkit.cts.WebSettingsTest#testAccessSupportMultipleWindows;android.webkit.cts.WebViewTest#testAppCanInjectHeadersViaImmutableMap;android.webkit.cts.WebViewTest#testSecureSiteSetsCertificate;android.webkit.cts.WebViewTest#testSetWebViewClient;android.webkit.cts.WebViewTest#testSetScrollBarStyle;android.webkit.cts.CookieTest#testDomain;android.webkit.cts.WebViewTest#testZoom;android.webkit.cts.URLUtilTest#testIsDataUrl;android.webkit.cts.CookieManagerTest#testAcceptCookie;android.webkit.cts.WebChromeClientTest#testOnReceivedTitle;android.webkit.cts.URLUtilTest#testIsFileUrl;android.webkit.cts.WebSettingsTest#testAccessJavaScriptEnabled;android.webkit.cts.URLUtilTest#testIsNetworkUrl;android.webkit.cts.WebViewTest#testFindAddress;android.webkit.cts.WebViewTest#testSetNetworkAvailable;android.webkit.cts.WebViewTest#testClearSslPreferences;android.webkit.cts.URLUtilTest#testIsHttpsUrl;android.webkit.cts.MimeTypeMapTest#testGetFileExtensionFromUrl;android.webkit.cts.WebViewTest#testGetOriginalUrl;android.webkit.cts.WebChromeClientTest#testBlockWindowsSync;android.webkit.cts.WebViewTest#testLoadData;android.webkit.cts.WebViewTest#testInsecureSiteClearsCertificate;android.webkit.cts.WebBackForwardListTest#testGetCurrentItem;android.webkit.cts.URLUtilTest#testStripAnchor;android.webkit.cts.URLUtilTest#testGuessFileName;android.webkit.cts.URLUtilTest#testAndroidTestCaseSetupProperly;android.webkit.cts.WebViewTest#testEvaluateJavascript;android.webkit.cts.DateSorterTest#testConstructor;android.webkit.cts.WebViewTest#testPageScroll;android.webkit.cts.WebSettingsTest#testIframesWhenAccessFromFileURLsEnabled;android.webkit.cts.WebViewTest#testFlingScroll;android.webkit.cts.WebSettingsTest#testXHRWhenAccessFromFileURLsEnabled;android.webkit.cts.WebChromeClientTest#testOnJsPrompt;android.webkit.cts.WebSettingsTest#testAccessSupportZoom;android.webkit.cts.WebSettingsTest#testLoadsImagesAutomatically;android.webkit.cts.URLUtilTest#testIsValidUrl;android.webkit.cts.WebViewTest#testRequestFocusNodeHref;android.webkit.cts.WebViewTest#testLoadDataWithBaseUrl;android.webkit.cts.WebChromeClientTest#testOnJsAlert;android.webkit.cts.WebSettingsTest#testAccessSansSerifFontFamily;android.webkit.cts.CookieManagerTest#testCookieManager;android.webkit.cts.WebViewTest#testSetMapTrackballToArrowKeys;android.webkit.cts.WebViewTest#testCreatingWebViewCreatesCookieSyncManager;android.webkit.cts.DateSorterTest#testGetIndex;android.webkit.cts.GeolocationTest#testGeolocationPermissions;android.webkit.cts.WebChromeClientTest#testOnJsBeforeUnload;android.webkit.cts.CookieManagerTest#testClone;android.webkit.cts.CookieManagerTest#testGetInstance;android.webkit.cts.WebViewTest#testGetZoomControls;android.webkit.cts.CookieTest#testSubDomain;android.webkit.cts.WebSettingsTest#testUserAgentString_default;android.webkit.cts.MimeTypeMapTest#testGetMimeTypeFromExtension;android.webkit.cts.WebSettingsTest#testBlockNetworkImage;android.webkit.cts.WebViewTest#testPlatformNotifications;android.webkit.cts.URLUtilTest#testIsAboutUrl;android.webkit.cts.WebViewTest#testSetPictureListener;android.webkit.cts.MimeTypeMapTest#testHasMimeType;android.webkit.cts.WebViewTest#testOnReceivedSslErrorProceed;android.webkit.cts.DateSorterTest#testGetLabel;android.webkit.cts.GeolocationTest#testSimpleGeolocationRequestAcceptAlways;android.webkit.cts.URLUtilTest#testDecode;android.webkit.cts.HttpAuthHandlerTest#testProceed;android.webkit.cts.WebSettingsTest#testSetNeedInitialFocus;android.webkit.cts.WebSettingsTest#testIframesWhenAccessFromFileURLsDisabled;android.webkit.cts.WebSettingsTest#testAccessCursiveFontFamily;android.webkit.cts.WebViewTest#testFindAll;android.webkit.cts.WebViewTest#testStopLoading;android.webkit.cts.DateSorterTest#testAndroidTestCaseSetupProperly;android.webkit.cts.WebSettingsTest#testAccessDefaultFixedFontSize;android.webkit.cts.CookieManagerTest#testb3167208;android.webkit.cts.WebSettingsTest#testAccessMinimumFontSize;android.webkit.cts.WebSettingsTest#testAccessUseWideViewPort;android.webkit.cts.WebSettingsTest#testAccessSaveFormData;android.webkit.cts.WebViewTest#testRequestChildRectangleOnScreen;android.webkit.cts.URLUtilTest#testIsJavaScriptUrl;android.webkit.cts.WebViewTest#testFindNext;android.webkit.cts.MimeTypeMapTest#testHasExtension;android.webkit.cts.WebViewTest#testSetDownloadListener;android.webkit.cts.WebSettingsTest#testXHRWhenAccessFromFileURLsDisabled;android.webkit.cts.WebViewTest#testDestroy;android.webkit.cts.MimeTypeMapTest#testGetSingleton;android.webkit.cts.WebViewTest#testAndroidAssetQueryParam;android.webkit.cts.WebViewTest#testAccessPluginList;android.webkit.cts.CookieTest#testPath;android.webkit.cts.WebViewTest#testAccessHttpAuthUsernamePassword;android.webkit.cts.WebViewTest#testUseRemovedJavascriptInterface;android.webkit.cts.WebSettingsTest#testAccessTextSize;android.webkit.cts.URLUtilTest#testIsAssetUrl;android.webkit.cts.CookieTest#testInvalidDomain;android.webkit.cts.CookieSyncManagerTest#testCookieSyncManager;android.webkit.cts.URLUtilTest#testComposeSearchUrl;android.webkit.cts.WebChromeClientTest#testWindows;android.webkit.cts.WebViewTest#testRequestImageRef;android.webkit.cts.WebSettingsTest#testAccessDefaultFontSize;android.webkit.cts.WebViewClientTest#testShouldOverrideKeyEvent;android.webkit.cts.WebHistoryItemTest#testWebHistoryItem;android.webkit.cts.WebSettingsTest#testAccessBuiltInZoomControls;android.webkit.cts.WebSettingsTest#testAppCacheDisabled;android.webkit.cts.WebViewTest#testSetWebChromeClient;android.webkit.cts.WebViewTest#testGetHitTestResult;android.webkit.cts.WebSettingsTest#testAccessStandardFontFamily;android.webkit.cts.GeolocationTest#testSimpleGeolocationRequestReject;android.webkit.cts.WebSettingsTest#testBlockNetworkLoads;android.webkit.cts.DateSorterTest#testGetBoundary;android.webkit.cts.WebViewTest#testCapturePicture;android.webkit.cts.WebSettingsTest#testAccessPluginsEnabled;android.webkit.cts.WebViewTest#testSaveAndRestoreState;android.webkit.cts.WebViewTest#testLoadUrl;android.webkit.cts.HttpAuthHandlerTest#testCancel;android.webkit.cts.URLUtilTest#testIsCookielessProxyUrl;android.webkit.cts.WebViewTest#testGetFavicon;android.webkit.cts.MimeTypeMapTest#testGetExtensionFromMimeType" />
- <Entry uri="android.widget" exclude="android.widget.cts.ImageSwitcherTest#testSetImageDrawable;android.widget.cts.ExpandableListViewTest#testOnSaveInstanceState;android.widget.cts.RemoteViewsTest#testSetFloat;android.widget.cts.ToggleButtonTest#testConstructor;android.widget.cts.CursorTreeAdapterTest#testOnGroupCollapsed;android.widget.cts.ListViewTest#testDispatchDraw;android.widget.cts.HorizontalScrollViewTest#testRequestLayout;android.widget.cts.TextViewTest#testDrawableResolution;android.widget.cts.CursorTreeAdapterTest#testGetGroup;android.widget.cts.RemoteViewsTest#testSetBoolean;android.widget.cts.SimpleExpandableListAdapterTest#testGetChildrenCount;android.widget.cts.TimePickerTest#testSetEnabled;android.widget.cts.RemoteViewsTest#testSetChronometer;android.widget.cts.BaseExpandableListAdapterTest#testNotifyDataSetChanged;android.widget.cts.TextViewTest#testConstructor;android.widget.cts.FrameLayoutTest#testGenerateLayoutParams2;android.widget.cts.FrameLayoutTest#testGenerateLayoutParams1;android.widget.cts.DialerFilterTest#testClearText;android.widget.cts.ListViewTest#testFindViewWithTagTraversal;android.widget.cts.TextViewTest#testGetResolvedTextAlignmentWithInheritance;android.widget.cts.SimpleAdapterTest#testSetDropDownViewResource;android.widget.cts.GalleryTest#testShowContextMenu;android.widget.cts.ExpandableListViewTest#testSetChildIndicator;android.widget.cts.GalleryTest#testConstructor;android.widget.cts.CursorTreeAdapterTest#testGetGroupCount;android.widget.cts.TextViewTest#testDebug;android.widget.cts.ExpandableListViewTest#testSetIndicatorBounds;android.widget.cts.AutoCompleteTextViewTest#testOnKeyPreIme;android.widget.cts.TextViewTest#testOnTrackballEvent;android.widget.cts.ExpandableListViewTest#testAndroidTestCaseSetupProperly;android.widget.cts.PopupWindowTest#testAccessBackground;android.widget.cts.PopupWindowTest#testGetMaxAvailableHeight;android.widget.cts.TextViewTest#testGetTotalPaddingLeft;android.widget.cts.AutoCompleteTextViewTest#testAccessAdapter;android.widget.cts.AutoCompleteTextViewTest#testPerformCompletion;android.widget.cts.ZoomButtonTest#testDispatchUnhandledMove;android.widget.cts.AbsSpinnerTest#testOnMeasure;android.widget.cts.TextViewTest#testOnWindowFocusChanged;android.widget.cts.PopupWindowTest#testUpdateDimensionAndAlignAnchorViewWithOffsets;android.widget.cts.GridLayoutTest#testCheckLayoutParams;android.widget.cts.TabHostTest#testNewTabSpec;android.widget.cts.ViewAnimatorTest#testGetCurrentView;android.widget.cts.ListViewTest#testDispatchKeyEvent;android.widget.cts.TextViewTest#testSetMinEms;android.widget.cts.FrameLayoutTest#testGatherTransparentRegion;android.widget.cts.AbsListViewTest#testDraw;android.widget.cts.ZoomControlsTest#testHasFocus;android.widget.cts.RemoteViews_ActionExceptionTest#testConstructor;android.widget.cts.ViewFlipperTest#testActivityTestCaseSetUpProperly;android.widget.cts.RemoteViewsTest#testSetString;android.widget.cts.SimpleExpandableListAdapterTest#testConstructor;android.widget.cts.GalleryTest#testComputeHorizontalScrollExtent;android.widget.cts.DialerFilterTest#testGetFilterText;android.widget.cts.RadioGroupTest#testGenerateDefaultLayoutParams;android.widget.cts.DialerFilterTest#testSetFilterWatcher;android.widget.cts.CheckBoxTest#testConstructor;android.widget.cts.ProgressBarTest#testDrawableStateChanged;android.widget.cts.MultiAutoCompleteTextViewTest#testPerformFiltering;android.widget.cts.TextViewTest#testOnKeyMultiple;android.widget.cts.ProgressBarTest#testPostInvalidate;android.widget.cts.SlidingDrawerTest#testOpenAndClose;android.widget.cts.AutoCompleteTextViewTest#testConstructor;android.widget.cts.TextViewTest#testSetLineSpacing;android.widget.cts.ListViewTest#testFindViewTraversal;android.widget.cts.RadioGroupTest#testGetCheckedRadioButtonId;android.widget.cts.TabHostTest#testSetup2;android.widget.cts.TableLayout_LayoutParamsTest#testConstructor;android.widget.cts.HorizontalScrollViewTest#testRequestChildFocus;android.widget.cts.TabWidgetTest#testDispatchDraw;android.widget.cts.PopupWindowTest#testUpdate;android.widget.cts.BaseAdapterTest#testNotifyDataSetInvalidated;android.widget.cts.ProgressBarTest#testOnSaveAndRestoreInstanceState;android.widget.cts.TabHostTest#testSetup1;android.widget.cts.TextViewTest#testOnMeasure;android.widget.cts.CompoundButtonTest#testAccessInstanceState;android.widget.cts.TabWidgetTest#testOnFocusChange;android.widget.cts.DialerFilterTest#testOnFinishInflate;android.widget.cts.ImageViewTest#testSetSelected;android.widget.cts.TextViewTest#testDrawableResolution2;android.widget.cts.ExpandableListViewWithHeadersTest#testPreconditions;android.widget.cts.AbsListViewTest#testSetFilterText;android.widget.cts.ExpandableListViewTest#testGetAdapter;android.widget.cts.TextViewTest#testSingleLine;android.widget.cts.HorizontalScrollViewTest#testComputeScroll;android.widget.cts.CursorAdapterTest#testRunQueryOnBackgroundThread;android.widget.cts.ToastTest#testMakeText2;android.widget.cts.CursorAdapterTest#testGetView;android.widget.cts.ViewSwitcherTest#testSetFactory;android.widget.cts.ToastTest#testMakeText1;android.widget.cts.GridViewTest#testAccessStretchMode;android.widget.cts.AutoCompleteTextViewTest#testGetThreshold;android.widget.cts.RemoteViewsTest#testConstructor;android.widget.cts.AbsListViewTest#testCheckLayoutParams;android.widget.cts.ViewAnimatorTest#testSetAnimateFirstView;android.widget.cts.DigitalClockTest#testActivityTestCaseSetUpProperly;android.widget.cts.SlidingDrawerTest#testAnimateOpenAndClose;android.widget.cts.PopupWindowTest#testAccessFocusable;android.widget.cts.TimePickerTest#testSetOnTimeChangedListener;android.widget.cts.ScrollViewTest#testGetMaxScrollAmount;android.widget.cts.CursorAdapterTest#testOnContentChanged;android.widget.cts.TextViewTest#testGetTotalPaddingBottom;android.widget.cts.AnalogClockTest#testOnMeasure;android.widget.cts.RadioGroupTest#testCheck;android.widget.cts.CursorTreeAdapterTest#testNotifyDataSetChanged;android.widget.cts.TwoLineListItemTest#testActivityTestCaseSetUpProperly;android.widget.cts.AbsListViewTest#testGetContextMenuInfo;android.widget.cts.ViewAnimatorTest#testAccessOutAnimation;android.widget.cts.SlidingDrawerTest#testConstructor;android.widget.cts.TimePickerTest#testOnSaveInstanceStateAndOnRestoreInstanceState;android.widget.cts.AbsListViewTest#testAccessFastScrollEnabled;android.widget.cts.BaseAdapterTest#testGetItemViewType;android.widget.cts.AbsListViewTest#testSetOnScrollListener;android.widget.cts.ImageViewTest#testSetImageURI;android.widget.cts.RadioGroupTest#testOnFinishInflate;android.widget.cts.TableRowTest#testGenerateLayoutParams;android.widget.cts.DialerFilterTest#testGetLetters;android.widget.cts.HorizontalScrollViewTest#testComputeHorizontalScrollRange;android.widget.cts.TextViewTest#testSetPadding;android.widget.cts.VideoViewTest#testPlayVideo1;android.widget.cts.ArrayAdapterTest#testRemove;android.widget.cts.GridViewTest#testGetNumColumns;android.widget.cts.AbsSpinnerTest#testSetSelectionIntBoolean;android.widget.cts.LayoutDirectionTest#testDirectionForAllLayoutsWithCode;android.widget.cts.SimpleCursorAdapterTest#testAccessStringConversionColumn;android.widget.cts.ViewAnimatorTest#testGetBaseline;android.widget.cts.ChronometerTest#testConstructor;android.widget.cts.ResourceCursorTreeAdapterTest#testConstructor;android.widget.cts.AdapterViewTest#testGetPositionForView;android.widget.cts.GridViewTest#testSetVerticalSpacing;android.widget.cts.ButtonTest#testAndroidTestCaseSetupProperly;android.widget.cts.ToggleButtonTest#testToggleText;android.widget.cts.RatingBarTest#testSetMax;android.widget.cts.RemoteViewsTest#testSetViewVisibility;android.widget.cts.ScrollViewTest#testOnTouchEvent;android.widget.cts.BaseAdapterTest#testIsEnabled;android.widget.cts.ExpandableListViewTest#testSetOnChildClickListener;android.widget.cts.EditTextTest#testAndroidTestCaseSetupProperly;android.widget.cts.TextViewTest#testMarquee;android.widget.cts.ImageViewTest#testActivityTestCaseSetUpProperly;android.widget.cts.RadioGroupTest#testConstructors;android.widget.cts.DialerFilterTest#testAccessMode;android.widget.cts.DatePickerTest#testInit;android.widget.cts.TextViewTest#testGetTextColors;android.widget.cts.ProgressBarTest#testAccessProgress;android.widget.cts.TextViewTest#testGetPaint;android.widget.cts.SimpleExpandableListAdapterTest#testNewGroupView;android.widget.cts.AdapterView_AdapterContextMenuInfoTest#testConstructor;android.widget.cts.CompoundButtonTest#testConstructor;android.widget.cts.ImageViewTest#testSetImageLevel;android.widget.cts.SimpleCursorTreeAdapterTest#testBindGroupView;android.widget.cts.SimpleExpandableListAdapterTest#testGetGroup;android.widget.cts.TabWidgetTest#testFocusCurrentTab;android.widget.cts.RelativeLayoutTest#testOnLayout;android.widget.cts.ScrollViewTest#testComputeScroll;android.widget.cts.TextViewTest#testAccessHintTextColor;android.widget.cts.TableLayoutTest#testAccessShrinkAllColumns;android.widget.cts.RemoteViewsTest#testGetPackage;android.widget.cts.GridViewTest#testAttachLayoutAnimationParameters;android.widget.cts.LinearLayout_LayoutParamsTest#testAndroidTestCaseSetupProperly;android.widget.cts.CompoundButtonTest#testAndroidTestCaseSetupProperly;android.widget.cts.ImageViewTest#testSetImageDrawable;android.widget.cts.AdapterViewTest#testAccessVisiblePosition;android.widget.cts.ListViewTest#testSaveAndRestoreInstanceState;android.widget.cts.CompoundButtonTest#testToggle;android.widget.cts.TextViewTest#testMoveCursorToVisibleOffset;android.widget.cts.AutoCompleteTextViewTest#testAccessDropDownWidth;android.widget.cts.RadioGroupTest#testGenerateLayoutParams;android.widget.cts.AdapterViewTest#testCanAnimate;android.widget.cts.ScrollViewTest#testOnLayout;android.widget.cts.AutoCompleteTextViewTest#testEnoughToFilter;android.widget.cts.CheckedTextViewTest#testOnCreateDrawableState;android.widget.cts.RelativeLayout_LayoutParamsTest#testDebug;android.widget.cts.ToastTest#testAccessDuration;android.widget.cts.ToggleButtonTest#testSetChecked;android.widget.cts.HeaderViewListAdapterTest#testGetFootersCount;android.widget.cts.GridLayoutTest#testGenerateDefaultLayoutParams;android.widget.cts.AbsListViewTest#testAccessSmoothScrollbarEnabled;android.widget.cts.SlidingDrawerTest#testSetOnDrawerOpenListener;android.widget.cts.HeaderViewListAdapterTest#testGetHeadersCount;android.widget.cts.RemoteViewsTest#testSetLong;android.widget.cts.SlidingDrawerTest#testSetOnDrawerCloseListener;android.widget.cts.TextViewTest#testGetResolvedTextDirectionLtrWithInheritance;android.widget.cts.ScrollViewTest#testFullScroll;android.widget.cts.RelativeLayout_LayoutParamsTest#testAccessRule1;android.widget.cts.BaseExpandableListAdapterTest#testAreAllItemsEnabled;android.widget.cts.RelativeLayout_LayoutParamsTest#testAccessRule2;android.widget.cts.RemoteViewsActivityTest#testDerivedClass;android.widget.cts.ToastTest#testShowFailure;android.widget.cts.SimpleAdapterTest#testSetViewImage;android.widget.cts.TextViewTest#testAccessPrivateImeOptions;android.widget.cts.AdapterView_AdapterContextMenuInfoTest#testAndroidTestCaseSetupProperly;android.widget.cts.TwoLineListItemTest#testOnFinishInflate;android.widget.cts.ExpandableListViewTest#testGetExpandableListPosition;android.widget.cts.AbsListViewTest#testAccessListPadding;android.widget.cts.ExpandableListViewTest#testExpandGroup;android.widget.cts.TextViewTest#testGetUrls;android.widget.cts.LinearLayoutTest#testAccessBaselineAlignedChildIndex;android.widget.cts.CheckBoxTest#testAndroidTestCaseSetupProperly;android.widget.cts.ZoomControlsTest#testSetOnZoomInClickListener;android.widget.cts.AbsSpinnerTest#testGetCount;android.widget.cts.SimpleExpandableListAdapterTest#testNewChildView;android.widget.cts.ViewSwitcherTest#testConstructor;android.widget.cts.AutoCompleteTextViewTest#testReplaceText;android.widget.cts.ScrollViewTest#testAccessFillViewport;android.widget.cts.ToastTest#testShow;android.widget.cts.TextViewTest#testDrawableStateChanged;android.widget.cts.TimePickerTest#testGetBaseline;android.widget.cts.TextViewTest#testOnTouchEvent;android.widget.cts.ListViewTest#testOnFocusChanged;android.widget.cts.ImageViewTest#testDrawableStateChanged;android.widget.cts.FrameLayoutTest#testOnLayout;android.widget.cts.ListViewTest#testAccessItemsCanFocus;android.widget.cts.AutoCompleteTextViewTest#testOnCommitCompletion;android.widget.cts.ScrollViewTest#testAccessSmoothScrollingEnabled;android.widget.cts.TextViewTest#testSelection;android.widget.cts.CheckedTextViewTest#testOnDraw;android.widget.cts.ViewAnimatorTest#testConstructor;android.widget.cts.RadioGroupTest#testInternalPassThroughHierarchyChangeListener;android.widget.cts.SimpleCursorAdapterTest#testSetViewImage;android.widget.cts.AdapterViewTest#testGetSelected;android.widget.cts.ExpandableListViewWithHeadersTest#testSelectedPosition;android.widget.cts.ProgressBarTest#testAccessIndeterminateDrawable;android.widget.cts.TableLayoutTest#testSetOnHierarchyChangeListener;android.widget.cts.ExpandableListViewTest#testSetOnItemClickListener;android.widget.cts.ProgressBarTest#testSetVisibility;android.widget.cts.AutoCompleteTextViewTest#testConvertSelectionToString;android.widget.cts.ImageViewTest#testSetImageResource;android.widget.cts.ScrollViewTest#testGetVerticalFadingEdgeStrengths;android.widget.cts.FilterTest#testFilter2;android.widget.cts.CursorTreeAdapterTest#testAndroidTestCaseSetupProperly;android.widget.cts.FilterTest#testFilter1;android.widget.cts.ZoomControlsTest#testSetIsZoomOutEnabled;android.widget.cts.ScrollViewTest#testMeasureChildWithMargins;android.widget.cts.CompoundButtonTest#testSetOnCheckedChangeListener;android.widget.cts.TextViewTest#testGetEditableText;android.widget.cts.HorizontalScrollViewTest#testOnRequestFocusInDescendants;android.widget.cts.AbsSpinnerTest#testAccessAdapter;android.widget.cts.ChronometerTest#testAccessOnChronometerTickListener;android.widget.cts.HeaderViewListAdapterTest#testConstructor;android.widget.cts.FrameLayoutTest#testSetForegroundGravity;android.widget.cts.AbsSpinnerTest#testGetSelectedView;android.widget.cts.TextViewTest#testSetGetTextAlignment;android.widget.cts.TextViewTest#testGetLayout;android.widget.cts.ImageViewTest#testConstructor;android.widget.cts.ImageViewTest#testInvalidateDrawable;android.widget.cts.PopupWindowTest#testShowAsDropDownWithOffsets;android.widget.cts.PopupWindowTest#testIsAboveAnchor;android.widget.cts.AutoCompleteTextViewTest#testPerformFiltering;android.widget.cts.ViewFlipperTest#testConstructor;android.widget.cts.DatePickerTest#testUpdateDate;android.widget.cts.MultiAutoCompleteTextViewTest#testMultiAutoCompleteTextView;android.widget.cts.MultiAutoCompleteTextView_CommaTokenizerTest#testConstructor;android.widget.cts.RemoteViewsTest#testSetOnClickPendingIntent;android.widget.cts.HorizontalScrollViewTest#testScrollTo;android.widget.cts.HorizontalScrollViewTest#testOnTouchEvent;android.widget.cts.ListViewTest#testOnMeasure;android.widget.cts.TextViewTest#testSetHighlightColor;android.widget.cts.BaseExpandableListAdapterTest#testNotifyDataSetInvalidated;android.widget.cts.ExpandableListViewWithHeadersTest#testConvertionBetweenFlatAndPacked;android.widget.cts.HeaderViewListAdapterTest#testRemoveFooter;android.widget.cts.ListViewTest#testTransientStateStableIds;android.widget.cts.ScrollViewTest#testSmoothScrollBy;android.widget.cts.ViewSwitcherTest#testAndroidTestCaseSetupProperly;android.widget.cts.TextViewTest#testAccessAutoLinkMask;android.widget.cts.ScrollerTest#testScrollModeWithDefaultDuration;android.widget.cts.GalleryTest#testComputeHorizontalScrollRange;android.widget.cts.CheckedTextViewTest#testSetPadding;android.widget.cts.CursorTreeAdapterTest#testGetGroupId;android.widget.cts.RemoteViewsTest#testApply;android.widget.cts.DialerFilterTest#testIsQwertyKeyboard;android.widget.cts.GalleryTest#testShowContextMenuForChild;android.widget.cts.ScrollViewTest#testPageScroll;android.widget.cts.SimpleCursorTreeAdapterTest#testSetViewImage;android.widget.cts.TableLayoutTest#testOnLayout;android.widget.cts.ArrayAdapterTest#testGetPosition;android.widget.cts.TabWidgetTest#testOnSizeChanged;android.widget.cts.SimpleCursorAdapterTest#testNewDropDownView;android.widget.cts.AutoCompleteTextViewTest#testAccessListSelection;android.widget.cts.TextViewTest#testSetText1;android.widget.cts.TextViewTest#testSetText2;android.widget.cts.RemoteViewsActivityTest#testWebView;android.widget.cts.TextViewTest#testSetText3;android.widget.cts.ToastTest#testAccessGravity;android.widget.cts.HorizontalScrollViewTest#testAddViewWithIndex;android.widget.cts.ResourceCursorTreeAdapterTest#testNewChildView;android.widget.cts.HeaderViewListAdapterTest#testAndroidTestCaseSetupProperly;android.widget.cts.ExpandableListViewTest#testGetPackedPositionForGroup;android.widget.cts.ZoomControlsTest#testShowAndHide;android.widget.cts.CompoundButtonTest#testAccessChecked;android.widget.cts.SimpleAdapterTest#testGetView;android.widget.cts.DialerFilterTest#testGetDigits;android.widget.cts.ArrayAdapterTest#testDataChangeEvent;android.widget.cts.ImageSwitcherTest#testAndroidTestCaseSetupProperly;android.widget.cts.SimpleExpandableListAdapterTest#testGetChildView;android.widget.cts.ZoomControlsTest#testConstructor;android.widget.cts.CompoundButtonTest#testVerifyDrawable;android.widget.cts.TextViewTest#testSetEms;android.widget.cts.AbsSpinnerTest#testGenerateDefaultLayoutParams;android.widget.cts.TextViewTest#testAccessError;android.widget.cts.ZoomButtonTest#testOnTouchEvent;android.widget.cts.TextViewTest#testSetMaxLinesException;android.widget.cts.CompoundButtonTest#testSetButtonDrawableById;android.widget.cts.FrameLayoutTest#testGenerateDefaultLayoutParams;android.widget.cts.HorizontalScrollViewTest#testComputeScrollDeltaToGetChildRectOnScreen;android.widget.cts.ExpandableListViewTest#testSetSelectedChild;android.widget.cts.MediaControllerTest#testShow;android.widget.cts.CursorAdapterTest#testGetDropDownView;android.widget.cts.ListViewTest#testNoSelectableItems;android.widget.cts.TextViewTest#testGetBaseLine;android.widget.cts.AbsSpinnerTest#testConstructor;android.widget.cts.ListViewTest#testAccessDividerHeight;android.widget.cts.TabWidgetTest#testChildDrawableStateChanged;android.widget.cts.AbsListViewTest#testAccessSelectedItem;android.widget.cts.ScrollerTest#testTimePassed;android.widget.cts.ArrayAdapterTest#testGetItem;android.widget.cts.ImageViewTest#testVerifyDrawable;android.widget.cts.ProgressBarTest#testConstructor;android.widget.cts.ProgressBarTest#testIncrementSecondaryProgressBy;android.widget.cts.CursorTreeAdapterTest#testSetChildrenCursor;android.widget.cts.FrameLayoutTest#testCheckLayoutParams;android.widget.cts.AbsoluteLayoutTest#testGenerateDefaultLayoutParams;android.widget.cts.ZoomButtonTest#testOnKeyUp;android.widget.cts.BaseExpandableListAdapterTest#testGetCombinedId;android.widget.cts.ListViewTest#testSetSelection;android.widget.cts.SimpleCursorAdapterTest#testNewView;android.widget.cts.AutoCompleteTextViewTest#testPopupWindow;android.widget.cts.TextView_SaveStateTest#testWriteToParcel;android.widget.cts.TabHost_TabSpecTest#testSetContent2;android.widget.cts.SeekBarTest#testConstructor;android.widget.cts.TabHost_TabSpecTest#testSetContent1;android.widget.cts.TextViewTest#testSetExtractedText;android.widget.cts.TabHost_TabSpecTest#testSetContent3;android.widget.cts.SimpleExpandableListAdapterTest#testGetGroupCount;android.widget.cts.TextViewTest#testComputeVerticalScrollRange;android.widget.cts.GridLayoutTest#testAlignment;android.widget.cts.ExpandableListViewTest#testGetPackedPositionForChild;android.widget.cts.FrameLayoutTest#testConstructor;android.widget.cts.CursorAdapterTest#testAndroidTestCaseSetupProperly;android.widget.cts.RelativeLayoutTest#testSetHorizontalGravity;android.widget.cts.GalleryTest#testDispatchKeyEvent;android.widget.cts.ToastTest#testSetText1;android.widget.cts.HorizontalScrollViewTest#testFullScroll;android.widget.cts.RemoteViewsTest#testSetCharSequence;android.widget.cts.ToastTest#testSetText2;android.widget.cts.HeaderViewListAdapterTest#testRemoveHeader;android.widget.cts.TextSwitcherTest#testSetCurrentText;android.widget.cts.TwoLineListItemTest#testConstructor;android.widget.cts.MediaControllerTest#testMediaController;android.widget.cts.ListViewTest#testConstructor;android.widget.cts.AbsListViewTest#testAccessSelector;android.widget.cts.BaseExpandableListAdapterTest#testDataSetObserver;android.widget.cts.BaseAdapterTest#testHasStableIds;android.widget.cts.TextViewTest#testAccessImeActionLabel;android.widget.cts.CompoundButtonTest#testSetButtonDrawableByDrawable;android.widget.cts.AbsListViewTest#testLayoutChildren;android.widget.cts.ImageViewTest#testClearColorFilter;android.widget.cts.TextViewTest#testGetResolvedTextDirectionRtl;android.widget.cts.CheckedTextViewTest#testChecked;android.widget.cts.MultiAutoCompleteTextViewTest#testReplaceText;android.widget.cts.DigitalClockTest#testOnDetachedFromWindow;android.widget.cts.CursorTreeAdapterTest#testGetChildView;android.widget.cts.FrameLayout_LayoutParamsTest#testAndroidTestCaseSetupProperly;android.widget.cts.ImageViewTest#testSetMaxWidth;android.widget.cts.TabHostTest#testDispatchWindowFocusChanged;android.widget.cts.DigitalClockTest#testConstructor;android.widget.cts.DialerFilterTest#testOnKeyUpDown;android.widget.cts.TextViewTest#testSetHorizontallyScrolling;android.widget.cts.HeaderViewListAdapterTest#testGetViewTypeCount;android.widget.cts.AbsSeekBarTest#testAccessKeyProgressIncrement;android.widget.cts.AbsSeekBarTest#testSetThumb;android.widget.cts.ProgressBarTest#testOnDraw;android.widget.cts.TextViewTest#testSetText;android.widget.cts.PopupWindowTest#testAccessContentView;android.widget.cts.GridLayoutTest#testConstructor;android.widget.cts.PopupWindowTest#testAccessHeight;android.widget.cts.ToggleButtonTest#testAndroidTestCaseSetupProperly;android.widget.cts.ProgressBarTest#testVerifyDrawable;android.widget.cts.TableRowTest#testOnLayout;android.widget.cts.RadioGroupTest#testInternalCheckedStateTracker;android.widget.cts.HorizontalScrollViewTest#testOnSizeChanged;android.widget.cts.ResourceCursorAdapterTest#testNewView;android.widget.cts.MultiAutoCompleteTextView_CommaTokenizerTest#testFindTokenEnd;android.widget.cts.ZoomButtonTest#testOnLongClick;android.widget.cts.AnalogClockTest#testOnDetachedFromWindow;android.widget.cts.TextViewTest#testSetMaxLines;android.widget.cts.ExpandableListViewTest#testPerformItemClick;android.widget.cts.TextViewTest#testResetTextDirection;android.widget.cts.ScrollViewTest#testScrollTo;android.widget.cts.TableLayoutTest#testGenerateDefaultLayoutParams;android.widget.cts.ImageViewTest#testSetAdjustViewBounds;android.widget.cts.TableRowTest#testCheckLayoutParams;android.widget.cts.CheckedTextViewTest#testToggle;android.widget.cts.RemoteViewsTest#testSetProgressBar;android.widget.cts.VideoViewTest#testSetOnErrorListener;android.widget.cts.GalleryTest#testDispatchSetPressed;android.widget.cts.HorizontalScrollViewTest#testSmoothScrollTo;android.widget.cts.ZoomControlsTest#testSetZoomSpeed;android.widget.cts.ExpandableListViewTest#testSetAdapter;android.widget.cts.AutoCompleteTextViewTest#testOnFilterComplete;android.widget.cts.ImageViewTest#testGetDrawable;android.widget.cts.CursorTreeAdapterTest#testRunQueryOnBackgroundThread;android.widget.cts.SlidingDrawerTest#testGetHandle;android.widget.cts.BaseAdapterTest#testAndroidTestCaseSetupProperly;android.widget.cts.MediaControllerTest#testOnTrackballEvent;android.widget.cts.GalleryTest#testGenerateLayoutParams;android.widget.cts.PopupWindowTest#testConstructor;android.widget.cts.GalleryTest#testFoo;android.widget.cts.PopupWindowTest#testAccessAnimationStyle;android.widget.cts.ToggleButtonTest#testAccessTextOn;android.widget.cts.AbsoluteLayoutTest#testGenerateLayoutParams1;android.widget.cts.TextViewTest#testGetDefaultEditable;android.widget.cts.AbsoluteLayoutTest#testGenerateLayoutParams2;android.widget.cts.ArrayAdapterTest#testGetFilter;android.widget.cts.CheckedTextViewTest#testSetCheckMarkDrawableById;android.widget.cts.ScrollerTest#testAbortAnimation;android.widget.cts.LinearLayout_LayoutParamsTest#testConstructor;android.widget.cts.TextViewTest#testComputeVerticalScrollExtent;android.widget.cts.AbsSpinnerTest#testSetSelectionInt;android.widget.cts.TabHostTest#testGetTabContentView;android.widget.cts.TextViewTest#testGetDefaultMovementMethod;android.widget.cts.ScrollViewTest#testRequestChildRectangleOnScreen;android.widget.cts.TabHostTest#testGetCurrentView;android.widget.cts.TextViewTest#testAccessFilters;android.widget.cts.GalleryTest#testCheckLayoutParams;android.widget.cts.RadioGroup_LayoutParamsTest#testConstructor;android.widget.cts.BaseExpandableListAdapterTest#testOnGroupExpanded;android.widget.cts.MultiAutoCompleteTextView_CommaTokenizerTest#testTerminateToken;android.widget.cts.ScrollViewTest#testConstructor;android.widget.cts.ListViewTest#testLayoutChildren;android.widget.cts.RemoteViewsTest#testSetImageViewUri;android.widget.cts.ViewAnimatorTest#testAccessDisplayedChild;android.widget.cts.ExpandableListViewTest#testCollapseGroup;android.widget.cts.AnalogClockTest#testConstructor;android.widget.cts.CursorAdapterTest#testConvertToString;android.widget.cts.BaseAdapterTest#testIsEmpty;android.widget.cts.ResourceCursorTreeAdapterTest#testNewGroupView;android.widget.cts.ScrollViewTest#testFling;android.widget.cts.SlidingDrawerTest#testOnMeasure;android.widget.cts.TextViewTest#testSetMaxEms;android.widget.cts.LinearLayoutTest#testGenerateLayoutParams;android.widget.cts.PopupWindowTest#testAccessInputMethodMode;android.widget.cts.ListViewTest#testPerformItemClick;android.widget.cts.TextViewTest#testOnTextChanged;android.widget.cts.GridViewTest#testSetSelection;android.widget.cts.CursorAdapterTest#testHasStableIds;android.widget.cts.ProgressBarTest#testSetIndeterminate;android.widget.cts.TableRowTest#testGetVirtualChildCount;android.widget.cts.AutoCompleteTextViewTest#testOnTextChanged;android.widget.cts.TextViewTest#testSetLinesException;android.widget.cts.TextViewTest#testLength;android.widget.cts.RelativeLayoutTest#testSetGravity;android.widget.cts.RelativeLayoutTest#testConstructor;android.widget.cts.TextViewTest#testGetLineHeight;android.widget.cts.GalleryTest#testSetAnimationDuration;android.widget.cts.PopupWindowTest#testAccessTouchable;android.widget.cts.RelativeLayoutTest#testSetVerticalGravity;android.widget.cts.CursorTreeAdapterTest#testConstructor;android.widget.cts.AbsSpinnerTest#testRequestLayout;android.widget.cts.TextViewTest#testOnKeyShortcut;android.widget.cts.TableRowTest#testGetVirtualChildAt;android.widget.cts.SpinnerTest#testConstructor;android.widget.cts.SimpleAdapterTest#testGetDropDownView;android.widget.cts.ScrollViewTest#testAddViewWithLayoutParams;android.widget.cts.HeaderViewListAdapterTest#testGetItemViewType;android.widget.cts.RadioGroupTest#testSetOnCheckedChangeListener;android.widget.cts.ToggleButtonTest#testOnFinishInflate;android.widget.cts.DialerFilterTest#testAppend;android.widget.cts.HorizontalScrollViewTest#testRequestChildRectangleOnScreen;android.widget.cts.RadioButtonTest#testConstructor;android.widget.cts.PopupWindowTest#testDismiss;android.widget.cts.TableLayoutTest#testAddView2;android.widget.cts.TableLayoutTest#testAddView3;android.widget.cts.TabHostTest#testOnTouchModeChanged;android.widget.cts.TableLayoutTest#testAddView4;android.widget.cts.HeaderViewListAdapterTest#testGetWrappedAdapter;android.widget.cts.TableLayoutTest#testAddView1;android.widget.cts.ImageSwitcherTest#testSetImageResource;android.widget.cts.ToggleButtonTest#testDrawableStateChanged;android.widget.cts.EditTextTest#testSetSelectionIndex;android.widget.cts.EditTextTest#testExtendSelection;android.widget.cts.AbsSeekBarTest#testSetMax;android.widget.cts.CursorTreeAdapterTest#testChangeCursor;android.widget.cts.AdapterViewTest#testItemOrItemIdAtPosition;android.widget.cts.HorizontalScrollViewTest#testDispatchKeyEvent;android.widget.cts.TableRowTest#testGenerateDefaultLayoutParams;android.widget.cts.AbsListViewTest#testSetRecyclerListener;android.widget.cts.GalleryTest#testGetContextMenuInfo;android.widget.cts.AbsListViewTest#testAccessStackFromBottom;android.widget.cts.TextViewTest#testAccessText;android.widget.cts.SpinnerTest#testGetBaseline;android.widget.cts.ScrollViewTest#testRequestChildFocus;android.widget.cts.LinearLayoutTest#testGenerateDefaultLayoutParams;android.widget.cts.ButtonTest#testConstructor;android.widget.cts.ResourceCursorAdapterTest#testSetViewResource;android.widget.cts.ExpandableListViewBasicTest#testContextMenus;android.widget.cts.FrameLayoutTest#testVerifyDrawable;android.widget.cts.ExpandableListViewTest#testGetPackedPositionType;android.widget.cts.LinearLayoutTest#testLayoutHorizontal;android.widget.cts.ScrollViewTest#testComputeVerticalScrollRange;android.widget.cts.GridViewTest#testOnFocusChanged;android.widget.cts.ZoomControlsTest#testOnTouchEvent;android.widget.cts.GridViewTest#testActivityTestCaseSetUpProperly;android.widget.cts.ListViewTest#testAccessItemChecked;android.widget.cts.AbsoluteLayoutTest#testConstructor;android.widget.cts.TextViewTest#testComputeHorizontalScrollRange;android.widget.cts.HorizontalScrollViewTest#testAddViewWithLayoutParams;android.widget.cts.RemoteViewsTest#testGetLayoutId;android.widget.cts.CursorTreeAdapterTest#testNotifyDataSetChangedBoolean;android.widget.cts.TableRow_LayoutParamsTest#testSetBaseAttributes;android.widget.cts.HorizontalScrollViewTest#testMeasureChildWithMargins;android.widget.cts.TextViewTest#testSetIncludeFontPadding;android.widget.cts.HorizontalScrollViewTest#testAddView;android.widget.cts.TimePickerTest#testAccessCurrentMinute;android.widget.cts.TextViewTest#testGetExtendedPaddingBottom;android.widget.cts.BaseExpandableListAdapterTest#testIsEmpty;android.widget.cts.SimpleAdapterTest#testAccessViewBinder;android.widget.cts.SpinnerTest#testOnLayout;android.widget.cts.AutoCompleteTextViewTest#testAccessValidater;android.widget.cts.ScrollViewTest#testAddViewWithIndex;android.widget.cts.FrameLayoutTest#testDrawableStateChanged;android.widget.cts.PopupWindowTest#testSetTouchInterceptor;android.widget.cts.SimpleAdapterTest#testGetItem;android.widget.cts.ImageViewTest#testSetColorFilter1;android.widget.cts.ImageViewTest#testSetColorFilter2;android.widget.cts.SlidingDrawerTest#testDispatchDraw;android.widget.cts.TextViewTest#testSetOnEditorActionListener;android.widget.cts.DatePickerTest#testSetEnabled;android.widget.cts.RemoteViewsTest#testReapply;android.widget.cts.TextViewTest#testCompound;android.widget.cts.AdapterViewTest#testAccessEmptyView;android.widget.cts.Gallery_LayoutParamsTest#testConstructor;android.widget.cts.TextViewTest#testGetResolvedTextDirectionRtlWithInheritance;android.widget.cts.PopupWindowTest#testShowAtLocation;android.widget.cts.TextViewTest#testPerformLongClick;android.widget.cts.ExpandableListViewTest#testSetChildDivider;android.widget.cts.PopupWindowTest#testAccessClippingEnabled;android.widget.cts.RemoteViewsTest#testSetTextViewText;android.widget.cts.ScrollViewTest#testMeasureChild;android.widget.cts.TableLayoutTest#testAccessColumnShrinkable;android.widget.cts.TableRow_LayoutParamsTest#testConstructor;android.widget.cts.GalleryTest#testComputeHorizontalScrollOffset;android.widget.cts.ProgressBarTest#testOnMeasure;android.widget.cts.ProgressBarTest#testAccessProgressDrawable;android.widget.cts.SimpleAdapterTest#testGetFilter;android.widget.cts.RadioGroupTest#testSetOnHierarchyChangeListener;android.widget.cts.AbsListViewTest#testHandleDataChanged;android.widget.cts.AbsListViewTest#testShowContextMenuForChild;android.widget.cts.ExpandableListViewTest#testConstructor;android.widget.cts.HeaderViewListAdapterTest#testIsEmpty;android.widget.cts.AlphabetIndexerTest#testAlphabetIndexer;android.widget.cts.ChronometerTest#testAccessBase;android.widget.cts.TextViewTest#testAccessRawContentType;android.widget.cts.ScrollViewTest#testExecuteKeyEvent;android.widget.cts.SpinnerTest#testAccessPrompt;android.widget.cts.AnalogClockTest#testOnDraw;android.widget.cts.ChronometerTest#testStartAndStop;android.widget.cts.TabWidgetTest#testSetCurrentTab;android.widget.cts.CursorTreeAdapterTest#testGetFilter;android.widget.cts.ImageSwitcherTest#testSetImageURI;android.widget.cts.TabHostTest#testSetOnTabChangedListener;android.widget.cts.ScrollViewTest#testSmoothScrollTo;android.widget.cts.SimpleExpandableListAdapterTest#testGetGroupId;android.widget.cts.AbsListViewTest#testAccessCacheColorHint;android.widget.cts.TabHostTest#testSetCurrentTabByTag;android.widget.cts.HeaderViewListAdapterTest#testIsEnabled;android.widget.cts.ImageViewTest#testSetImageState;android.widget.cts.ExpandableListViewTest#testSetSelectedGroup;android.widget.cts.DialerFilterTest#testRemoveFilterWatcher;android.widget.cts.ToggleButtonTest#testAccessTextOff;android.widget.cts.TextViewTest#testSetSelectAllOnFocus;android.widget.cts.TextViewTest#testAccessKeyListener;android.widget.cts.ArrayAdapterTest#testInsert;android.widget.cts.MediaControllerTest#testSetPrevNextListeners;android.widget.cts.PopupWindowTest#testUpdatePositionAndDimension;android.widget.cts.TextViewTest#testOnDraw;android.widget.cts.TextViewTest#testSetFrame;android.widget.cts.ScrollerTest#testAccessFinalX;android.widget.cts.ScrollerTest#testAccessFinalY;android.widget.cts.AdapterViewTest#testOnLayout;android.widget.cts.ScrollViewTest#testOnRequestFocusInDescendants;android.widget.cts.TextViewTest#testCancelLongPress;android.widget.cts.GridViewTest#testAccessAdapter;android.widget.cts.ZoomButtonTest#testSetZoomSpeed;android.widget.cts.ExpandableListViewTest#testGetPackedPositionGroup;android.widget.cts.ExpandableListViewTest#testSetGroupIndicator;android.widget.cts.TextViewTest#testGetResolvedTextDirectionLtr;android.widget.cts.BaseAdapterTest#testGetDropDownView;android.widget.cts.ViewAnimatorTest#testShowPrevious;android.widget.cts.TextViewTest#testOnCreateContextMenu;android.widget.cts.TextViewTest#testAppend;android.widget.cts.DialerFilterTest#testOnFocusChanged;android.widget.cts.SlidingDrawerTest#testOnLayout;android.widget.cts.TextViewTest#testGetFocusedRect;android.widget.cts.TextSwitcherTest#testAddView;android.widget.cts.ExpandableListViewTest#testSetChildIndicatorBounds;android.widget.cts.RemoteViewsTest#testSetImageViewBitmap;android.widget.cts.CursorTreeAdapterTest#testHasStableIds;android.widget.cts.ToastTest#testConstructor;android.widget.cts.CursorTreeAdapterTest#testConvertToString;android.widget.cts.AbsListViewTest#testGetTopBottomFadingEdgeStrength;android.widget.cts.EditTextTest#testGetDefaultMovementMethod;android.widget.cts.AbsListViewTest#testAddTouchables;android.widget.cts.SeekBarTest#testSetOnSeekBarChangeListener;android.widget.cts.TextViewTest#testAccessLinkTextColor;android.widget.cts.TextViewTest#testAccessEllipsize;android.widget.cts.RatingBarTest#testAccessNumStars;android.widget.cts.ImageViewTest#testGetBaseline;android.widget.cts.AnalogClockTest#testOnAttachedToWindow;android.widget.cts.LayoutDirectionTest#testDirectionInheritanceForAllLayoutsWithCode;android.widget.cts.AutoCompleteTextViewTest#testAccessItemSelectedListener;android.widget.cts.TextSwitcherTest#testConstructor;android.widget.cts.TextViewTest#testGetResolvedTextAlignment;android.widget.cts.ExpandableListView_ExpandableListContextMenuInfoTest#testConstructor;android.widget.cts.HeaderViewListAdapterTest#testGetFilter;android.widget.cts.AbsSeekBarTest#testConstructor;android.widget.cts.ZoomControlsTest#testSetOnZoomOutClickListener;android.widget.cts.ArrayAdapterTest#testSort;android.widget.cts.HorizontalScrollViewTest#testFling;android.widget.cts.GridViewTest#testConstructor;android.widget.cts.RemoteViewsTest#testNotFeasibleSetters;android.widget.cts.RemoteViewsTest#testWriteToParcel;android.widget.cts.ScrollViewTest#testAddView;android.widget.cts.FilterTest#testConstructor;android.widget.cts.SimpleExpandableListAdapterTest#testGetChildId;android.widget.cts.AutoCompleteTextViewTest#testOnDetachedFromWindow;android.widget.cts.ScrollViewTest#testRequestLayout;android.widget.cts.ImageButtonTest#testConstructor;android.widget.cts.EditTextTest#testSelectAll;android.widget.cts.PopupWindowTest#testSetOnDismissListener;android.widget.cts.TimePickerTest#testAccessCurrentHour;android.widget.cts.ArrayAdapterTest#testCreateFromResource;android.widget.cts.TextViewTest#testSetShadowLayer;android.widget.cts.CompoundButtonTest#testOnCreateDrawableState;android.widget.cts.SimpleCursorAdapterTest#testChangeCursorAndColumns;android.widget.cts.ImageViewTest#testSetFrame;android.widget.cts.ArrayAdapterTest#testSetDropDownViewResouce;android.widget.cts.ResourceCursorAdapterTest#testNewDropDownView;android.widget.cts.ToastTest#testCancel;android.widget.cts.RatingBarTest#testAccessStepSize;android.widget.cts.CheckedTextViewTest#testSetCheckMarkDrawableByDrawable;android.widget.cts.TextViewTest#testInstanceState;android.widget.cts.AbsListViewTest#testGetFocusedRect;android.widget.cts.ViewAnimatorTest#testAddView;android.widget.cts.AdapterViewTest#testUnsupportedMethods;android.widget.cts.TextViewTest#testOnPrivateIMECommand;android.widget.cts.CursorAdapterTest#testAccessCursor;android.widget.cts.TextViewTest#testBringPointIntoView;android.widget.cts.SpinnerTest#testOnClick;android.widget.cts.TextViewTest#testSetEditableFactory;android.widget.cts.ViewSwitcherTest#testGetNextView;android.widget.cts.AbsSeekBarTest#testAccessThumbOffset;android.widget.cts.AbsoluteLayout_LayoutParamsTest#testDebug;android.widget.cts.DatePickerTest#testAccessDate;android.widget.cts.CursorAdapterTest#testConstructor;android.widget.cts.HorizontalScrollViewTest#testPageScroll;android.widget.cts.AutoCompleteTextViewTest#testPerformValidation;android.widget.cts.TextViewTest#testAccessLinksClickable;android.widget.cts.SlidingDrawerTest#testGetContent;android.widget.cts.ViewAnimatorTest#testShowNext;android.widget.cts.TextViewTest#testResetTextAlignment;android.widget.cts.ScrollerTest#testFlingMode;android.widget.cts.SlidingDrawerTest#testOnInterceptTouchEvent;android.widget.cts.DatePickerTest#testConstructor;android.widget.cts.TableLayoutTest#testAccessColumnCollapsed;android.widget.cts.AbsSeekBarTest#testVerifyDrawable;android.widget.cts.ScrollViewTest#testOnSizeChanged;android.widget.cts.TimePickerTest#testConstructors;android.widget.cts.TextViewTest#testAccessTransformationMethod;android.widget.cts.SimpleCursorTreeAdapterTest#testBindChildView;android.widget.cts.ScrollerTest#testExtendDuration;android.widget.cts.RatingBarTest#testAccessRating;android.widget.cts.RelativeLayoutTest#testCheckLayoutParams;android.widget.cts.GalleryTest#testGenerateDefaultLayoutParams;android.widget.cts.AbsoluteLayoutTest#testCheckLayoutParams;android.widget.cts.SlidingDrawerTest#testOnFinishInflate;android.widget.cts.EditTextTest#testSetSelectionStartstop;android.widget.cts.ToggleButtonTest#testSetBackgroundDrawable;android.widget.cts.HeaderViewListAdapterTest#testRegisterDataSetObserver;android.widget.cts.TextViewTest#testAccessFreezesText;android.widget.cts.ArrayAdapterTest#testAndroidTestCaseSetupProperly;android.widget.cts.ExpandableListViewTest#testGetSelectedPosition;android.widget.cts.TabHostTest#testGetCurrentTabTag;android.widget.cts.GridViewTest#testOnMeasure;android.widget.cts.HeaderViewListAdapterTest#testGetItemId;android.widget.cts.RadioButtonTest#testToggle;android.widget.cts.TextViewTest#testOnFocusChanged;android.widget.cts.AbsoluteLayout_LayoutParamsTest#testAndroidTestCaseSetupProperly;android.widget.cts.ViewAnimatorTest#testAccessInAnimation;android.widget.cts.AdapterViewTest#testDispatchSaveInstanceState;android.widget.cts.HeaderViewListAdapterTest#testHasStableIds;android.widget.cts.RelativeLayoutTest#testOnMeasure;android.widget.cts.HorizontalScrollViewTest#testSmoothScrollBy;android.widget.cts.ListViewTest#testTransientStateUnstableIds;android.widget.cts.TableRowTest#testConstructor;android.widget.cts.TabHost_TabSpecTest#testSetIndicator2;android.widget.cts.ArrayAdapterTest#testAccessView;android.widget.cts.TabHost_TabSpecTest#testSetIndicator1;android.widget.cts.HorizontalScrollViewTest#testAccessSmoothScrollingEnabled;android.widget.cts.TextViewTest#testSetCursorVisible;android.widget.cts.FrameLayout_LayoutParamsTest#testConstructor;android.widget.cts.AutoCompleteTextViewTest#testOnAttachedToWindow;android.widget.cts.CursorAdapterTest#testInit;android.widget.cts.SimpleAdapterTest#testGetItemId;android.widget.cts.TableLayout_LayoutParamsTest#testSetBaseAttributes;android.widget.cts.ExpandableListViewBasicTest#testExpandedGroupMovement;android.widget.cts.TextViewTest#testAccessPaintFlags;android.widget.cts.CursorAdapterTest#testGetItemId;android.widget.cts.GalleryTest#testSetSpacing;android.widget.cts.TextViewTest#testSetLines;android.widget.cts.ScrollerTest#testConstructor;android.widget.cts.CompoundButtonTest#testOnDraw;android.widget.cts.ListViewTest#testGetMaxScrollAmount;android.widget.cts.TwoLineListItemTest#testGetTexts;android.widget.cts.AbsListViewTest#testBeforeAndAfterTextChanged;android.widget.cts.AdapterViewTest#testConstructor;android.widget.cts.SimpleAdapterTest#testSetViewText;android.widget.cts.ExpandableListViewTest#testGetSelectedId;android.widget.cts.TableLayoutTest#testOnMeasure;android.widget.cts.DialerFilterTest#testSetDigitsWatcher;android.widget.cts.AbsListViewTest#testPointToPosition;android.widget.cts.ExpandableListViewTest#testGetPackedPositionChild;android.widget.cts.ExpandableListViewBasicTest#testSelectedPosition;android.widget.cts.RemoteViewsTest#testSetBitmap;android.widget.cts.TextViewTest#testFoo;android.widget.cts.EditTextTest#testAccessText;android.widget.cts.AbsListViewTest#testComputeVerticalScrollValues;android.widget.cts.VideoViewTest#testConstructor;android.widget.cts.EditTextTest#testGetDefaultEditable;android.widget.cts.EditTextTest#testConstructor;android.widget.cts.ExpandableListViewWithHeadersTest#testExpandOnFirstGroup;android.widget.cts.RelativeLayout_LayoutParamsTest#testStartEnd;android.widget.cts.ScrollViewTest#testAddViewWithIndexAndLayoutParams;android.widget.cts.ViewFlipperTest#testSetFlipInterval;android.widget.cts.ZoomButtonTest#testConstructor;android.widget.cts.TableRowTest#testOnMeasure;android.widget.cts.TableLayoutTest#testAccessStretchAllColumns;android.widget.cts.GridViewTest#testSetHorizontalSpacing;android.widget.cts.ExpandableListViewWithHeadersTest#testContextMenus;android.widget.cts.ListViewTest#testRequestChildRectangleOnScreen;android.widget.cts.VideoViewTest#testSetMediaController;android.widget.cts.AdapterViewTest#testAccessOnItemClickAndLongClickListener;android.widget.cts.RemoteViewsTest#testSetImageViewResource;android.widget.cts.TextViewTest#testScroll;android.widget.cts.VideoViewTest#testResolveAdjustedSize;android.widget.cts.ScrollViewTest#testOnInterceptTouchEvent;android.widget.cts.ViewAnimatorTest#testRemoveViews;android.widget.cts.AlphabetIndexerTest#testAndroidTestCaseSetupProperly;android.widget.cts.ImageViewTest#testSetMaxHeight;android.widget.cts.ImageViewTest#testOnMeasure;android.widget.cts.RadioGroup_LayoutParamsTest#testSetBaseAttributes;android.widget.cts.AbsoluteLayoutTest#testOnMeasure;android.widget.cts.CompoundButtonTest#testPerformClick;android.widget.cts.ExpandableListViewTest#testAccessExpandableListAdapter;android.widget.cts.TextView_SaveStateTest#testToString;android.widget.cts.CursorTreeAdapterTest#testGetGroupView;android.widget.cts.TextViewTest#testPressKey;android.widget.cts.ProgressBarTest#testAccessMax;android.widget.cts.TextViewTest#testBeginEndBatchEdit;android.widget.cts.HeaderViewListAdapterTest#testGetItem;android.widget.cts.LinearLayoutTest#testActivityTestCaseSetUpProperly;android.widget.cts.TextViewTest#testSetGetTextDirection;android.widget.cts.HeaderViewListAdapterTest#testGetView;android.widget.cts.ScrollerTest#testScrollMode;android.widget.cts.ProgressBarTest#testAccessInterpolator;android.widget.cts.TextViewTest#testTextAttr;android.widget.cts.AbsListViewTest#testAccessScrollingCacheEnabled;android.widget.cts.MediaControllerTest#testSetEnabled;android.widget.cts.SimpleCursorAdapterTest#testConvertToString;android.widget.cts.ExpandableListViewTest#testDispatchDraw;android.widget.cts.DigitalClockTest#testOnAttachedToWindow;android.widget.cts.BaseAdapterTest#testGetViewTypeCount;android.widget.cts.CursorAdapterTest#testGetCount;android.widget.cts.ToastTest#testAccessView;android.widget.cts.FrameLayoutTest#testOnMeasure;android.widget.cts.HorizontalScrollViewTest#testGetMaxScrollAmount;android.widget.cts.AbsListViewTest#testSetScrollIndicators;android.widget.cts.TabHostTest#testOnAttachedToAndDetachedFromWindow;android.widget.cts.CursorAdapterTest#testGetFilter;android.widget.cts.CursorTreeAdapterTest#testGetChild;android.widget.cts.SlidingDrawerTest#testSetOnDrawerScrollListener;android.widget.cts.ProgressBarTest#testOnSizeChange;android.widget.cts.TextViewTest#testGetTotalPaddingRight;android.widget.cts.GridViewTest#testPressKey;android.widget.cts.CheckedTextViewTest#testDrawableStateChanged;android.widget.cts.ScrollerTest#testGetDuration;android.widget.cts.TableLayoutTest#testRequestLayout;android.widget.cts.ImageViewTest#testAccessImageMatrix;android.widget.cts.PopupWindowTest#testAccessWidth;android.widget.cts.BaseAdapterTest#testDataSetObserver;android.widget.cts.SpinnerTest#testPerformClick;android.widget.cts.MediaControllerTest#testConstructor;android.widget.cts.SimpleCursorTreeAdapterTest#testConstructor;android.widget.cts.TextViewTest#testGetTextColor;android.widget.cts.DialerFilterTest#testOnModechange;android.widget.cts.AdapterViewTest#testDispatchRestoreInstanceState;android.widget.cts.ImageViewTest#testOnCreateDrawableState;android.widget.cts.CursorTreeAdapterTest#testNotifyDataSetInvalidated;android.widget.cts.SimpleExpandableListAdapterTest#testGetGroupView;android.widget.cts.ListViewTest#testRequestLayout;android.widget.cts.Gallery_LayoutParamsTest#testAndroidTestCaseSetupProperly;android.widget.cts.SimpleExpandableListAdapterTest#testGetChild;android.widget.cts.TableRowTest#testSetOnHierarchyChangeListener;android.widget.cts.TextViewTest#testGetExtendedPaddingTop;android.widget.cts.ResourceCursorAdapterTest#testSetDropDownViewResource;android.widget.cts.LinearLayoutTest#testAccessBaselineAligned;android.widget.cts.CursorTreeAdapterTest#testGetChildId;android.widget.cts.GridViewTest#testSetColumnWidth;android.widget.cts.AbsListViewTest#testAccessTranscriptMode;android.widget.cts.VideoViewTest#testGetBufferPercentage;android.widget.cts.LinearLayoutTest#testConstructor;android.widget.cts.TextViewTest#testAccessContentType;android.widget.cts.SimpleCursorAdapterTest#testBindView;android.widget.cts.SlidingDrawerTest#testOnTouchEvent;android.widget.cts.ListViewTest#testOnKeyUpDown;android.widget.cts.SimpleCursorAdapterTest#testAccessCursorToStringConverter;android.widget.cts.TabWidgetTest#testAddView;android.widget.cts.TextViewTest#testIsInputMethodTarget;android.widget.cts.AbsoluteLayout_LayoutParamsTest#testConstructor;android.widget.cts.RelativeLayoutTest#testGetBaseline;android.widget.cts.TextViewTest#testGetLineCount;android.widget.cts.GridLayoutTest#testActivityTestCaseSetUpProperly;android.widget.cts.ProgressBarTest#testInvalidateDrawable;android.widget.cts.AbsSpinnerTest#testOnSaveAndRestoreInstanceState;android.widget.cts.MultiAutoCompleteTextViewTest#testConstructor;android.widget.cts.ExpandableListViewTest#testGetFlatListPosition;android.widget.cts.ArrayAdapterTest#testAddAllParams;android.widget.cts.CursorTreeAdapterTest#testSetGroupCursor;android.widget.cts.TextViewTest#testDidTouchFocusSelect;android.widget.cts.SimpleCursorAdapterTest#testConstructor;android.widget.cts.TextViewTest#testAccessImeOptions;android.widget.cts.TabHostTest#testClearAllTabs;android.widget.cts.TextViewTest#testGetFadingEdgeStrength;android.widget.cts.TextViewTest#testSetMinLines;android.widget.cts.DialerFilterTest#testConstructor;android.widget.cts.AbsListViewTest#testConstructor;android.widget.cts.HorizontalScrollViewTest#testAddViewWithIndexAndLayoutParams;android.widget.cts.TextViewTest#testTextDirectionDefault;android.widget.cts.TextViewTest#testGetLineBounds;android.widget.cts.HorizontalScrollViewTest#testConstructor;android.widget.cts.AutoCompleteTextViewTest#testSetFrame;android.widget.cts.RelativeLayoutTest#testGenerateLayoutParams2;android.widget.cts.RelativeLayoutTest#testGenerateLayoutParams1;android.widget.cts.ArrayAdapterTest#testAdd;android.widget.cts.PopupWindowTest#testUpdateDimensionAndAlignAnchorView;android.widget.cts.ToastTest#testAccessMargin;android.widget.cts.RemoteViewsActivityTest#testGood;android.widget.cts.TextViewTest#testAccessTextSize;android.widget.cts.TableLayoutTest#testColumnStretchableEffect;android.widget.cts.CompoundButtonTest#testDrawableStateChanged;android.widget.cts.CursorAdapterTest#testAccessFilterQueryProvider;android.widget.cts.HorizontalScrollViewTest#testArrowScroll;android.widget.cts.ScrollViewTest#testOnMeasure;android.widget.cts.TextViewTest#testTextChangedListener;android.widget.cts.AutoCompleteTextViewTest#testSetCompletionHint;android.widget.cts.TextViewTest#testAccessTextColor;android.widget.cts.FrameLayoutTest#testAccessMeasureAllChildren;android.widget.cts.LinearLayoutTest#testAccessWeightSum;android.widget.cts.HorizontalScrollViewTest#testOnMeasure;android.widget.cts.ResourceCursorAdapterTest#testConstructor;android.widget.cts.FrameLayoutTest#testOnSizeChanged;android.widget.cts.LinearLayoutTest#testCheckLayoutParams;android.widget.cts.PopupWindowTest#testSetWindowLayoutMode;android.widget.cts.ExpandableListView_ExpandableListContextMenuInfoTest#testAndroidTestCaseSetupProperly;android.widget.cts.ListViewTest#testAccessHeaderView;android.widget.cts.RatingBarTest#testAccessOnRatingBarChangeListener;android.widget.cts.GridViewTest#testSetGravity;android.widget.cts.TextViewTest#testAccessHint;android.widget.cts.HorizontalScrollViewTest#testOnLayout;android.widget.cts.ListViewTest#testAccessAdapter;android.widget.cts.ArrayAdapterTest#testGetItemId;android.widget.cts.ImageButtonTest#testAndroidTestCaseSetupProperly;android.widget.cts.TextViewTest#testOnDetachedFromWindow;android.widget.cts.ProgressBarTest#testAccessSecondaryProgress;android.widget.cts.SimpleExpandableListAdapterTest#testIsChildSelectable;android.widget.cts.TextViewTest#testTextAlignmentDefault;android.widget.cts.TableLayoutTest#testCheckLayoutParams;android.widget.cts.AnalogClockTest#testOnSizeChanged;android.widget.cts.DialerFilterTest#testSetLettersWatcher;android.widget.cts.ViewAnimatorTest#testAccessDisplayedChildBoundary;android.widget.cts.TextViewTest#testSetSpannableFactory;android.widget.cts.ImageSwitcherTest#testConstructor;android.widget.cts.TextViewTest#testAccessMovementMethod;android.widget.cts.AbsSeekBarTest#testDrawableStateChanged;android.widget.cts.RadioGroupTest#testAddView;android.widget.cts.HorizontalScrollViewTest#testOnInterceptTouchEvent;android.widget.cts.TabHostTest#testConstructor;android.widget.cts.CursorTreeAdapterTest#testGetChildrenCount;android.widget.cts.SimpleCursorAdapterTest#testChangeCursor;android.widget.cts.HorizontalScrollViewTest#testAccessFillViewport;android.widget.cts.AbsSeekBarTest#testFoo;android.widget.cts.TableLayoutTest#testConstructor;android.widget.cts.GalleryTest#testSetUnselectedAlpha;android.widget.cts.AbsListViewTest#testInvalidateViews;android.widget.cts.HeaderViewListAdapterTest#testGetCount;android.widget.cts.RatingBarTest#testAccessIndicator;android.widget.cts.RelativeLayout_LayoutParamsTest#testConstructor;android.widget.cts.CursorAdapterTest#testNewDropDownView;android.widget.cts.ChronometerTest#testAccessFormat;android.widget.cts.ExpandableListViewBasicTest#testPreconditions;android.widget.cts.AbsListViewTest#testFoo;android.widget.cts.ListViewTest#testOnTouchEvent;android.widget.cts.TabHostTest#testAddTab;android.widget.cts.AdapterViewTest#testChangeFocusable;android.widget.cts.ListViewTest#testAccessDivider;android.widget.cts.ListViewTest#testAccessFooterView;android.widget.cts.SimpleCursorAdapterTest#testAccessViewBinder;android.widget.cts.ZoomButtonTest#testSetEnabled;android.widget.cts.CheckedTextViewTest#testConstructor;android.widget.cts.GridViewTest#testLayoutChildren;android.widget.cts.ImageViewTest#testSetAlpha;android.widget.cts.TextViewTest#testClearComposingText;android.widget.cts.ZoomControlsTest#testSetIsZoomInEnabled;android.widget.cts.TabWidgetTest#testConstructor;android.widget.cts.TableRowTest#testGenerateLayoutParams2;android.widget.cts.TextViewTest#testSetTextAppearance;android.widget.cts.FilterTest#testConvertResultToString;android.widget.cts.MultiAutoCompleteTextViewTest#testPerformValidation;android.widget.cts.TextViewTest#testHeightAndWidth;android.widget.cts.CursorTreeAdapterTest#testIsChildSelectable;android.widget.cts.ArrayAdapterTest#testAddAllCollection;android.widget.cts.AbsListView_LayoutParamsTest#testConstructors;android.widget.cts.ScrollViewTest#testDispatchKeyEvent;android.widget.cts.TabHostTest#testAccessCurrentTab;android.widget.cts.ViewSwitcherTest#testReset;android.widget.cts.TimePickerTest#testAccessIs24HourView;android.widget.cts.TextViewTest#testGetTotalPaddingTop;android.widget.cts.HorizontalScrollViewTest#testExecuteKeyEvent;android.widget.cts.AbsoluteLayoutTest#testOnLayout;android.widget.cts.ImageViewTest#testAccessScaleType;android.widget.cts.TabHostTest#testGetTabWidget;android.widget.cts.SlidingDrawerTest#testAnimateToggle;android.widget.cts.SlidingDrawerTest#testLockAndUnlock;android.widget.cts.GalleryTest#testSetGravity;android.widget.cts.ListViewTest#testOnFinishInflate;android.widget.cts.ScrollViewTest#testArrowScroll;android.widget.cts.ScrollViewTest#testComputeScrollDeltaToGetChildRectOnScreen;android.widget.cts.TabHostTest#testGetCurrentTabView;android.widget.cts.ExpandableListViewBasicTest#testExpandGroup;android.widget.cts.TextViewTest#testExtractText;android.widget.cts.RemoteViewsTest#testOnLoadClass;android.widget.cts.RadioGroupTest#testCheckLayoutParams;android.widget.cts.TextSwitcherTest#testSetText;android.widget.cts.AbsListViewTest#testGenerateLayoutParams;android.widget.cts.TabWidgetTest#testSetEnabled;android.widget.cts.AutoCompleteTextViewTest#testAccessItemClickListener;android.widget.cts.ChronometerTest#testFoo;android.widget.cts.ExpandableListViewBasicTest#testConvertionBetweenFlatAndPacked;android.widget.cts.DatePickerTest#testOnSaveInstanceState;android.widget.cts.DatePickerTest#testAndroidTestCaseSetupProperly;android.widget.cts.ImageButtonTest#testOnSetAlpha;android.widget.cts.AdapterViewTest#testAccessOnItemSelectedListener;android.widget.cts.BaseExpandableListAdapterTest#testOnGroupCollapsed;android.widget.cts.TableLayoutTest#testGenerateLayoutParams1;android.widget.cts.TableLayoutTest#testGenerateLayoutParams2;android.widget.cts.LinearLayoutTest#testLayoutVertical;android.widget.cts.CursorTreeAdapterTest#testAccessQueryProvider;android.widget.cts.TextViewTest#testSetTextKeepState1;android.widget.cts.ArrayAdapterTest#testConstructor;android.widget.cts.RemoteViewsTest#testDescribeContents;android.widget.cts.RelativeLayoutTest#testGenerateDefaultLayoutParams;android.widget.cts.SimpleExpandableListAdapterTest#testHasStableIds;android.widget.cts.TextViewTest#testOnPreDraw;android.widget.cts.ExpandableListViewBasicTest#testCollapseGroup;android.widget.cts.ExpandableListViewWithHeadersTest#testExpandOnFirstPosition;android.widget.cts.SimpleCursorAdapterTest#testSetViewText;android.widget.cts.RelativeLayoutTest#testSetIgnoreGravity;android.widget.cts.RadioGroup_LayoutParamsTest#testAndroidTestCaseSetupProperly;android.widget.cts.BaseAdapterTest#testAreAllItemsEnabled;android.widget.cts.RemoteViewsTest#testSetInt;android.widget.cts.AutoCompleteTextViewTest#testAccessDropDownAnchor;android.widget.cts.ScrollerTest#testIsFinished;android.widget.cts.TextViewTest#testAccessGravity;android.widget.cts.TableLayoutTest#testAccessColumnStretchable;android.widget.cts.ExpandableListViewTest#testSetOnGroupClickListener;android.widget.cts.AlphabetIndexerTest#testCompare;android.widget.cts.AbsListView_LayoutParamsTest#testAndroidTestCaseSetupProperly;android.widget.cts.LayoutDirectionTest#testLayoutDirectionDefaults;android.widget.cts.HeaderViewListAdapterTest#testAreAllItemsEnabled;android.widget.cts.LinearLayout_LayoutParamsTest#testDebug;android.widget.cts.CursorTreeAdapterTest#testGetCursor;android.widget.cts.ImageViewTest#testOnDraw;android.widget.cts.GalleryTest#testGetChildDrawingOrder;android.widget.cts.TextViewTest#testAccessInputExtras;android.widget.cts.LinearLayoutTest#testGetBaseline;android.widget.cts.RatingBarTest#testConstructor;android.widget.cts.RemoteViewsTest#testSetUri;android.widget.cts.SpinnerTest#testsetPromptId;android.widget.cts.RemoteViewsTest#testSetTextColor;android.widget.cts.VideoViewTest#testGetDuration;android.widget.cts.AdapterViewTest#testGetCount;android.widget.cts.MultiAutoCompleteTextView_CommaTokenizerTest#testFindTokenStart;android.widget.cts.ImageViewTest#testSetImageBitmap;android.widget.cts.HeaderViewListAdapterTest#testUnregisterDataSetObserver;android.widget.cts.PopupWindowTest#testAccessOutsideTouchable;android.widget.cts.EditTextTest#testSetEllipsize;android.widget.cts.SimpleAdapterTest#testGetCount;android.widget.cts.SpinnerTest#testSetOnItemClickListener;android.widget.cts.SlidingDrawerTest#testToggle;android.widget.cts.AbsSpinnerTest#testPointToPosition;android.widget.cts.ViewFlipperTest#testViewFlipper;android.widget.cts.TabHostTest#testDispatchKeyEvent;android.widget.cts.ExpandableListViewTest#testIsGroupExpanded;android.widget.cts.LayoutDirectionTest#testDirectionFromXml;android.widget.cts.TextViewTest#testVerifyDrawable;android.widget.cts.ProgressBarTest#testIncrementProgressBy;android.widget.cts.PopupWindowTest#testShowAsDropDown;android.widget.cts.ListViewTest#testCanAnimate;android.widget.cts.HorizontalScrollViewTest#testMeasureChild;android.widget.cts.RadioGroupTest#testClearCheck;android.widget.cts.SimpleAdapterTest#testConstructor;android.widget.cts.HorizontalScrollViewTest#testGetHorizontalFadingEdgeStrengths;android.widget.cts.CursorAdapterTest#testGetItem" />
-</TestPlan>
diff --git a/tests/plans/CTS-stable.xml b/tests/plans/CTS-stable.xml
deleted file mode 100644
index 62198f0..0000000
--- a/tests/plans/CTS-stable.xml
+++ /dev/null
@@ -1,80 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<TestPlan version="1.0">
- <Entry uri="android.aadb"/>
- <Entry uri="android.acceleration"/>
- <Entry uri="android.accessibility"/>
- <Entry uri="android.accessibilityservice"/>
- <Entry uri="android.accounts"/>
- <Entry uri="android.admin"/>
- <Entry uri="android.animation"/>
- <Entry uri="android.app" exclude="android.app.cts.ActivityManagerTest#testIsRunningInTestHarness;android.app.cts.AlertDialogTest#testAlertDialogCancelable;android.app.cts.ExpandableListActivityTest#testCallback" />
- <Entry uri="android.bionic"/>
- <Entry uri="android.bluetooth"/>
- <Entry uri="android.calendarcommon"/>
- <Entry uri="android.content"/>
- <Entry uri="android.core.tests.libcore.package.com"/>
- <Entry uri="android.core.tests.libcore.package.conscrypt"/>
- <Entry uri="android.core.tests.libcore.package.dalvik"/>
- <Entry uri="android.core.tests.libcore.package.libcore"/>
- <Entry uri="android.core.tests.libcore.package.org"/>
- <Entry uri="android.core.tests.libcore.package.sun"/>
- <Entry uri="android.core.tests.libcore.package.tests"/>
- <Entry uri="android.core.vm-tests-tf"/>
- <Entry uri="android.database"/>
- <Entry uri="android.display"/>
- <Entry uri="android.dpi"/>
- <Entry uri="android.dpi2"/>
- <Entry uri="android.dreams"/>
- <Entry uri="android.drm"/>
- <Entry uri="android.effect"/>
- <Entry uri="android.gesture"/>
- <Entry uri="android.graphics"/>
- <Entry uri="android.graphics2"/>
- <Entry uri="android.hardware" exclude="android.hardware.cts.SensorIntegrationTests#testAccelerometerDoesNotStopGyroscope;android.hardware.cts.SensorIntegrationTests#testsAccelerometerDoesnNotStopMagnetometer;android.hardware.cts.SensorIntegrationTests#testAndroidTestCaseSetupProperly;android.hardware.cts.SensorIntegrationTests#testBatchAndFlush;android.hardware.cts.SensorIntegrationTests#testGyroscopeDoesNotStopAccelerometer;android.hardware.cts.SensorIntegrationTests#testGyroscopeDoesNotStopMagnetometer;android.hardware.cts.SensorIntegrationTests#testMagnetometerDoesNotStopAccelerometer;android.hardware.cts.SensorIntegrationTests#testMagnetometerDoesNotStopGyroscope;android.hardware.cts.SensorMagneticFieldTest#testBatchingStoppingOtherClients;android.hardware.cts.SensorMagneticFieldTest#testBatchingStoppingOtherClientsBatching;android.hardware.cts.SensorMagneticFieldTest#testFrequencyAccuracy;android.hardware.cts.SensorMagneticFieldTest#testOneClientSeveralThreads;android.hardware.cts.SensorMagneticFieldTest#testOneClientSeveralThreadsBatching;android.hardware.cts.SensorMagneticFieldTest#testStandardDeviationWhileStatic;android.hardware.cts.SensorMagneticFieldTest#testStoppingOtherClients;android.hardware.cts.SensorMagneticFieldTest#testStoppingOtherClientsBatching;android.hardware.cts.SensorAccelerometerTest#testBatchingStoppingOtherClients;android.hardware.cts.SensorAccelerometerTest#testBatchingStoppingOtherClientsBatching;android.hardware.cts.SensorAccelerometerTest#testFrequencyAccuracy;android.hardware.cts.SensorAccelerometerTest#testOneClientSeveralThreads;android.hardware.cts.SensorAccelerometerTest#testOneClientSeveralThreadsBatching;android.hardware.cts.SensorGyroscopeTest#testBatchingStoppingOtherClients;android.hardware.cts.SensorGyroscopeTest#testBatchingStoppingOtherClientsBatching;android.hardware.cts.SensorGyroscopeTest#testFrequencyAccuracy;android.hardware.cts.SensorGyroscopeTest#testOneClientSeveralThreads;android.hardware.cts.SensorGyroscopeTest#testOneClientSeveralThreadsBatching;android.hardware.cts.SensorGyroscopeTest#testStandardDeviationWhilStatic;android.hardware.cts.SensorGyroscopeTest#testStoppingOtherClients;android.hardware.cts.SensorGyroscopeTest#testStoppingOtherClientsBatching;android.hardware.cts.SensorAccelerometerTest#testStandardDeviationWhileStatic;android.hardware.cts.SensorAccelerometerTest#testStoppingOtherClients;android.hardware.cts.SensorAccelerometerTest#testStoppingOtherClientsBatching;android.hardware.camera2.cts.CameraDeviceTest#testCameraDeviceRepeatingRequest;android.hardware.camera2.cts.ImageReaderTest#testImageReaderFromCameraJpeg;android.hardware.cts.CameraGLTest#testCameraToSurfaceTextureMetadata;android.hardware.cts.CameraTest#testImmediateZoom;android.hardware.cts.CameraTest#testPreviewCallback;android.hardware.cts.CameraTest#testSmoothZoom;android.hardware.cts.CameraTest#testVideoSnapshot;android.hardware.cts.CameraGLTest#testSetPreviewTextureBothCallbacks;android.hardware.cts.CameraGLTest#testSetPreviewTexturePreviewCallback" />
- <Entry uri="android.jni"/>
- <Entry uri="android.keystore"/>
- <Entry uri="android.location"/>
- <Entry uri="android.media" exclude="android.media.cts.StreamingMediaPlayerTest#testHLS;android.media.cts.DecoderTest#testCodecResetsH264WithSurface" />
- <Entry uri="android.mediastress" exclude="android.mediastress.cts.NativeMediaTest#test480pPlay" />
- <Entry uri="android.nativemedia.sl"/>
- <Entry uri="android.nativemedia.xa"/>
- <Entry uri="android.nativeopengl"/>
- <Entry uri="android.ndef"/>
- <Entry uri="android.net" exclude="android.net.cts.ConnectivityManagerTest#testStartUsingNetworkFeature_enableHipri;android.net.wifi.cts.ScanResultTest#testScanResultTimeStamp;android.net.cts.DnsTest#testDnsWorks;android.net.cts.TrafficStatsTest#testTrafficStatsForLocalhost;android.net.wifi.cts.ScanResultTest#testAndroidTestCaseSetupProperly;android.net.wifi.cts.NsdManagerTest#testAndroidTestCaseSetupProperly;android.net.cts.SSLCertificateSocketFactoryTest#testCreateSocket;android.net.cts.SSLCertificateSocketFactoryTest#test_createSocket_bind;android.net.cts.SSLCertificateSocketFactoryTest#test_createSocket_simple;android.net.cts.SSLCertificateSocketFactoryTest#test_createSocket_wrapping" />
- <Entry uri="android.opengl"/>
- <Entry uri="android.openglperf"/>
- <Entry uri="android.os"/>
- <Entry uri="android.permission"/>
- <Entry uri="android.permission2"/>
- <Entry uri="android.preference"/>
- <Entry uri="android.preference2"/>
- <Entry uri="android.provider" exclude="android.provider.cts.CalendarTest#testRemindersAlarm" />
- <Entry uri="android.renderscript"/>
- <Entry uri="android.rscpp"/>
- <Entry uri="android.rsg"/>
- <Entry uri="android.sax"/>
- <Entry uri="android.security" exclude="android.security.cts.ListeningPortsTest#testNoRemotelyAccessibleListeningUdp6Ports;android.security.cts.ListeningPortsTest#testNoRemotelyAccessibleListeningUdpPorts;android.security.cts.BannedFilesTest#testNoSu;android.security.cts.BannedFilesTest#testNoSuInPath;android.security.cts.PackageSignatureTest#testPackageSignatures" />
- <Entry uri="android.speech"/>
- <Entry uri="android.telephony"/>
- <Entry uri="android.tests.appsecurity"/>
- <Entry uri="android.tests.sigtest"/>
- <Entry uri="android.text"/>
- <Entry uri="android.textureview"/>
- <Entry uri="android.theme"/>
- <Entry uri="android.uiautomator"/>
- <Entry uri="android.usb"/>
- <Entry uri="android.util"/>
- <Entry uri="android.view"/>
- <Entry uri="android.webkit" exclude="android.webkit.cts.WebViewClientTest#testDoUpdateVisitedHistory;android.webkit.cts.WebViewClientTest#testLoadPage;android.webkit.cts.WebViewClientTest#testOnFormResubmission;android.webkit.cts.WebViewClientTest#testOnReceivedError;android.webkit.cts.WebViewClientTest#testOnReceivedHttpAuthRequest;android.webkit.cts.WebViewClientTest#testOnScaleChanged;android.webkit.cts.WebViewClientTest#testOnUnhandledKeyEvent;android.webkit.cts.WebViewTest#testSetInitialScale" />
- <Entry uri="android.widget" exclude="android.widget.cts.GridViewTest#testSetNumColumns" />
- <Entry uri="com.android.cts.browserbench"/>
- <Entry uri="com.android.cts.dram"/>
- <Entry uri="com.android.cts.filesystemperf"/>
- <Entry uri="com.android.cts.jank"/>
- <Entry uri="com.android.cts.opengl"/>
- <Entry uri="com.android.cts.simplecpu"/>
- <Entry uri="com.android.cts.ui"/>
- <Entry uri="com.android.cts.uihost"/>
- <Entry uri="com.android.cts.videoperf"/>
- <Entry uri="zzz.android.monkey"/>
-</TestPlan>
diff --git a/tests/print/Android.mk b/tests/print/Android.mk
new file mode 100644
index 0000000..fea7dc0
--- /dev/null
+++ b/tests/print/Android.mk
@@ -0,0 +1,36 @@
+# 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)
+
+##################################################
+# Build the print instrument library
+##################################################
+include $(CLEAR_VARS)
+LOCAL_MODULE := CtsPrintInstrument
+LOCAL_SRC_FILES := $(call all-subdir-java-files) \
+ src/android/print/cts/IPrivilegedOperations.aidl
+LOCAL_MODULE_TAGS := optional
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_JAVA_LIBRARY)
+
+# Copy the shell script to run the print instrument Jar to the CTS out folder.
+$(CTS_TESTCASES_OUT)/$(LOCAL_MODULE).jar : $(LOCAL_BUILT_MODULE) | $(ACP)
+ $(copy-file-to-target)
+
+# Copy the built print instrument library Jar to the CTS out folder.
+$(CTS_TESTCASES_OUT)/print-instrument : $(LOCAL_PATH)/print-instrument | $(ACP)
+ $(copy-file-to-target)
+
diff --git a/tests/print/print-instrument b/tests/print/print-instrument
new file mode 100755
index 0000000..a79cb8a
--- /dev/null
+++ b/tests/print/print-instrument
@@ -0,0 +1,37 @@
+# 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.
+
+# Script to start "print-instrument" on the device
+#
+# The script sets up an alternative dalvik cache when running as
+# non-root. Jar files needs to be dexopt'd to run in Dalvik. For
+# plain jar files, this is done at first use. shell user does not
+# have write permission to default system Dalvik cache so we
+# redirect to an alternative cache.
+
+RUN_BASE=/data/local/tmp
+
+# If not running as root, use an alternative dex cache.
+if [ ${USER_ID} -ne 0 ]; then
+ tmp_cache=${RUN_BASE}/dalvik-cache
+ if [ ! -d ${tmp_cache} ]; then
+ mkdir -p ${tmp_cache}
+ fi
+ export ANDROID_DATA=${RUN_BASE}
+fi
+
+# Run print-instrument.
+export CLASSPATH=${RUN_BASE}/CtsPrintInstrument.jar
+
+exec app_process ${RUN_BASE} android.print.cts.PrintInstrument ${@}
diff --git a/tests/print/src/android/print/cts/IPrivilegedOperations.aidl b/tests/print/src/android/print/cts/IPrivilegedOperations.aidl
new file mode 100644
index 0000000..93c8c3e
--- /dev/null
+++ b/tests/print/src/android/print/cts/IPrivilegedOperations.aidl
@@ -0,0 +1,21 @@
+/*
+ * 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 android.print.cts;
+
+interface IPrivilegedOperations {
+ boolean clearApplicationUserData(String packageName);
+}
diff --git a/tests/print/src/android/print/cts/PrintInstrument.java b/tests/print/src/android/print/cts/PrintInstrument.java
new file mode 100644
index 0000000..1c568a1
--- /dev/null
+++ b/tests/print/src/android/print/cts/PrintInstrument.java
@@ -0,0 +1,279 @@
+/*
+ * 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 android.print.cts;
+
+import android.app.ActivityManagerNative;
+import android.app.IActivityManager;
+import android.app.IInstrumentationWatcher;
+import android.app.Instrumentation;
+import android.app.UiAutomationConnection;
+import android.content.ComponentName;
+import android.content.pm.IPackageDataObserver;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.util.AndroidException;
+import android.view.IWindowManager;
+
+import com.android.internal.os.BaseCommand;
+
+import java.io.PrintStream;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public final class PrintInstrument extends BaseCommand {
+
+ private static final String ARG_PRIVILEGED_OPS = "ARG_PRIVILEGED_OPS";
+
+ private IActivityManager mAm;
+
+ public static void main(String[] args) {
+ PrintInstrument instrumenter = new PrintInstrument();
+ instrumenter.run(args);
+ }
+
+ @Override
+ public void onRun() throws Exception {
+ mAm = ActivityManagerNative.getDefault();
+ if (mAm == null) {
+ System.err.println(NO_SYSTEM_ERROR_CODE);
+ throw new AndroidException("Can't connect to activity manager;"
+ + " is the system running?");
+ }
+
+ String op = nextArgRequired();
+
+ if (op.equals("instrument")) {
+ runInstrument();
+ } else {
+ showError("Error: unknown command '" + op + "'");
+ }
+ }
+
+ @Override
+ public void onShowUsage(PrintStream out) {
+ /* do nothing */
+ }
+
+ @SuppressWarnings("deprecation")
+ private void runInstrument() throws Exception {
+ String profileFile = null;
+ boolean wait = false;
+ boolean rawMode = false;
+ boolean no_window_animation = false;
+ int userId = UserHandle.USER_CURRENT;
+ Bundle args = new Bundle();
+ String argKey = null, argValue = null;
+ IWindowManager wm = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
+
+ String opt;
+ while ((opt=nextOption()) != null) {
+ if (opt.equals("-p")) {
+ profileFile = nextArgRequired();
+ } else if (opt.equals("-w")) {
+ wait = true;
+ } else if (opt.equals("-r")) {
+ rawMode = true;
+ } else if (opt.equals("-e")) {
+ argKey = nextArgRequired();
+ argValue = nextArgRequired();
+ args.putString(argKey, argValue);
+ } else if (opt.equals("--no_window_animation")
+ || opt.equals("--no-window-animation")) {
+ no_window_animation = true;
+ } else if (opt.equals("--user")) {
+ userId = parseUserArg(nextArgRequired());
+ } else {
+ System.err.println("Error: Unknown option: " + opt);
+ return;
+ }
+ }
+
+ if (userId == UserHandle.USER_ALL) {
+ System.err.println("Error: Can't start instrumentation with user 'all'");
+ return;
+ }
+
+ String cnArg = nextArgRequired();
+ ComponentName cn = ComponentName.unflattenFromString(cnArg);
+ if (cn == null) throw new IllegalArgumentException("Bad component name: " + cnArg);
+
+ InstrumentationWatcher watcher = null;
+ UiAutomationConnection connection = null;
+ if (wait) {
+ watcher = new InstrumentationWatcher();
+ watcher.setRawOutput(rawMode);
+ connection = new UiAutomationConnection();
+ }
+
+ float[] oldAnims = null;
+ if (no_window_animation) {
+ oldAnims = wm.getAnimationScales();
+ wm.setAnimationScale(0, 0.0f);
+ wm.setAnimationScale(1, 0.0f);
+ }
+
+ args.putIBinder(ARG_PRIVILEGED_OPS, new PrivilegedOperations(mAm));
+
+ if (!mAm.startInstrumentation(cn, profileFile, 0, args, watcher, connection, userId, null)) {
+ throw new AndroidException("INSTRUMENTATION_FAILED: " + cn.flattenToString());
+ }
+
+ if (watcher != null) {
+ if (!watcher.waitForFinish()) {
+ System.out.println("INSTRUMENTATION_ABORTED: System has crashed.");
+ }
+ }
+
+ if (oldAnims != null) {
+ wm.setAnimationScales(oldAnims);
+ }
+ }
+
+ private int parseUserArg(String arg) {
+ int userId;
+ if ("all".equals(arg)) {
+ userId = UserHandle.USER_ALL;
+ } else if ("current".equals(arg) || "cur".equals(arg)) {
+ userId = UserHandle.USER_CURRENT;
+ } else {
+ userId = Integer.parseInt(arg);
+ }
+ return userId;
+ }
+
+ private class InstrumentationWatcher extends IInstrumentationWatcher.Stub {
+ private boolean mFinished = false;
+ private boolean mRawMode = false;
+
+ /**
+ * Set or reset "raw mode". In "raw mode", all bundles are dumped. In "pretty mode",
+ * if a bundle includes Instrumentation.REPORT_KEY_STREAMRESULT, just print that.
+ * @param rawMode true for raw mode, false for pretty mode.
+ */
+ public void setRawOutput(boolean rawMode) {
+ mRawMode = rawMode;
+ }
+
+ @Override
+ public void instrumentationStatus(ComponentName name, int resultCode, Bundle results) {
+ synchronized (this) {
+ // pretty printer mode?
+ String pretty = null;
+ if (!mRawMode && results != null) {
+ pretty = results.getString(Instrumentation.REPORT_KEY_STREAMRESULT);
+ }
+ if (pretty != null) {
+ System.out.print(pretty);
+ } else {
+ if (results != null) {
+ for (String key : results.keySet()) {
+ System.out.println(
+ "INSTRUMENTATION_STATUS: " + key + "=" + results.get(key));
+ }
+ }
+ System.out.println("INSTRUMENTATION_STATUS_CODE: " + resultCode);
+ }
+ notifyAll();
+ }
+ }
+
+ @Override
+ public void instrumentationFinished(ComponentName name, int resultCode,
+ Bundle results) {
+ synchronized (this) {
+ // pretty printer mode?
+ String pretty = null;
+ if (!mRawMode && results != null) {
+ pretty = results.getString(Instrumentation.REPORT_KEY_STREAMRESULT);
+ }
+ if (pretty != null) {
+ System.out.println(pretty);
+ } else {
+ if (results != null) {
+ for (String key : results.keySet()) {
+ System.out.println(
+ "INSTRUMENTATION_RESULT: " + key + "=" + results.get(key));
+ }
+ }
+ System.out.println("INSTRUMENTATION_CODE: " + resultCode);
+ }
+ mFinished = true;
+ notifyAll();
+ }
+ }
+
+ public boolean waitForFinish() {
+ synchronized (this) {
+ while (!mFinished) {
+ try {
+ if (!mAm.asBinder().pingBinder()) {
+ return false;
+ }
+ wait(1000);
+ } catch (InterruptedException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ }
+ return true;
+ }
+ }
+
+ private static final class PrivilegedOperations extends IPrivilegedOperations.Stub {
+ private final IActivityManager mAm;
+
+ public PrivilegedOperations(IActivityManager am) {
+ mAm = am;
+ }
+
+ @Override
+ public boolean clearApplicationUserData(final String clearedPackageName)
+ throws RemoteException {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ final AtomicBoolean success = new AtomicBoolean();
+ final CountDownLatch completionLatch = new CountDownLatch(1);
+
+ mAm.clearApplicationUserData(clearedPackageName,
+ new IPackageDataObserver.Stub() {
+ @Override
+ public void onRemoveCompleted(String packageName, boolean succeeded) {
+ if (clearedPackageName.equals(packageName) && succeeded) {
+ success.set(true);
+ } else {
+ success.set(false);
+ }
+ completionLatch.countDown();
+ }
+ }, UserHandle.USER_CURRENT);
+
+ try {
+ completionLatch.await();
+ } catch (InterruptedException ie) {
+ /* ignore */
+ }
+
+ return success.get();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+}
diff --git a/tests/res/drawable-nodpi/vector_icon_clip_path_1_golden.png b/tests/res/drawable-nodpi/vector_icon_clip_path_1_golden.png
new file mode 100644
index 0000000..78dddaa
--- /dev/null
+++ b/tests/res/drawable-nodpi/vector_icon_clip_path_1_golden.png
Binary files differ
diff --git a/tests/res/drawable-nodpi/vector_icon_create_golden.png b/tests/res/drawable-nodpi/vector_icon_create_golden.png
new file mode 100644
index 0000000..943fce5
--- /dev/null
+++ b/tests/res/drawable-nodpi/vector_icon_create_golden.png
Binary files differ
diff --git a/tests/res/drawable-nodpi/vector_icon_delete_golden.png b/tests/res/drawable-nodpi/vector_icon_delete_golden.png
new file mode 100644
index 0000000..b46363e
--- /dev/null
+++ b/tests/res/drawable-nodpi/vector_icon_delete_golden.png
Binary files differ
diff --git a/tests/res/drawable-nodpi/vector_icon_heart_golden.png b/tests/res/drawable-nodpi/vector_icon_heart_golden.png
new file mode 100644
index 0000000..7450751
--- /dev/null
+++ b/tests/res/drawable-nodpi/vector_icon_heart_golden.png
Binary files differ
diff --git a/tests/res/drawable-nodpi/vector_icon_random_path_1_golden.png b/tests/res/drawable-nodpi/vector_icon_random_path_1_golden.png
new file mode 100644
index 0000000..91776a9
--- /dev/null
+++ b/tests/res/drawable-nodpi/vector_icon_random_path_1_golden.png
Binary files differ
diff --git a/tests/res/drawable-nodpi/vector_icon_random_path_2_golden.png b/tests/res/drawable-nodpi/vector_icon_random_path_2_golden.png
new file mode 100644
index 0000000..9af40a3
--- /dev/null
+++ b/tests/res/drawable-nodpi/vector_icon_random_path_2_golden.png
Binary files differ
diff --git a/tests/res/drawable-nodpi/vector_icon_repeated_a_1_golden.png b/tests/res/drawable-nodpi/vector_icon_repeated_a_1_golden.png
new file mode 100644
index 0000000..b3acfe7
--- /dev/null
+++ b/tests/res/drawable-nodpi/vector_icon_repeated_a_1_golden.png
Binary files differ
diff --git a/tests/res/drawable-nodpi/vector_icon_repeated_a_2_golden.png b/tests/res/drawable-nodpi/vector_icon_repeated_a_2_golden.png
new file mode 100644
index 0000000..bbc84b9
--- /dev/null
+++ b/tests/res/drawable-nodpi/vector_icon_repeated_a_2_golden.png
Binary files differ
diff --git a/tests/res/drawable-nodpi/vector_icon_repeated_cq_golden.png b/tests/res/drawable-nodpi/vector_icon_repeated_cq_golden.png
new file mode 100644
index 0000000..8d73cfd
--- /dev/null
+++ b/tests/res/drawable-nodpi/vector_icon_repeated_cq_golden.png
Binary files differ
diff --git a/tests/res/drawable-nodpi/vector_icon_repeated_st_golden.png b/tests/res/drawable-nodpi/vector_icon_repeated_st_golden.png
new file mode 100644
index 0000000..6094a9a
--- /dev/null
+++ b/tests/res/drawable-nodpi/vector_icon_repeated_st_golden.png
Binary files differ
diff --git a/tests/res/drawable-nodpi/vector_icon_schedule_golden.png b/tests/res/drawable-nodpi/vector_icon_schedule_golden.png
new file mode 100644
index 0000000..949916c
--- /dev/null
+++ b/tests/res/drawable-nodpi/vector_icon_schedule_golden.png
Binary files differ
diff --git a/tests/res/drawable-nodpi/vector_icon_settings_golden.png b/tests/res/drawable-nodpi/vector_icon_settings_golden.png
new file mode 100644
index 0000000..d12b142
--- /dev/null
+++ b/tests/res/drawable-nodpi/vector_icon_settings_golden.png
Binary files differ
diff --git a/tests/res/drawable-nodpi/vector_icon_transformation_1_golden.png b/tests/res/drawable-nodpi/vector_icon_transformation_1_golden.png
new file mode 100644
index 0000000..6f659c3
--- /dev/null
+++ b/tests/res/drawable-nodpi/vector_icon_transformation_1_golden.png
Binary files differ
diff --git a/tests/res/drawable-nodpi/vector_icon_transformation_2_golden.png b/tests/res/drawable-nodpi/vector_icon_transformation_2_golden.png
new file mode 100644
index 0000000..e0e14f3
--- /dev/null
+++ b/tests/res/drawable-nodpi/vector_icon_transformation_2_golden.png
Binary files differ
diff --git a/tests/res/drawable-nodpi/vector_icon_transformation_3_golden.png b/tests/res/drawable-nodpi/vector_icon_transformation_3_golden.png
new file mode 100644
index 0000000..b6798c2
--- /dev/null
+++ b/tests/res/drawable-nodpi/vector_icon_transformation_3_golden.png
Binary files differ
diff --git a/tests/res/drawable-nodpi/vector_icon_transformation_4_golden.png b/tests/res/drawable-nodpi/vector_icon_transformation_4_golden.png
new file mode 100644
index 0000000..a5d4d33
--- /dev/null
+++ b/tests/res/drawable-nodpi/vector_icon_transformation_4_golden.png
Binary files differ
diff --git a/tests/res/drawable-nodpi/vector_icon_transformation_5_golden.png b/tests/res/drawable-nodpi/vector_icon_transformation_5_golden.png
new file mode 100644
index 0000000..0d8ded1
--- /dev/null
+++ b/tests/res/drawable-nodpi/vector_icon_transformation_5_golden.png
Binary files differ
diff --git a/tests/res/drawable-nodpi/vector_icon_transformation_6_golden.png b/tests/res/drawable-nodpi/vector_icon_transformation_6_golden.png
new file mode 100644
index 0000000..64d07fa
--- /dev/null
+++ b/tests/res/drawable-nodpi/vector_icon_transformation_6_golden.png
Binary files differ
diff --git a/tests/res/drawable/alpha.png b/tests/res/drawable/alpha.png
new file mode 100644
index 0000000..8a88548
--- /dev/null
+++ b/tests/res/drawable/alpha.png
Binary files differ
diff --git a/tests/res/drawable/bitmapdrawable_theme.xml b/tests/res/drawable/bitmapdrawable_theme.xml
new file mode 100644
index 0000000..6df36b3
--- /dev/null
+++ b/tests/res/drawable/bitmapdrawable_theme.xml
@@ -0,0 +1,26 @@
+<?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.
+-->
+
+<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
+ android:antialias="?attr/themeBoolean"
+ android:autoMirrored="?attr/themeBoolean"
+ android:dither="?attr/themeBoolean"
+ android:filter="?attr/themeBoolean"
+ android:gravity="?attr/themeGravity"
+ android:mipMap="?attr/themeBoolean"
+ android:src="?attr/themeBitmap"
+ android:tileMode="?attr/themeTileMode" />
diff --git a/tests/res/drawable/colordrawable_theme.xml b/tests/res/drawable/colordrawable_theme.xml
new file mode 100644
index 0000000..00c6fe7
--- /dev/null
+++ b/tests/res/drawable/colordrawable_theme.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+
+<color xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?attr/themeColor" />
diff --git a/tests/res/drawable/google_logo_1.png b/tests/res/drawable/google_logo_1.png
new file mode 100644
index 0000000..6e038fc
--- /dev/null
+++ b/tests/res/drawable/google_logo_1.png
Binary files differ
diff --git a/tests/res/drawable/google_logo_2.webp b/tests/res/drawable/google_logo_2.webp
new file mode 100644
index 0000000..f92c42b
--- /dev/null
+++ b/tests/res/drawable/google_logo_2.webp
Binary files differ
diff --git a/tests/res/drawable/gradientdrawable_radius_base.xml b/tests/res/drawable/gradientdrawable_radius_base.xml
new file mode 100644
index 0000000..ecd50f8
--- /dev/null
+++ b/tests/res/drawable/gradientdrawable_radius_base.xml
@@ -0,0 +1,39 @@
+<?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.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <gradient
+ android:centerColor="#ffff0000"
+ android:endColor="#0000ffff"
+ android:gradientRadius="50%"
+ android:startColor="#ffffffff"
+ android:type="radial" />
+
+ <corners android:radius="8px" />
+
+ <padding
+ android:bottom="10px"
+ android:left="4px"
+ android:right="6px"
+ android:top="2px" />
+
+ <size
+ android:height="50px"
+ android:width="50px" />
+
+</shape>
\ No newline at end of file
diff --git a/tests/res/drawable/gradientdrawable_radius_parent.xml b/tests/res/drawable/gradientdrawable_radius_parent.xml
new file mode 100644
index 0000000..73d116a
--- /dev/null
+++ b/tests/res/drawable/gradientdrawable_radius_parent.xml
@@ -0,0 +1,39 @@
+<?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.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <gradient
+ android:centerColor="#ffff0000"
+ android:endColor="#0000ffff"
+ android:gradientRadius="50%p"
+ android:startColor="#ffffffff"
+ android:type="radial" />
+
+ <corners android:radius="8px" />
+
+ <padding
+ android:bottom="10px"
+ android:left="4px"
+ android:right="6px"
+ android:top="2px" />
+
+ <size
+ android:height="50px"
+ android:width="50px" />
+
+</shape>
\ No newline at end of file
diff --git a/tests/res/drawable/gradientdrawable_theme.xml b/tests/res/drawable/gradientdrawable_theme.xml
new file mode 100644
index 0000000..68cec62
--- /dev/null
+++ b/tests/res/drawable/gradientdrawable_theme.xml
@@ -0,0 +1,50 @@
+<?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.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <corners
+ android:bottomLeftRadius="?attr/themeDimension"
+ android:bottomRightRadius="?attr/themeDimension"
+ android:topLeftRadius="?attr/themeDimension"
+ android:topRightRadius="?attr/themeDimension" />
+
+ <gradient
+ android:angle="?attr/themeAngle"
+ android:centerColor="?attr/themeColor"
+ android:centerX="?attr/themeFloat"
+ android:centerY="?attr/themeFloat"
+ android:endColor="?attr/themeColor"
+ android:gradientRadius="?attr/themeFloat"
+ android:startColor="?attr/themeColor"
+ android:useLevel="?attr/themeBoolean" />
+
+ <padding
+ android:bottom="?attr/themeDimension"
+ android:left="?attr/themeDimension"
+ android:right="?attr/themeDimension"
+ android:top="?attr/themeDimension" />
+
+ <size
+ android:height="?attr/themeDimension"
+ android:width="?attr/themeDimension" />
+
+ <solid android:color="?attr/themeColor" />
+
+ <stroke android:color="?attr/themeColor" />
+
+</shape>
diff --git a/tests/res/drawable/layerdrawable_theme.xml b/tests/res/drawable/layerdrawable_theme.xml
new file mode 100644
index 0000000..2a678ff
--- /dev/null
+++ b/tests/res/drawable/layerdrawable_theme.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.
+-->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
+ android:autoMirrored="?attr/themeBoolean" >
+
+ <item android:drawable="@drawable/bitmapdrawable_theme"/>
+ <item>
+ <nine-patch
+ android:autoMirrored="?attr/themeBoolean"
+ android:dither="?attr/themeBoolean"
+ android:src="?attr/themeNinePatch" />
+ </item>
+
+</layer-list>
\ No newline at end of file
diff --git a/tests/res/drawable/ninepatchdrawable_theme.xml b/tests/res/drawable/ninepatchdrawable_theme.xml
new file mode 100644
index 0000000..bb031a5
--- /dev/null
+++ b/tests/res/drawable/ninepatchdrawable_theme.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+
+<nine-patch xmlns:android="http://schemas.android.com/apk/res/android"
+ android:autoMirrored="?attr/themeBoolean"
+ android:dither="?attr/themeBoolean"
+ android:src="?attr/themeNinePatch" />
diff --git a/tests/res/drawable/rippledrawable_theme.xml b/tests/res/drawable/rippledrawable_theme.xml
new file mode 100644
index 0000000..0a9d6c0
--- /dev/null
+++ b/tests/res/drawable/rippledrawable_theme.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:tint="?attr/themeColor" />
diff --git a/tests/res/drawable/vector_icon_clip_path_1.xml b/tests/res/drawable/vector_icon_clip_path_1.xml
new file mode 100644
index 0000000..5b4c4ab
--- /dev/null
+++ b/tests/res/drawable/vector_icon_clip_path_1.xml
@@ -0,0 +1,77 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <size
+ android:height="64dp"
+ android:width="64dp" />
+
+ <viewport
+ android:viewportHeight="12.25"
+ android:viewportWidth="7.30625" />
+
+ <group
+ android:pivotX="3.65"
+ android:pivotY="6.125"
+ android:rotation="-30" >
+ <path
+ android:name="clip1"
+ android:clipToPath="true"
+ android:pathData="
+ M 0, 6.125
+ l 7.3, 0
+ l 0, 12.25
+ l -7.3, 0
+ z" />
+ </group>
+ <group>
+ <path
+ android:name="one"
+ android:fill="#ff88ff"
+ android:pathData="M 1.215625,9.5l 1.9375,0.0 0.0,-6.671875 -2.109375,0.421875 0.0,-1.078125
+ l 2.09375,-0.421875 1.1874998,0.0 0.0,7.75 1.9375,0.0 0.0,1.0
+ l -5.046875,0.0 0.0,-1.0Z" />
+ </group>
+ <group
+ android:pivotX="3.65"
+ android:pivotY="6.125"
+ android:rotation="-30" >
+ <path
+ android:name="clip2"
+ android:clipToPath="true"
+ android:pathData="
+ M 0, 0
+ l 7.3, 0
+ l 0, 6.125
+ l -7.3, 0
+ z" />
+ </group>
+ <group>
+ <path
+ android:name="two"
+ android:fill="#ff88ff"
+ android:pathData="M 2.534375,9.6875l 4.140625,0.0 0.0,1.0 -5.5625,0.0 0.0,-1.0q 0.671875,-0.6875 1.828125,-1.859375
+ q 1.1718752,-1.1875 1.4687502,-1.53125 0.578125,-0.625 0.796875,-1.0625
+ q 0.234375,-0.453125 0.234375,-0.875 0.0,-0.703125 -0.5,-1.140625
+ q -0.484375,-0.4375 -1.2656252,-0.4375 -0.5625,0.0 -1.1875,0.1875
+ q -0.609375,0.1875 -1.3125,0.59375l 0.0,-1.203125q 0.71875,-0.28125 1.328125,-0.421875
+ q 0.625,-0.15625 1.140625,-0.15625 1.3593752,0.0 2.1718752,0.6875
+ q 0.8125,0.671875 0.8125,1.8125 0.0,0.53125 -0.203125,1.015625
+ q -0.203125,0.484375 -0.734375,1.140625 -0.15625,0.171875 -0.9375,0.984375
+ q -0.78125024,0.8125 -2.2187502,2.265625Z" />
+ </group>
+
+</vector>
\ No newline at end of file
diff --git a/tests/res/drawable/vector_icon_create.xml b/tests/res/drawable/vector_icon_create.xml
new file mode 100644
index 0000000..1c50b44
--- /dev/null
+++ b/tests/res/drawable/vector_icon_create.xml
@@ -0,0 +1,33 @@
+<?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.
+ */
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <size
+ android:height="64dp"
+ android:width="64dp" />
+
+ <viewport
+ android:viewportHeight="24"
+ android:viewportWidth="24" />
+
+ <path
+ android:fill="#FF000000"
+ android:pathData="M3.0,17.25L3.0,21.0l3.75,0.0L17.813995,9.936001l-3.75,-3.75L3.0,17.25zM20.707,7.0429993c0.391,-0.391 0.391,-1.023 0.0,-1.414l-2.336,-2.336c-0.391,-0.391 -1.023,-0.391 -1.414,0.0l-1.832,1.832l3.75,3.75L20.707,7.0429993z" />
+
+</vector>
\ No newline at end of file
diff --git a/tests/res/drawable/vector_icon_delete.xml b/tests/res/drawable/vector_icon_delete.xml
new file mode 100644
index 0000000..9324ac1
--- /dev/null
+++ b/tests/res/drawable/vector_icon_delete.xml
@@ -0,0 +1,33 @@
+<?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.
+ */
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <size
+ android:height="64dp"
+ android:width="64dp" />
+
+ <viewport
+ android:viewportHeight="24"
+ android:viewportWidth="24" />
+
+ <path
+ android:fill="#FF000000"
+ android:pathData="M6.0,19.0c0.0,1.104 0.896,2.0 2.0,2.0l8.0,0.0c1.104,0.0 2.0,-0.896 2.0,-2.0l0.0,-12.0L6.0,7.0L6.0,19.0zM18.0,4.0l-2.5,0.0l-1.0,-1.0l-5.0,0.0l-1.0,1.0L6.0,4.0C5.4469986,4.0 5.0,4.4469986 5.0,5.0l0.0,1.0l14.0,0.0l0.0,-1.0C19.0,4.4469986 18.552002,4.0 18.0,4.0z" />
+
+</vector>
\ No newline at end of file
diff --git a/tests/res/drawable/vector_icon_heart.xml b/tests/res/drawable/vector_icon_heart.xml
new file mode 100644
index 0000000..924cb0d
--- /dev/null
+++ b/tests/res/drawable/vector_icon_heart.xml
@@ -0,0 +1,33 @@
+<?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.
+ */
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <size
+ android:height="64dp"
+ android:width="64dp" />
+
+ <viewport
+ android:viewportHeight="24"
+ android:viewportWidth="24" />
+
+ <path
+ android:fill="#FF000000"
+ android:pathData="M16.0,5.0c-1.955,0.0 -3.83,1.268 -4.5,3.0c-0.67,-1.732 -2.547,-3.0 -4.5,-3.0C4.4570007,5.0 2.5,6.931999 2.5,9.5c0.0,3.529 3.793,6.258 9.0,11.5c5.207,-5.242 9.0,-7.971 9.0,-11.5C20.5,6.931999 18.543,5.0 16.0,5.0z" />
+
+</vector>
\ No newline at end of file
diff --git a/tests/res/drawable/vector_icon_random_path_1.xml b/tests/res/drawable/vector_icon_random_path_1.xml
new file mode 100644
index 0000000..6a1ad70
--- /dev/null
+++ b/tests/res/drawable/vector_icon_random_path_1.xml
@@ -0,0 +1,53 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <size
+ android:height="64dp"
+ android:width="64dp" />
+
+ <viewport
+ android:viewportHeight="128"
+ android:viewportWidth="128" />
+
+ <path
+ android:fill="#FF00FF00"
+ android:pathData="
+ m 0.0 0.0
+ c 58.357853 57.648304 47.260395 2.2044754 3.0 3.0
+ s 61.29288 10.748665 6.0 6.0
+ s 0.12015152 45.193787 9.0 9.0
+ s 32.573513 46.862522 12.0 12.0
+ C 52.051823 62.050003 14.197739 51.99994 15.0 15.0
+ S 58.365482 51.877937 18.0 18.0
+ S 26.692455 3.9604378 21.0 21.0
+ S 21.433464 52.17514 24.0 24.0
+ M 27.0 27.0
+ s 0.77630234 20.606667 30.0 30.0
+ M 33.0 33.0
+ S 31.06879 21.506374 36.0 36.0
+ m 39.0 39.0
+ s 11.699013 23.684185 42.0 42.0
+ m 45.0 45.0
+ S 3.7642136 38.589584 48.0 48.0
+ Q 27.203026 53.329338 51.0 51.0
+ s 39.229023 15.1781845 54.0 54.0
+ Q 47.946877 23.706299 57.0 57.0
+ S 45.63452 56.15198 60.0 60.0 "
+ android:stroke="#FF0000FF"
+ android:strokeWidth="1" />
+
+</vector>
\ No newline at end of file
diff --git a/tests/res/drawable/vector_icon_random_path_2.xml b/tests/res/drawable/vector_icon_random_path_2.xml
new file mode 100644
index 0000000..0800e0c
--- /dev/null
+++ b/tests/res/drawable/vector_icon_random_path_2.xml
@@ -0,0 +1,53 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <size
+ android:height="64dp"
+ android:width="64dp" />
+
+ <viewport
+ android:viewportHeight="128"
+ android:viewportWidth="128" />
+
+ <path
+ android:fill="#FF00FF00"
+ android:pathData="
+ m 0.0 0.0
+ q 4.7088394 36.956432 3.0 3.0
+ s 29.470345 16.754963 6.0 6.0
+ q 20.278355 7.4670525 9.0 9.0
+ S 30.897224 17.732414 12.0 12.0
+ T 15.0 15.0
+ s 63.47204 45.67142 18.0 18.0
+ T 21.0 21.0
+ S 0.3184204 24.808247 24.0 24.0
+ t 27.0 27.0
+ s 39.02275 38.261158 30.0 30.0
+ t 33.0 33.0
+ S 50.709816 16.067192 36.0 36.0
+ a 62.50911 7.7131805 51.932335 0 0 39.0 39.0
+ s 5.155651 15.749123 42.0 42.0
+ a 51.87415 40.30564 49.804344 0 0 45.0 45.0
+ S 16.16534 62.55986 48.0 48.0
+ A 39.90161 43.904438 41.642593 1 0 51.0 51.0
+ s 46.258068 32.12831 54.0 54.0
+ A 22.962704 55.05604 42.912285 1 1 57.0 57.0
+ S 36.47731 54.216763 60.0 60.0 "
+ android:stroke="#FF0000FF"
+ android:strokeWidth="1" />
+
+</vector>
\ No newline at end of file
diff --git a/tests/res/drawable/vector_icon_repeated_a_1.xml b/tests/res/drawable/vector_icon_repeated_a_1.xml
new file mode 100644
index 0000000..4d241ff
--- /dev/null
+++ b/tests/res/drawable/vector_icon_repeated_a_1.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <size
+ android:height="64dp"
+ android:width="64dp" />
+
+ <viewport
+ android:viewportHeight="128"
+ android:viewportWidth="128" />
+
+ <path
+ android:fill="#FF00FF00"
+ android:pathData="m 45.712063 19.109837
+ H 24.509682
+ a 59.3415 26.877445 22.398209 1 1 3.3506432 1.6524277
+ a 34.922844 36.72583 13.569004 0 0 24.409462 20.931156
+ a 43.47134 32.61542 52.534607 1 0 7.187504 61.509724
+ A 30.621132 41.44202 50.885685 0 0 23.235489 26.638653
+ A 7.251148 15.767811 44.704533 1 1 19.989803 21.33052
+ A 55.645584 46.20288 19.40316 0 1 32.881298 53.410923
+ c 30.649612 4.8525085 21.96682 1.3304634 17.300182 14.747681
+ a 9.375069 44.365055 57.169727 0 0 56.01326 52.59596
+ A 50.071907 37.331825 56.301754 1 0 14.676102 62.04976
+ C 36.531925 4.6217957 47.59332 54.793385 13.562473 13.753647
+ A 2.3695297 42.578487 54.250687 0 1 33.1337 41.511288
+ a 39.4827 38.844944 54.52335 1 1 13.549484 46.81581
+ c 56.943657 51.96854 27.938824 61.148792 24.168636 46.642727
+ "
+ android:stroke="#FF0000FF"
+ android:strokeWidth="1" />
+
+</vector>
\ No newline at end of file
diff --git a/tests/res/drawable/vector_icon_repeated_a_2.xml b/tests/res/drawable/vector_icon_repeated_a_2.xml
new file mode 100644
index 0000000..2b75420
--- /dev/null
+++ b/tests/res/drawable/vector_icon_repeated_a_2.xml
@@ -0,0 +1,49 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <size
+ android:height="64dp"
+ android:width="64dp" />
+
+ <viewport
+ android:viewportHeight="128"
+ android:viewportWidth="128" />
+
+ <path
+ android:fill="#FF00FF00"
+ android:pathData="m 45.712063 19.109837
+ H 24.509682
+ A 37.689938 2.3916092 17.462616 1 0 24.958328 48.110596
+ q 45.248383 30.396336 5.777027 3.4086685
+ a 30.966236 62.67946 50.532032 1 0 29.213684 60.63014
+ L 56.16764 8.342098
+ Q 61.172253 1.4613304 4.4721107 38.287144
+ A 6.284897 22.991482 47.409508 1 1 44.10166 60.998764
+ t 36.36881 55.68292
+ a 51.938667 35.22107 22.272938 1 1 28.572739 60.848858
+ A 19.610851 11.569599 51.407906 1 1 56.82705 24.386292
+ T 36.918854 59.542286
+ a 33.191364 10.553429 53.047726 1 0 54.874985 7.409252
+ s 30.186714 42.154182 59.73551 35.50219
+ A 47.9379 5.776497 28.307701 1 1 3.3323975 30.113499
+ a 22.462494 28.096004 55.76455 0 0 25.58981 30.816948
+ S 43.91107 54.679676 19.540264 0.34284973
+ "
+ android:stroke="#FF0000FF"
+ android:strokeWidth="1" />
+
+</vector>
\ No newline at end of file
diff --git a/tests/res/drawable/vector_icon_repeated_cq.xml b/tests/res/drawable/vector_icon_repeated_cq.xml
new file mode 100644
index 0000000..5127eaa
--- /dev/null
+++ b/tests/res/drawable/vector_icon_repeated_cq.xml
@@ -0,0 +1,46 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <size
+ android:height="64dp"
+ android:width="64dp" />
+
+ <viewport
+ android:viewportHeight="128"
+ android:viewportWidth="128" />
+
+ <path
+ android:fill="#FF00FF00"
+ android:pathData="m 30.81895 41.37989
+ v 31.00579
+ c 24.291603 52.03364 40.6086 24.840137 29.56704 6.5204926
+ 45.133224 22.913471 33.052887 21.727486 33.369 61.60278
+ 9.647232 22.098152 48.939598 47.470215 53.653687 62.32235
+ C 2.0560722 1.4615479 7.0928993 26.005287 40.137558 36.75628
+ 11.246731 32.178127 59.367462 60.34823 57.254383 37.357815
+ 47.75605 11.424667 3.3105545 51.886635 56.63027 17.12133
+ q 28.37534 32.85535 25.85654 33.57151
+ 10.356537 51.850616 54.085087 35.653175
+ 12.530029 52.87991 17.44696 11.780586
+ Q 2.585228 51.92801 60.000664 56.79912
+ 54.18275 51.500694 9.375679 23.836113
+ 60.35329 59.026245 31.058632 35.14934
+ "
+ android:stroke="#FF0000FF"
+ android:strokeWidth="1" />
+
+</vector>
\ No newline at end of file
diff --git a/tests/res/drawable/vector_icon_repeated_st.xml b/tests/res/drawable/vector_icon_repeated_st.xml
new file mode 100644
index 0000000..bea9c66
--- /dev/null
+++ b/tests/res/drawable/vector_icon_repeated_st.xml
@@ -0,0 +1,46 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <size
+ android:height="64dp"
+ android:width="64dp" />
+
+ <viewport
+ android:viewportHeight="128"
+ android:viewportWidth="128" />
+
+ <path
+ android:fill="#FF00FF00"
+ android:pathData="m 20.20005 8.139153
+ h 10.053165
+ s 14.2943 49.612846 35.520653 54.904068
+ 50.1405 17.044182 5.470337 40.180553
+ 3.125019 34.221123 53.212563 32.862965
+ S 35.985264 35.74349 0.15337753 59.27337
+ 2.2951508 44.56783 51.089413 29.829689
+ 8.5599785 22.649555 4.3914986 28.139206
+ t 11.932453 44.041077
+ 62.629326 7.40921
+ 23.302986 54.116184
+ T 43.560753 63.370514
+ 40.156204 17.60786
+ 40.12051 60.803394
+ "
+ android:stroke="#FF0000FF"
+ android:strokeWidth="1" />
+
+</vector>
\ No newline at end of file
diff --git a/tests/res/drawable/vector_icon_schedule.xml b/tests/res/drawable/vector_icon_schedule.xml
new file mode 100644
index 0000000..26b6623
--- /dev/null
+++ b/tests/res/drawable/vector_icon_schedule.xml
@@ -0,0 +1,36 @@
+<?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.
+ */
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <size
+ android:height="64dp"
+ android:width="64dp" />
+
+ <viewport
+ android:viewportHeight="24"
+ android:viewportWidth="24" />
+
+ <path
+ android:fillOpacity="0.9"
+ android:pathData="M11.994999,2.0C6.4679985,2.0 2.0,6.4780006 2.0,12.0s4.468,10.0 9.995,10.0S22.0,17.522 22.0,12.0S17.521,2.0 11.994999,2.0zM12.0,20.0c-4.42,0.0 -8.0,-3.582 -8.0,-8.0s3.58,-8.0 8.0,-8.0s8.0,3.582 8.0,8.0S16.419998,20.0 12.0,20.0z" />
+ <path
+ android:fillOpacity="0.9"
+ android:pathData="M12.5,6.0l-1.5,0.0 0.0,7.0 5.3029995,3.1819992 0.75,-1.249999 -4.5529995,-2.7320004z" />
+
+</vector>
\ No newline at end of file
diff --git a/tests/res/drawable/vector_icon_settings.xml b/tests/res/drawable/vector_icon_settings.xml
new file mode 100644
index 0000000..dd7d206
--- /dev/null
+++ b/tests/res/drawable/vector_icon_settings.xml
@@ -0,0 +1,33 @@
+<?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.
+ */
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <size
+ android:height="64dp"
+ android:width="64dp" />
+
+ <viewport
+ android:viewportHeight="24"
+ android:viewportWidth="24" />
+
+ <path
+ android:fill="#FF000000"
+ android:pathData="M19.429,12.975998c0.042,-0.32 0.07,-0.645 0.07,-0.976s-0.029,-0.655 -0.07,-0.976l2.113,-1.654c0.188,-0.151 0.243,-0.422 0.118,-0.639l-2.0,-3.463c-0.125,-0.217 -0.386,-0.304 -0.612,-0.218l-2.49,1.004c-0.516,-0.396 -1.081,-0.731 -1.69,-0.984l-0.375,-2.648C14.456,2.1829987 14.25,2.0 14.0,2.0l-4.0,0.0C9.75,2.0 9.544,2.1829987 9.506,2.422001L9.131,5.0699997C8.521,5.322998 7.957,5.6570015 7.44,6.054001L4.952,5.0509987C4.726,4.965 4.464,5.052002 4.34,5.269001l-2.0,3.463C2.2150002,8.947998 2.27,9.219002 2.4580002,9.369999l2.112,1.653C4.528,11.344002 4.5,11.668999 4.5,12.0s0.029,0.656 0.071,0.977L2.4580002,14.630001c-0.188,0.151 -0.243,0.422 -0.118,0.639l2.0,3.463c0.125,0.217 0.386,0.304 0.612,0.218l2.489,-1.004c0.516,0.396 1.081,0.731 1.69,0.984l0.375,2.648C9.544,21.817001 9.75,22.0 10.0,22.0l4.0,0.0c0.25,0.0 0.456,-0.183 0.494,-0.422l0.375,-2.648c0.609,-0.253 1.174,-0.588 1.689,-0.984l2.49,1.004c0.226,0.086 0.487,-0.001 0.612,-0.218l2.0,-3.463c0.125,-0.217 0.07,-0.487 -0.118,-0.639L19.429,12.975998zM12.0,16.0c-2.21,0.0 -4.0,-1.791 -4.0,-4.0c0.0,-2.21 1.79,-4.0 4.0,-4.0c2.208,0.0 4.0,1.79 4.0,4.0C16.0,14.209 14.208,16.0 12.0,16.0z" />
+
+</vector>
\ No newline at end of file
diff --git a/tests/res/drawable/vector_icon_transformation_1.xml b/tests/res/drawable/vector_icon_transformation_1.xml
new file mode 100644
index 0000000..5fb665d
--- /dev/null
+++ b/tests/res/drawable/vector_icon_transformation_1.xml
@@ -0,0 +1,42 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <size
+ android:height="64dp"
+ android:width="64dp" />
+
+ <viewport
+ android:viewportHeight="500"
+ android:viewportWidth="800" />
+
+ <group
+ android:pivotX="90"
+ android:pivotY="100"
+ android:rotation="20">
+ <path
+ android:name="pie2"
+ android:pathData="M200,350 l 50,-25
+ a25,12 -30 0,1 100,-50 l 50,-25
+ a25,25 -30 0,1 100,-50 l 50,-25
+ a25,37 -30 0,1 100,-50 l 50,-25
+ a25,50 -30 0,1 100,-50 l 50,-25"
+ android:fill="#00000000"
+ android:stroke="#FF00FF00"
+ android:strokeWidth="10" />
+ </group>
+
+</vector>
diff --git a/tests/res/drawable/vector_icon_transformation_2.xml b/tests/res/drawable/vector_icon_transformation_2.xml
new file mode 100644
index 0000000..649f33f
--- /dev/null
+++ b/tests/res/drawable/vector_icon_transformation_2.xml
@@ -0,0 +1,52 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <size
+ android:height="64dp"
+ android:width="64dp" />
+
+ <viewport
+ android:viewportHeight="200"
+ android:viewportWidth="200" />
+
+ <group>
+ <path
+ android:name="background1"
+ android:pathData="M 0,0 l 100,0 l 0, 100 l -100, 0 z"
+ android:fill="#FF000000"/>
+ <path
+ android:name="background2"
+ android:pathData="M 100,100 l 100,0 l 0, 100 l -100, 0 z"
+ android:fill="#FF000000"/>
+ </group>
+ <group
+ android:pivotX="100"
+ android:pivotY="100"
+ android:rotation="90"
+ android:scaleX="0.75"
+ android:scaleY="0.5"
+ android:translateX="0.0"
+ android:translateY="100.0">
+ <path
+ android:name="twoLines"
+ android:pathData="M 100,10 v 90 M 10,100 h 90"
+ android:fill="#00000000"
+ android:stroke="#FF00FF00"
+ android:strokeWidth="10" />
+ </group>
+
+</vector>
\ No newline at end of file
diff --git a/tests/res/drawable/vector_icon_transformation_3.xml b/tests/res/drawable/vector_icon_transformation_3.xml
new file mode 100644
index 0000000..ae11124
--- /dev/null
+++ b/tests/res/drawable/vector_icon_transformation_3.xml
@@ -0,0 +1,52 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <size
+ android:height="64dp"
+ android:width="64dp" />
+
+ <viewport
+ android:viewportHeight="200"
+ android:viewportWidth="200" />
+
+ <group>
+ <path
+ android:name="background1"
+ android:pathData="M 0,0 l 100,0 l 0, 100 l -100, 0 z"
+ android:fill="#FF000000"/>
+ <path
+ android:name="background2"
+ android:pathData="M 100,100 l 100,0 l 0, 100 l -100, 0 z"
+ android:fill="#FF000000"/>
+ </group>
+ <group
+ android:pivotX="0"
+ android:pivotY="0"
+ android:rotation="90"
+ android:scaleX="0.75"
+ android:scaleY="0.5"
+ android:translateX="100.0"
+ android:translateY="100.0">
+ <path
+ android:name="twoLines"
+ android:pathData="M 100,10 v 90 M 10,100 h 90"
+ android:fill="#00000000"
+ android:stroke="#FF00FF00"
+ android:strokeWidth="10" />
+ </group>
+
+</vector>
\ No newline at end of file
diff --git a/tests/res/drawable/vector_icon_transformation_4.xml b/tests/res/drawable/vector_icon_transformation_4.xml
new file mode 100644
index 0000000..8d38cb5
--- /dev/null
+++ b/tests/res/drawable/vector_icon_transformation_4.xml
@@ -0,0 +1,72 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <size
+ android:height="64dp"
+ android:width="64dp" />
+
+ <viewport
+ android:viewportHeight="400"
+ android:viewportWidth="400" />
+
+ <group android:name="backgroundGroup" >
+ <path
+ android:name="background1"
+ android:fill="#80000000"
+ android:pathData="M 0,0 l 200,0 l 0, 200 l -200, 0 z" />
+ <path
+ android:name="background2"
+ android:fill="#80000000"
+ android:pathData="M 200,200 l 200,0 l 0, 200 l -200, 0 z" />
+ </group>
+ <group
+ android:name="translateToCenterGroup"
+ android:translateX="50.0"
+ android:translateY="90.0" >
+ <path
+ android:name="twoLines"
+ android:pathData="M 0,0 v 100 M 0,0 h 100"
+ android:stroke="#FFFF0000"
+ android:strokeWidth="20" />
+
+ <group
+ android:name="rotationGroup"
+ android:pivotX="0.0"
+ android:pivotY="0.0"
+ android:rotation="-45.0" >
+ <path
+ android:name="twoLines1"
+ android:pathData="M 0,0 v 100 M 0,0 h 100"
+ android:stroke="#FF00FF00"
+ android:strokeWidth="20" />
+
+ <group
+ android:name="translateGroup"
+ android:translateX="130.0"
+ android:translateY="160.0" >
+ <group android:name="scaleGroup" >
+ <path
+ android:name="twoLines2"
+ android:pathData="M 0,0 v 100 M 0,0 h 100"
+ android:stroke="#FF0000FF"
+ android:strokeWidth="20" />
+ </group>
+ </group>
+ </group>
+ </group>
+
+</vector>
\ No newline at end of file
diff --git a/tests/res/drawable/vector_icon_transformation_5.xml b/tests/res/drawable/vector_icon_transformation_5.xml
new file mode 100644
index 0000000..8088578
--- /dev/null
+++ b/tests/res/drawable/vector_icon_transformation_5.xml
@@ -0,0 +1,85 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <size
+ android:height="64dp"
+ android:width="64dp" />
+
+ <viewport
+ android:viewportHeight="400"
+ android:viewportWidth="400" />
+
+ <group android:name="backgroundGroup" >
+ <path
+ android:name="background1"
+ android:fill="#80000000"
+ android:pathData="M 0,0 l 200,0 l 0, 200 l -200, 0 z" />
+ <path
+ android:name="background2"
+ android:fill="#80000000"
+ android:pathData="M 200,200 l 200,0 l 0, 200 l -200, 0 z" />
+ </group>
+ <group
+ android:name="translateToCenterGroup"
+ android:translateX="50.0"
+ android:translateY="90.0" >
+ <path
+ android:name="twoLines"
+ android:pathData="M 0,0 v 150 M 0,0 h 150"
+ android:stroke="#FFFF0000"
+ android:strokeWidth="20" />
+
+ <group
+ android:name="rotationGroup"
+ android:pivotX="0.0"
+ android:pivotY="0.0"
+ android:rotation="-45.0" >
+ <path
+ android:name="twoLines1"
+ android:pathData="M 0,0 v 100 M 0,0 h 100"
+ android:stroke="#FF00FF00"
+ android:strokeWidth="20" />
+
+ <group
+ android:name="translateGroup"
+ android:translateX="130.0"
+ android:translateY="160.0" >
+ <group android:name="scaleGroup" >
+ <path
+ android:name="twoLines3"
+ android:pathData="M 0,0 v 100 M 0,0 h 100"
+ android:stroke="#FF0000FF"
+ android:strokeWidth="20" />
+ </group>
+ </group>
+
+ <group
+ android:name="translateGroupHalf"
+ android:translateX="65.0"
+ android:translateY="80.0" >
+ <group android:name="scaleGroup" >
+ <path
+ android:name="twoLines2"
+ android:pathData="M 0,0 v 100 M 0,0 h 100"
+ android:stroke="#FF0000FF"
+ android:strokeWidth="20" />
+ </group>
+ </group>
+ </group>
+ </group>
+
+</vector>
\ No newline at end of file
diff --git a/tests/res/drawable/vector_icon_transformation_6.xml b/tests/res/drawable/vector_icon_transformation_6.xml
new file mode 100644
index 0000000..f2f8881
--- /dev/null
+++ b/tests/res/drawable/vector_icon_transformation_6.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <size
+ android:height="64dp"
+ android:width="64dp" />
+
+ <viewport
+ android:viewportHeight="400"
+ android:viewportWidth="400" />
+
+ <group android:name="backgroundGroup"
+ android:alpha = "0.5" >
+ <path
+ android:name="background1"
+ android:fill="#FF000000"
+ android:pathData="M 0,0 l 200,0 l 0, 200 l -200, 0 z" />
+ <path
+ android:name="background2"
+ android:fill="#FF000000"
+ android:pathData="M 200,200 l 200,0 l 0, 200 l -200, 0 z" />
+ </group>
+ <group
+ android:name="translateToCenterGroup"
+ android:translateX="50.0"
+ android:translateY="90.0"
+ android:alpha = "0.5" >
+ <path
+ android:name="twoLines"
+ android:pathData="M 0,0 v 100 M 0,0 h 100"
+ android:stroke="#FFFF0000"
+ android:strokeWidth="20" />
+
+ <group
+ android:name="rotationGroup"
+ android:pivotX="0.0"
+ android:pivotY="0.0"
+ android:rotation="-45.0"
+ android:alpha = "0.5" >
+ <path
+ android:name="twoLines1"
+ android:pathData="M 0,0 v 100 M 0,0 h 100"
+ android:stroke="#FF00FF00"
+ android:strokeWidth="20" />
+
+ <group
+ android:name="translateGroup"
+ android:translateX="130.0"
+ android:translateY="160.0"
+ android:alpha = "0.5">
+ <group android:name="scaleGroup" >
+ <path
+ android:name="twoLines3"
+ android:pathData="M 0,0 v 100 M 0,0 h 100"
+ android:stroke="#FF0000FF"
+ android:strokeWidth="20" />
+ </group>
+ </group>
+
+ <group
+ android:name="translateGroupHalf"
+ android:translateX="65.0"
+ android:translateY="80.0"
+ android:alpha = "0.5">
+ <group android:name="scaleGroup" >
+ <path
+ android:name="twoLines2"
+ android:pathData="M 0,0 v 100 M 0,0 h 100"
+ android:stroke="#FF0000FF"
+ android:strokeWidth="20" />
+ </group>
+ </group>
+ </group>
+ </group>
+
+</vector>
\ No newline at end of file
diff --git a/tests/res/layout/inflater_layout_tags.xml b/tests/res/layout/inflater_layout_tags.xml
new file mode 100644
index 0000000..dc3eb29
--- /dev/null
+++ b/tests/res/layout/inflater_layout_tags.xml
@@ -0,0 +1,39 @@
+<?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.
+ */
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/viewlayout_root"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+
+ <tag
+ android:id="@+id/tag_viewlayout_root"
+ android:value="@string/tag1" />
+
+ <View
+ android:id="@+id/mock_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" >
+
+ <tag
+ android:id="@+id/tag_mock_view"
+ android:value="@string/tag2" />
+ </View>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/res/layout/inflater_override_theme_layout.xml b/tests/res/layout/inflater_override_theme_layout.xml
new file mode 100644
index 0000000..2d2a578
--- /dev/null
+++ b/tests/res/layout/inflater_override_theme_layout.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:theme="@style/Theme_OverrideOuter" >
+
+ <View
+ android:id="@+id/view_outer"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:theme="@style/Theme_OverrideInner" >
+
+ <View
+ android:id="@+id/view_inner"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ <View
+ android:id="@+id/view_attr"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:theme="?attr/themeOverrideAttr" />
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/tests/res/layout/surface_view_2.xml b/tests/res/layout/surface_view_2.xml
new file mode 100644
index 0000000..fe53c71
--- /dev/null
+++ b/tests/res/layout/surface_view_2.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <SurfaceView
+ android:id="@+id/surface_view"
+ android:layout_width="320dp"
+ android:layout_height="240dp"/>
+
+</LinearLayout>
diff --git a/tests/res/raw/testmp3_2.mp3 b/tests/res/raw/testmp3_2.mp3
new file mode 100644
index 0000000..6a70c69
--- /dev/null
+++ b/tests/res/raw/testmp3_2.mp3
Binary files differ
diff --git a/tests/res/values-b+kok+419+VARIANT/configVarying.xml b/tests/res/values-b+kok+419+VARIANT/configVarying.xml
new file mode 100644
index 0000000..3560279
--- /dev/null
+++ b/tests/res/values-b+kok+419+VARIANT/configVarying.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+
+<resources>
+ <item type="configVarying" name="simple">simple kok 419 VARIANT</item>
+ <bag type="configVarying" name="bag">
+ <item name="testString">bag kok 419 VARIANT</item>
+ </bag>
+</resources>
diff --git a/tests/res/values-b+kok+419/configVarying.xml b/tests/res/values-b+kok+419/configVarying.xml
new file mode 100644
index 0000000..b6f37f9
--- /dev/null
+++ b/tests/res/values-b+kok+419/configVarying.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<resources>
+ <item type="configVarying" name="simple">simple kok 419</item>
+ <bag type="configVarying" name="bag">
+ <item name="testString">bag kok 419</item>
+ </bag>
+</resources>
diff --git a/tests/res/values-b+kok+IN/configVarying.xml b/tests/res/values-b+kok+IN/configVarying.xml
new file mode 100644
index 0000000..967ea47
--- /dev/null
+++ b/tests/res/values-b+kok+IN/configVarying.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<resources>
+ <item type="configVarying" name="simple">simple kok IN</item>
+ <bag type="configVarying" name="bag">
+ <item name="testString">bag kok IN</item>
+ </bag>
+</resources>
diff --git a/tests/res/values-b+kok+Knda+419+VARIANT/configVarying.xml b/tests/res/values-b+kok+Knda+419+VARIANT/configVarying.xml
new file mode 100644
index 0000000..2b2bcea
--- /dev/null
+++ b/tests/res/values-b+kok+Knda+419+VARIANT/configVarying.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+
+<resources>
+ <item type="configVarying" name="simple">simple kok Knda 419 VARIANT</item>
+ <bag type="configVarying" name="bag">
+ <item name="testString">bag kok Knda 419 VARIANT</item>
+ </bag>
+</resources>
diff --git a/tests/res/values-b+kok+Knda+419/configVarying.xml b/tests/res/values-b+kok+Knda+419/configVarying.xml
new file mode 100644
index 0000000..cbc53b9
--- /dev/null
+++ b/tests/res/values-b+kok+Knda+419/configVarying.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<resources>
+ <item type="configVarying" name="simple">simple kok Knda 419</item>
+ <bag type="configVarying" name="bag">
+ <item name="testString">bag kok Knda 419</item>
+ </bag>
+</resources>
diff --git a/tests/res/values-b+kok+Knda/configVarying.xml b/tests/res/values-b+kok+Knda/configVarying.xml
new file mode 100644
index 0000000..f735749
--- /dev/null
+++ b/tests/res/values-b+kok+Knda/configVarying.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<resources>
+ <item type="configVarying" name="simple">simple kok Knda</item>
+ <bag type="configVarying" name="bag">
+ <item name="testString">bag kok Knda</item>
+ </bag>
+</resources>
diff --git a/tests/res/values-b+kok+VARIANT/configVarying.xml b/tests/res/values-b+kok+VARIANT/configVarying.xml
new file mode 100644
index 0000000..6743b7f
--- /dev/null
+++ b/tests/res/values-b+kok+VARIANT/configVarying.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+
+<resources>
+ <item type="configVarying" name="simple">simple kok VARIANT</item>
+ <bag type="configVarying" name="bag">
+ <item name="testString">bag kok VARIANT</item>
+ </bag>
+</resources>
diff --git a/tests/res/values-b+kok/configVarying.xml b/tests/res/values-b+kok/configVarying.xml
new file mode 100644
index 0000000..e1bafa1
--- /dev/null
+++ b/tests/res/values-b+kok/configVarying.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<resources>
+ <item type="configVarying" name="simple">simple kok</item>
+ <bag type="configVarying" name="bag">
+ <item name="testString">bag kok</item>
+ </bag>
+</resources>
diff --git a/tests/res/values/attrs.xml b/tests/res/values/attrs.xml
index 9793893..4c3d9db 100644
--- a/tests/res/values/attrs.xml
+++ b/tests/res/values/attrs.xml
@@ -125,4 +125,21 @@
<attr name="textColorHint"/>
<attr name="textColorLink"/>
</declare-styleable>
+ <!-- Integer used to uniquely identify theme overrides. -->
+ <attr name="themeType" format="integer"/>
+ <!-- Theme reference used to override parent theme. -->
+ <attr name="themeOverrideAttr" format="reference"/>
+
+ <!-- Drawable theming attributes -->
+ <attr name="themeBoolean" />
+ <attr name="themeColor" />
+ <attr name="themeFloat" />
+ <attr name="themeInteger" />
+ <attr name="themeDimension" />
+ <attr name="themeDrawable" />
+ <attr name="themeBitmap" />
+ <attr name="themeNinePatch" />
+ <attr name="themeGravity" />
+ <attr name="themeTileMode" />
+ <attr name="themeAngle" />
</resources>
diff --git a/tests/res/values/strings.xml b/tests/res/values/strings.xml
index 0ab3dd6..ebdb9b9 100644
--- a/tests/res/values/strings.xml
+++ b/tests/res/values/strings.xml
@@ -152,6 +152,8 @@
<string name="version_v3">base</string>
<string name="authenticator_label">Android CTS</string>
<string name="search_label">Android CTS</string>
+ <string name="tag1">tag 1</string>
+ <string name="tag2">tag 2</string>
<string name="button">Button</string>
<string name="holo_test">Holo Test</string>
diff --git a/tests/res/values/styles.xml b/tests/res/values/styles.xml
index f8b56fd..47238a3 100644
--- a/tests/res/values/styles.xml
+++ b/tests/res/values/styles.xml
@@ -14,7 +14,7 @@
limitations under the License.
-->
-<resources>
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
<style name="Whatever">
<item name="type1">true</item>
@@ -138,4 +138,31 @@
<item name="android:panelColorBackground">#ffffffff</item>
</style>
+ <style name="Theme_OverrideOuter">
+ <item name="themeType">1</item>
+ </style>
+
+ <style name="Theme_OverrideInner">
+ <item name="themeType">2</item>
+ <item name="themeOverrideAttr">@style/Theme_OverrideAttr</item>
+ </style>
+
+ <style name="Theme_OverrideAttr">
+ <item name="themeType">3</item>
+ </style>
+
+ <style name="Theme_ThemedDrawableTest">
+ <item name="themeBoolean">true</item>
+ <item name="themeColor">@android:color/black</item>
+ <item name="themeFloat">1.0</item>
+ <item name="themeAngle">45.0</item>
+ <item name="themeInteger">1</item>
+ <item name="themeDimension">1px</item>
+ <item name="themeDrawable">@drawable/icon_black</item>
+ <item name="themeBitmap">@drawable/icon_black</item>
+ <item name="themeNinePatch">@drawable/ninepatch_0</item>
+ <item name="themeGravity">48</item>
+ <item name="themeTileMode">2</item>
+ </style>
+
</resources>
diff --git a/tests/sample/Android.mk b/tests/sample/Android.mk
index e1a9408..5e8f571 100755
--- a/tests/sample/Android.mk
+++ b/tests/sample/Android.mk
@@ -22,9 +22,6 @@
# When built, explicitly put it in the data partition.
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-# All tests should include android.test.runner.
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil ctstestrunner
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/sample/AndroidManifest.xml b/tests/sample/AndroidManifest.xml
index ae58f0a..f07ebbe 100755
--- a/tests/sample/AndroidManifest.xml
+++ b/tests/sample/AndroidManifest.xml
@@ -31,9 +31,12 @@
<!-- self-instrumenting test package. -->
<instrumentation
- android:name="android.test.InstrumentationCtsTestRunner"
+ android:name="android.support.test.runner.AndroidJUnitRunner"
android:label="CTS sample tests"
- android:targetPackage="android.sample.cts" />
-
+ android:targetPackage="android.sample.cts" >
+ <meta-data
+ android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/src/android/hardware/camera2/cts/Camera2SurfaceViewStubActivity.java b/tests/src/android/hardware/camera2/cts/Camera2SurfaceViewStubActivity.java
new file mode 100644
index 0000000..47b4cba
--- /dev/null
+++ b/tests/src/android/hardware/camera2/cts/Camera2SurfaceViewStubActivity.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 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 android.hardware.camera2.cts;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.ConditionVariable;
+import android.util.Log;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import com.android.cts.stub.R;
+
+public class Camera2SurfaceViewStubActivity extends Activity implements SurfaceHolder.Callback {
+ private final static String TAG = "Camera2SurfaceViewStubActivity";
+ private final ConditionVariable surfaceChangedDone = new ConditionVariable();
+
+ private SurfaceView mSurfaceView;
+ private int currentWidth = 0;
+ private int currentHeight = 0;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.surface_view_2);
+ mSurfaceView = (SurfaceView) findViewById(R.id.surface_view);
+ mSurfaceView.getHolder().addCallback(this);
+ }
+
+ public SurfaceView getSurfaceView() {
+ return mSurfaceView;
+ }
+
+ public boolean waitForSurfaceSizeChanged(int timeOutMs, int expectWidth, int expectHeight) {
+ if (timeOutMs <= 0 || expectWidth <= 0 || expectHeight <= 0) {
+ throw new IllegalArgumentException(
+ String.format(
+ "timeout(%d), expectWidth(%d), and expectHeight(%d) " +
+ "should all be positive numbers",
+ timeOutMs, expectWidth, expectHeight));
+ }
+
+ int waitTimeMs = timeOutMs;
+ boolean changeSucceeded = false;
+ while (!changeSucceeded && waitTimeMs > 0) {
+ long startTimeMs = System.currentTimeMillis();
+ changeSucceeded = surfaceChangedDone.block(waitTimeMs);
+ if (!changeSucceeded) {
+ Log.e(TAG, "Wait for surface change timed out after " + timeOutMs + " ms");
+ return changeSucceeded;
+ } else {
+ // Get a surface change callback, need to check if the size is expected.
+ surfaceChangedDone.close();
+ if (currentWidth == expectWidth && currentHeight == expectHeight) {
+ return changeSucceeded;
+ }
+ // Do a further iteration surface change check as surfaceChanged could be called
+ // again.
+ changeSucceeded = false;
+ }
+ waitTimeMs -= (System.currentTimeMillis() - startTimeMs);
+ }
+
+ // Couldn't get expected surface size change.
+ return false;
+ }
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ }
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+ Log.i(TAG, "Surface Changed to: " + width + "x" + height);
+ currentWidth = width;
+ currentHeight = height;
+ surfaceChangedDone.open();
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ }
+}
diff --git a/tests/src/android/hardware/camera2/cts/common.rs b/tests/src/android/hardware/camera2/cts/common.rs
new file mode 100644
index 0000000..4c134b3
--- /dev/null
+++ b/tests/src/android/hardware/camera2/cts/common.rs
@@ -0,0 +1,45 @@
+/*
+ * Copyright 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.
+ */
+
+#ifndef ANDROID_HARDWARE_CAMERA2_CTS_COMMON_RS
+#define ANDROID_HARDWARE_CAMERA2_CTS_COMMON_RS
+
+#pragma version(1)
+#pragma rs java_package_name(android.hardware.camera2.cts)
+#pragma rs_fp_relaxed
+
+typedef uchar3 yuvx_444; // interleaved YUV. (y,u,v) per pixel. use .x/.y/.z to read
+typedef uchar3 yuvf_420; // flexible YUV (4:2:0). use rsGetElementAtYuv to read.
+
+#define convert_yuvx_444 convert_uchar3
+#define convert_yuvf_420 __error_cant_output_flexible_yuv__
+
+#define rsGetElementAt_yuvx_444 rsGetElementAt_uchar3
+#define rsGetElementAt_yuvf_420 __error_cant_output_flexible_yuv__
+
+#define RS_KERNEL __attribute__((kernel))
+
+#ifndef LOG_DEBUG
+#define LOG_DEBUG 0
+#endif
+
+#if LOG_DEBUG
+#define LOGD(string, expr) rsDebug((string), (expr))
+#else
+#define LOGD(string, expr) if (0) rsDebug((string), (expr))
+#endif
+
+#endif // header guard
diff --git a/tests/src/android/hardware/camera2/cts/crop_yuvf_420_to_yuvx_444.rs b/tests/src/android/hardware/camera2/cts/crop_yuvf_420_to_yuvx_444.rs
new file mode 100644
index 0000000..f930f58
--- /dev/null
+++ b/tests/src/android/hardware/camera2/cts/crop_yuvf_420_to_yuvx_444.rs
@@ -0,0 +1,37 @@
+/*
+ * Copyright 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.
+ */
+
+#include "common.rs"
+
+// Must be YUV 420 888 (flexible YUV)
+rs_allocation mInput;
+
+// Input globals
+uint32_t src_x;
+uint32_t src_y;
+
+// Crop each pixel from mInput
+yuvx_444 RS_KERNEL crop(uint32_t x, uint32_t y) {
+
+ uchar py = rsGetElementAtYuv_uchar_Y(mInput, x + src_x, y + src_y);
+ uchar pu = rsGetElementAtYuv_uchar_U(mInput, x + src_x, y + src_y);
+ uchar pv = rsGetElementAtYuv_uchar_V(mInput, x + src_x, y + src_y);
+
+ yuvx_444 yuv = { py, pu, pv };
+
+ return yuv;
+}
+
diff --git a/tests/src/android/hardware/camera2/cts/means_yuvx_444_1d_to_single.rs b/tests/src/android/hardware/camera2/cts/means_yuvx_444_1d_to_single.rs
new file mode 100644
index 0000000..052052f
--- /dev/null
+++ b/tests/src/android/hardware/camera2/cts/means_yuvx_444_1d_to_single.rs
@@ -0,0 +1,54 @@
+/*
+ * Copyright 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.
+ */
+
+#include "common.rs"
+
+// #define LOG_DEBUG 1
+
+// Must be yuvx_444 (interleaved yuv)
+rs_allocation mInput;
+
+uint32_t width;
+float inv_width; // must be set to 1/w
+
+// Average 1D array -> Single element
+// Input: mInput must be yuvx_444
+yuvx_444 RS_KERNEL means_yuvx_444(void) {
+
+ uint3 sum = { 0, 0, 0 };
+
+ LOGD("width", width);
+
+ for (uint32_t i = 0; i < width; ++i) {
+ yuvx_444 elem = rsGetElementAt_yuvx_444(mInput, i);
+
+ LOGD("elem", elem);
+ LOGD("i", i);
+
+ sum.x += elem.x;
+ sum.y += elem.y;
+ sum.z += elem.z;
+ }
+
+ sum.x *= inv_width; // multiply by 1/w
+ sum.y *= inv_width; // multiply by 1/w
+ sum.z *= inv_width; // multiply by 1/w
+
+ yuvx_444 avg = convert_yuvx_444(sum);
+
+ return avg;
+}
+
diff --git a/tests/src/android/hardware/camera2/cts/means_yuvx_444_2d_to_1d.rs b/tests/src/android/hardware/camera2/cts/means_yuvx_444_2d_to_1d.rs
new file mode 100644
index 0000000..7dc4e0d
--- /dev/null
+++ b/tests/src/android/hardware/camera2/cts/means_yuvx_444_2d_to_1d.rs
@@ -0,0 +1,86 @@
+/*
+ * Copyright 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.
+ */
+
+#include "common.rs"
+
+// #define LOG_DEBUG 1
+
+// Must be YUV 420 888 (flexible YUV)
+rs_allocation mInput;
+
+uint32_t width;
+float inv_width; // 1/w
+uint32_t src_x; // x-offset from mInput
+uint32_t src_y; // y-offset from mInput
+
+// Average a 2D array -> 1D array (by each row)
+// Input: mInput must be yuvf_420
+yuvx_444 RS_KERNEL means_yuvf_420(uint32_t x) {
+
+ LOGD("x", x);
+
+ uint3 sum = { 0, 0, 0 };
+
+ for (uint32_t i = 0; i < width; ++i) {
+ uchar py = rsGetElementAtYuv_uchar_Y(mInput, src_x + i, src_y + x);
+ uchar pu = rsGetElementAtYuv_uchar_U(mInput, src_x + i, src_y + x);
+ uchar pv = rsGetElementAtYuv_uchar_V(mInput, src_x + i, src_y + x);
+
+ yuvx_444 elem = { py, pu, pv };
+
+ LOGD("elem", elem);
+
+ sum.x += elem.x;
+ sum.y += elem.y;
+ sum.z += elem.z;
+ }
+
+ sum.x *= inv_width; // multiply by 1/w
+ sum.y *= inv_width; // multiply by 1/w
+ sum.z *= inv_width; // multiply by 1/w
+
+ yuvx_444 avg = convert_yuvx_444(sum);
+
+ return avg;
+}
+
+// Average a 2D array -> 1D array (by each row)
+// Input: mInput must be yuvx_444
+yuvx_444 RS_KERNEL means_yuvx_444(uint32_t x) {
+
+ LOGD("x", x);
+
+ uint3 sum = { 0, 0, 0 };
+
+ for (uint32_t i = 0; i < width; ++i) {
+ yuvx_444 elem = rsGetElementAt_yuvx_444(mInput, src_x + i, src_y + x);
+
+ LOGD("elem", elem);
+
+ sum.x += elem.x;
+ sum.y += elem.y;
+ sum.z += elem.z;
+ }
+
+ sum.x *= inv_width; // multiply by 1/w
+ sum.y *= inv_width; // multiply by 1/w
+ sum.z *= inv_width; // multiply by 1/w
+
+ yuvx_444 avg = convert_yuvx_444(sum);
+
+ return avg;
+}
+
diff --git a/tests/src/android/renderscript/cts/AtomicTest.rs b/tests/src/android/renderscript/cts/AtomicTest.rs
new file mode 100644
index 0000000..2a78ffd
--- /dev/null
+++ b/tests/src/android/renderscript/cts/AtomicTest.rs
@@ -0,0 +1,130 @@
+/*
+ * 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.
+ */
+
+#include "shared.rsh"
+#include "rs_atomic.rsh"
+
+volatile int32_t gISum;
+volatile uint32_t gUSum;
+
+
+void __attribute__((kernel)) test_Inc(int32_t v) {
+ rsAtomicInc(&gISum);
+}
+void __attribute__((kernel)) test_uInc(uint32_t v) {
+ rsAtomicInc(&gUSum);
+}
+
+void __attribute__((kernel)) test_Dec(int32_t v) {
+ rsAtomicDec(&gISum);
+}
+void __attribute__((kernel)) test_uDec(uint32_t v) {
+ rsAtomicDec(&gUSum);
+}
+
+#define TEST_OP(op) \
+void __attribute__((kernel)) test_##op(int32_t v) { \
+ rsAtomic##op(&gISum, v); \
+} \
+void __attribute__((kernel)) test_u##op(uint32_t v) { \
+ rsAtomic##op(&gUSum, v); \
+}
+
+TEST_OP(Add)
+TEST_OP(Sub)
+TEST_OP(And)
+TEST_OP(Or)
+TEST_OP(Xor)
+TEST_OP(Min)
+TEST_OP(Max)
+
+// the folowing functions copy the global to an allocation
+// to allow the calling code to read it back.
+void getValueS(rs_allocation v) {
+ rsSetElementAt_int(v, gISum, 0);
+}
+
+void getValueU(rs_allocation v) {
+ rsSetElementAt_uint(v, gUSum, 0);
+}
+
+void computeReference_Min(rs_allocation a, rs_allocation result) {
+ uint32_t dimX = rsAllocationGetDimX(a);
+ uint32_t dimY = rsAllocationGetDimY(a);
+ for (uint32_t y = 0; y < dimY; y++) {
+ for (uint32_t x = 0; x < dimX; x++) {
+ int v = rsGetElementAt_int(a, x, y);
+ gISum = min(gISum, v);
+ }
+ }
+ rsSetElementAt_int(result, gISum, 0);
+}
+
+void computeReference_uMin(rs_allocation a, rs_allocation result) {
+ uint32_t dimX = rsAllocationGetDimX(a);
+ uint32_t dimY = rsAllocationGetDimY(a);
+ for (uint32_t y = 0; y < dimY; y++) {
+ for (uint32_t x = 0; x < dimX; x++) {
+ uint v = rsGetElementAt_uint(a, x, y);
+ gUSum = min(gUSum, v);
+ }
+ }
+ rsSetElementAt_uint(result, gUSum, 0);
+}
+
+void computeReference_Max(rs_allocation a, rs_allocation result) {
+ uint32_t dimX = rsAllocationGetDimX(a);
+ uint32_t dimY = rsAllocationGetDimY(a);
+ for (uint32_t y = 0; y < dimY; y++) {
+ for (uint32_t x = 0; x < dimX; x++) {
+ int v = rsGetElementAt_int(a, x, y);
+ gISum = max(gISum, v);
+ }
+ }
+ rsSetElementAt_int(result, gISum, 0);
+}
+
+void computeReference_uMax(rs_allocation a, rs_allocation result) {
+ uint32_t dimX = rsAllocationGetDimX(a);
+ uint32_t dimY = rsAllocationGetDimY(a);
+ for (uint32_t y = 0; y < dimY; y++) {
+ for (uint32_t x = 0; x < dimX; x++) {
+ uint v = rsGetElementAt_uint(a, x, y);
+ gUSum = max(gUSum, v);
+ }
+ }
+ rsSetElementAt_uint(result, gUSum, 0);
+}
+
+
+void __attribute__((kernel)) test_Cas(int32_t v) {
+ int tmp = gISum;
+ int retryCount = 10000;
+ while (retryCount && (rsAtomicCas(&gISum, tmp, tmp + 1) != tmp)) {
+ retryCount--;
+ tmp = gISum;
+ }
+}
+void __attribute__((kernel)) test_uCas(uint32_t v) {
+ uint tmp = gUSum;
+ int retryCount = 10000;
+ while (retryCount && (rsAtomicCas(&gUSum, tmp, tmp + 1) != tmp)) {
+ retryCount--;
+ tmp = gUSum;
+ }
+}
+
+
diff --git a/tests/src/android/renderscript/cts/TestClamp.rs b/tests/src/android/renderscript/cts/TestClamp.rs
index bc0b379..9bb5c87 100644
--- a/tests/src/android/renderscript/cts/TestClamp.rs
+++ b/tests/src/android/renderscript/cts/TestClamp.rs
@@ -208,6 +208,54 @@
return clamp(inValue, inMinValue, inMaxValue);
}
+long __attribute__((kernel)) testClampLongLongLongLong(long inValue, unsigned int x) {
+ long inMinValue = rsGetElementAt_long(gAllocInMinValue, x);
+ long inMaxValue = rsGetElementAt_long(gAllocInMaxValue, x);
+ return clamp(inValue, inMinValue, inMaxValue);
+}
+
+long2 __attribute__((kernel)) testClampLong2Long2Long2Long2(long2 inValue, unsigned int x) {
+ long2 inMinValue = rsGetElementAt_long2(gAllocInMinValue, x);
+ long2 inMaxValue = rsGetElementAt_long2(gAllocInMaxValue, x);
+ return clamp(inValue, inMinValue, inMaxValue);
+}
+
+long3 __attribute__((kernel)) testClampLong3Long3Long3Long3(long3 inValue, unsigned int x) {
+ long3 inMinValue = rsGetElementAt_long3(gAllocInMinValue, x);
+ long3 inMaxValue = rsGetElementAt_long3(gAllocInMaxValue, x);
+ return clamp(inValue, inMinValue, inMaxValue);
+}
+
+long4 __attribute__((kernel)) testClampLong4Long4Long4Long4(long4 inValue, unsigned int x) {
+ long4 inMinValue = rsGetElementAt_long4(gAllocInMinValue, x);
+ long4 inMaxValue = rsGetElementAt_long4(gAllocInMaxValue, x);
+ return clamp(inValue, inMinValue, inMaxValue);
+}
+
+ulong __attribute__((kernel)) testClampUlongUlongUlongUlong(ulong inValue, unsigned int x) {
+ ulong inMinValue = rsGetElementAt_ulong(gAllocInMinValue, x);
+ ulong inMaxValue = rsGetElementAt_ulong(gAllocInMaxValue, x);
+ return clamp(inValue, inMinValue, inMaxValue);
+}
+
+ulong2 __attribute__((kernel)) testClampUlong2Ulong2Ulong2Ulong2(ulong2 inValue, unsigned int x) {
+ ulong2 inMinValue = rsGetElementAt_ulong2(gAllocInMinValue, x);
+ ulong2 inMaxValue = rsGetElementAt_ulong2(gAllocInMaxValue, x);
+ return clamp(inValue, inMinValue, inMaxValue);
+}
+
+ulong3 __attribute__((kernel)) testClampUlong3Ulong3Ulong3Ulong3(ulong3 inValue, unsigned int x) {
+ ulong3 inMinValue = rsGetElementAt_ulong3(gAllocInMinValue, x);
+ ulong3 inMaxValue = rsGetElementAt_ulong3(gAllocInMaxValue, x);
+ return clamp(inValue, inMinValue, inMaxValue);
+}
+
+ulong4 __attribute__((kernel)) testClampUlong4Ulong4Ulong4Ulong4(ulong4 inValue, unsigned int x) {
+ ulong4 inMinValue = rsGetElementAt_ulong4(gAllocInMinValue, x);
+ ulong4 inMaxValue = rsGetElementAt_ulong4(gAllocInMaxValue, x);
+ return clamp(inValue, inMinValue, inMaxValue);
+}
+
char2 __attribute__((kernel)) testClampChar2CharCharChar2(char2 inValue, unsigned int x) {
char inMinValue = rsGetElementAt_char(gAllocInMinValue, x);
char inMaxValue = rsGetElementAt_char(gAllocInMaxValue, x);
@@ -315,3 +363,39 @@
uint inMaxValue = rsGetElementAt_uint(gAllocInMaxValue, x);
return clamp(inValue, inMinValue, inMaxValue);
}
+
+long2 __attribute__((kernel)) testClampLong2LongLongLong2(long2 inValue, unsigned int x) {
+ long inMinValue = rsGetElementAt_long(gAllocInMinValue, x);
+ long inMaxValue = rsGetElementAt_long(gAllocInMaxValue, x);
+ return clamp(inValue, inMinValue, inMaxValue);
+}
+
+long3 __attribute__((kernel)) testClampLong3LongLongLong3(long3 inValue, unsigned int x) {
+ long inMinValue = rsGetElementAt_long(gAllocInMinValue, x);
+ long inMaxValue = rsGetElementAt_long(gAllocInMaxValue, x);
+ return clamp(inValue, inMinValue, inMaxValue);
+}
+
+long4 __attribute__((kernel)) testClampLong4LongLongLong4(long4 inValue, unsigned int x) {
+ long inMinValue = rsGetElementAt_long(gAllocInMinValue, x);
+ long inMaxValue = rsGetElementAt_long(gAllocInMaxValue, x);
+ return clamp(inValue, inMinValue, inMaxValue);
+}
+
+ulong2 __attribute__((kernel)) testClampUlong2UlongUlongUlong2(ulong2 inValue, unsigned int x) {
+ ulong inMinValue = rsGetElementAt_ulong(gAllocInMinValue, x);
+ ulong inMaxValue = rsGetElementAt_ulong(gAllocInMaxValue, x);
+ return clamp(inValue, inMinValue, inMaxValue);
+}
+
+ulong3 __attribute__((kernel)) testClampUlong3UlongUlongUlong3(ulong3 inValue, unsigned int x) {
+ ulong inMinValue = rsGetElementAt_ulong(gAllocInMinValue, x);
+ ulong inMaxValue = rsGetElementAt_ulong(gAllocInMaxValue, x);
+ return clamp(inValue, inMinValue, inMaxValue);
+}
+
+ulong4 __attribute__((kernel)) testClampUlong4UlongUlongUlong4(ulong4 inValue, unsigned int x) {
+ ulong inMinValue = rsGetElementAt_ulong(gAllocInMinValue, x);
+ ulong inMaxValue = rsGetElementAt_ulong(gAllocInMaxValue, x);
+ return clamp(inValue, inMinValue, inMaxValue);
+}
diff --git a/tests/src/android/renderscript/cts/TestConvert.rs b/tests/src/android/renderscript/cts/TestConvert.rs
index 85f8c94..7c94d5e 100644
--- a/tests/src/android/renderscript/cts/TestConvert.rs
+++ b/tests/src/android/renderscript/cts/TestConvert.rs
@@ -607,3 +607,615 @@
uint4 __attribute__((kernel)) testConvertUint4Uint4Uint4(uint4 inV) {
return convert_uint4(inV);
}
+
+double2 __attribute__((kernel)) testConvertDouble2Double2Double2(double2 inV) {
+ return convert_double2(inV);
+}
+
+double3 __attribute__((kernel)) testConvertDouble3Double3Double3(double3 inV) {
+ return convert_double3(inV);
+}
+
+double4 __attribute__((kernel)) testConvertDouble4Double4Double4(double4 inV) {
+ return convert_double4(inV);
+}
+
+double2 __attribute__((kernel)) testConvertDouble2Long2Double2(long2 inV) {
+ return convert_double2(inV);
+}
+
+double3 __attribute__((kernel)) testConvertDouble3Long3Double3(long3 inV) {
+ return convert_double3(inV);
+}
+
+double4 __attribute__((kernel)) testConvertDouble4Long4Double4(long4 inV) {
+ return convert_double4(inV);
+}
+
+double2 __attribute__((kernel)) testConvertDouble2Ulong2Double2(ulong2 inV) {
+ return convert_double2(inV);
+}
+
+double3 __attribute__((kernel)) testConvertDouble3Ulong3Double3(ulong3 inV) {
+ return convert_double3(inV);
+}
+
+double4 __attribute__((kernel)) testConvertDouble4Ulong4Double4(ulong4 inV) {
+ return convert_double4(inV);
+}
+
+long2 __attribute__((kernel)) testConvertLong2Double2Long2(double2 inV) {
+ return convert_long2(inV);
+}
+
+long3 __attribute__((kernel)) testConvertLong3Double3Long3(double3 inV) {
+ return convert_long3(inV);
+}
+
+long4 __attribute__((kernel)) testConvertLong4Double4Long4(double4 inV) {
+ return convert_long4(inV);
+}
+
+long2 __attribute__((kernel)) testConvertLong2Long2Long2(long2 inV) {
+ return convert_long2(inV);
+}
+
+long3 __attribute__((kernel)) testConvertLong3Long3Long3(long3 inV) {
+ return convert_long3(inV);
+}
+
+long4 __attribute__((kernel)) testConvertLong4Long4Long4(long4 inV) {
+ return convert_long4(inV);
+}
+
+long2 __attribute__((kernel)) testConvertLong2Ulong2Long2(ulong2 inV) {
+ return convert_long2(inV);
+}
+
+long3 __attribute__((kernel)) testConvertLong3Ulong3Long3(ulong3 inV) {
+ return convert_long3(inV);
+}
+
+long4 __attribute__((kernel)) testConvertLong4Ulong4Long4(ulong4 inV) {
+ return convert_long4(inV);
+}
+
+ulong2 __attribute__((kernel)) testConvertUlong2Double2Ulong2(double2 inV) {
+ return convert_ulong2(inV);
+}
+
+ulong3 __attribute__((kernel)) testConvertUlong3Double3Ulong3(double3 inV) {
+ return convert_ulong3(inV);
+}
+
+ulong4 __attribute__((kernel)) testConvertUlong4Double4Ulong4(double4 inV) {
+ return convert_ulong4(inV);
+}
+
+ulong2 __attribute__((kernel)) testConvertUlong2Long2Ulong2(long2 inV) {
+ return convert_ulong2(inV);
+}
+
+ulong3 __attribute__((kernel)) testConvertUlong3Long3Ulong3(long3 inV) {
+ return convert_ulong3(inV);
+}
+
+ulong4 __attribute__((kernel)) testConvertUlong4Long4Ulong4(long4 inV) {
+ return convert_ulong4(inV);
+}
+
+ulong2 __attribute__((kernel)) testConvertUlong2Ulong2Ulong2(ulong2 inV) {
+ return convert_ulong2(inV);
+}
+
+ulong3 __attribute__((kernel)) testConvertUlong3Ulong3Ulong3(ulong3 inV) {
+ return convert_ulong3(inV);
+}
+
+ulong4 __attribute__((kernel)) testConvertUlong4Ulong4Ulong4(ulong4 inV) {
+ return convert_ulong4(inV);
+}
+
+float2 __attribute__((kernel)) testConvertFloat2Double2Float2(double2 inV) {
+ return convert_float2(inV);
+}
+
+float3 __attribute__((kernel)) testConvertFloat3Double3Float3(double3 inV) {
+ return convert_float3(inV);
+}
+
+float4 __attribute__((kernel)) testConvertFloat4Double4Float4(double4 inV) {
+ return convert_float4(inV);
+}
+
+float2 __attribute__((kernel)) testConvertFloat2Long2Float2(long2 inV) {
+ return convert_float2(inV);
+}
+
+float3 __attribute__((kernel)) testConvertFloat3Long3Float3(long3 inV) {
+ return convert_float3(inV);
+}
+
+float4 __attribute__((kernel)) testConvertFloat4Long4Float4(long4 inV) {
+ return convert_float4(inV);
+}
+
+float2 __attribute__((kernel)) testConvertFloat2Ulong2Float2(ulong2 inV) {
+ return convert_float2(inV);
+}
+
+float3 __attribute__((kernel)) testConvertFloat3Ulong3Float3(ulong3 inV) {
+ return convert_float3(inV);
+}
+
+float4 __attribute__((kernel)) testConvertFloat4Ulong4Float4(ulong4 inV) {
+ return convert_float4(inV);
+}
+
+char2 __attribute__((kernel)) testConvertChar2Double2Char2(double2 inV) {
+ return convert_char2(inV);
+}
+
+char3 __attribute__((kernel)) testConvertChar3Double3Char3(double3 inV) {
+ return convert_char3(inV);
+}
+
+char4 __attribute__((kernel)) testConvertChar4Double4Char4(double4 inV) {
+ return convert_char4(inV);
+}
+
+char2 __attribute__((kernel)) testConvertChar2Long2Char2(long2 inV) {
+ return convert_char2(inV);
+}
+
+char3 __attribute__((kernel)) testConvertChar3Long3Char3(long3 inV) {
+ return convert_char3(inV);
+}
+
+char4 __attribute__((kernel)) testConvertChar4Long4Char4(long4 inV) {
+ return convert_char4(inV);
+}
+
+char2 __attribute__((kernel)) testConvertChar2Ulong2Char2(ulong2 inV) {
+ return convert_char2(inV);
+}
+
+char3 __attribute__((kernel)) testConvertChar3Ulong3Char3(ulong3 inV) {
+ return convert_char3(inV);
+}
+
+char4 __attribute__((kernel)) testConvertChar4Ulong4Char4(ulong4 inV) {
+ return convert_char4(inV);
+}
+
+uchar2 __attribute__((kernel)) testConvertUchar2Double2Uchar2(double2 inV) {
+ return convert_uchar2(inV);
+}
+
+uchar3 __attribute__((kernel)) testConvertUchar3Double3Uchar3(double3 inV) {
+ return convert_uchar3(inV);
+}
+
+uchar4 __attribute__((kernel)) testConvertUchar4Double4Uchar4(double4 inV) {
+ return convert_uchar4(inV);
+}
+
+uchar2 __attribute__((kernel)) testConvertUchar2Long2Uchar2(long2 inV) {
+ return convert_uchar2(inV);
+}
+
+uchar3 __attribute__((kernel)) testConvertUchar3Long3Uchar3(long3 inV) {
+ return convert_uchar3(inV);
+}
+
+uchar4 __attribute__((kernel)) testConvertUchar4Long4Uchar4(long4 inV) {
+ return convert_uchar4(inV);
+}
+
+uchar2 __attribute__((kernel)) testConvertUchar2Ulong2Uchar2(ulong2 inV) {
+ return convert_uchar2(inV);
+}
+
+uchar3 __attribute__((kernel)) testConvertUchar3Ulong3Uchar3(ulong3 inV) {
+ return convert_uchar3(inV);
+}
+
+uchar4 __attribute__((kernel)) testConvertUchar4Ulong4Uchar4(ulong4 inV) {
+ return convert_uchar4(inV);
+}
+
+short2 __attribute__((kernel)) testConvertShort2Double2Short2(double2 inV) {
+ return convert_short2(inV);
+}
+
+short3 __attribute__((kernel)) testConvertShort3Double3Short3(double3 inV) {
+ return convert_short3(inV);
+}
+
+short4 __attribute__((kernel)) testConvertShort4Double4Short4(double4 inV) {
+ return convert_short4(inV);
+}
+
+short2 __attribute__((kernel)) testConvertShort2Long2Short2(long2 inV) {
+ return convert_short2(inV);
+}
+
+short3 __attribute__((kernel)) testConvertShort3Long3Short3(long3 inV) {
+ return convert_short3(inV);
+}
+
+short4 __attribute__((kernel)) testConvertShort4Long4Short4(long4 inV) {
+ return convert_short4(inV);
+}
+
+short2 __attribute__((kernel)) testConvertShort2Ulong2Short2(ulong2 inV) {
+ return convert_short2(inV);
+}
+
+short3 __attribute__((kernel)) testConvertShort3Ulong3Short3(ulong3 inV) {
+ return convert_short3(inV);
+}
+
+short4 __attribute__((kernel)) testConvertShort4Ulong4Short4(ulong4 inV) {
+ return convert_short4(inV);
+}
+
+ushort2 __attribute__((kernel)) testConvertUshort2Double2Ushort2(double2 inV) {
+ return convert_ushort2(inV);
+}
+
+ushort3 __attribute__((kernel)) testConvertUshort3Double3Ushort3(double3 inV) {
+ return convert_ushort3(inV);
+}
+
+ushort4 __attribute__((kernel)) testConvertUshort4Double4Ushort4(double4 inV) {
+ return convert_ushort4(inV);
+}
+
+ushort2 __attribute__((kernel)) testConvertUshort2Long2Ushort2(long2 inV) {
+ return convert_ushort2(inV);
+}
+
+ushort3 __attribute__((kernel)) testConvertUshort3Long3Ushort3(long3 inV) {
+ return convert_ushort3(inV);
+}
+
+ushort4 __attribute__((kernel)) testConvertUshort4Long4Ushort4(long4 inV) {
+ return convert_ushort4(inV);
+}
+
+ushort2 __attribute__((kernel)) testConvertUshort2Ulong2Ushort2(ulong2 inV) {
+ return convert_ushort2(inV);
+}
+
+ushort3 __attribute__((kernel)) testConvertUshort3Ulong3Ushort3(ulong3 inV) {
+ return convert_ushort3(inV);
+}
+
+ushort4 __attribute__((kernel)) testConvertUshort4Ulong4Ushort4(ulong4 inV) {
+ return convert_ushort4(inV);
+}
+
+int2 __attribute__((kernel)) testConvertInt2Double2Int2(double2 inV) {
+ return convert_int2(inV);
+}
+
+int3 __attribute__((kernel)) testConvertInt3Double3Int3(double3 inV) {
+ return convert_int3(inV);
+}
+
+int4 __attribute__((kernel)) testConvertInt4Double4Int4(double4 inV) {
+ return convert_int4(inV);
+}
+
+int2 __attribute__((kernel)) testConvertInt2Long2Int2(long2 inV) {
+ return convert_int2(inV);
+}
+
+int3 __attribute__((kernel)) testConvertInt3Long3Int3(long3 inV) {
+ return convert_int3(inV);
+}
+
+int4 __attribute__((kernel)) testConvertInt4Long4Int4(long4 inV) {
+ return convert_int4(inV);
+}
+
+int2 __attribute__((kernel)) testConvertInt2Ulong2Int2(ulong2 inV) {
+ return convert_int2(inV);
+}
+
+int3 __attribute__((kernel)) testConvertInt3Ulong3Int3(ulong3 inV) {
+ return convert_int3(inV);
+}
+
+int4 __attribute__((kernel)) testConvertInt4Ulong4Int4(ulong4 inV) {
+ return convert_int4(inV);
+}
+
+uint2 __attribute__((kernel)) testConvertUint2Double2Uint2(double2 inV) {
+ return convert_uint2(inV);
+}
+
+uint3 __attribute__((kernel)) testConvertUint3Double3Uint3(double3 inV) {
+ return convert_uint3(inV);
+}
+
+uint4 __attribute__((kernel)) testConvertUint4Double4Uint4(double4 inV) {
+ return convert_uint4(inV);
+}
+
+uint2 __attribute__((kernel)) testConvertUint2Long2Uint2(long2 inV) {
+ return convert_uint2(inV);
+}
+
+uint3 __attribute__((kernel)) testConvertUint3Long3Uint3(long3 inV) {
+ return convert_uint3(inV);
+}
+
+uint4 __attribute__((kernel)) testConvertUint4Long4Uint4(long4 inV) {
+ return convert_uint4(inV);
+}
+
+uint2 __attribute__((kernel)) testConvertUint2Ulong2Uint2(ulong2 inV) {
+ return convert_uint2(inV);
+}
+
+uint3 __attribute__((kernel)) testConvertUint3Ulong3Uint3(ulong3 inV) {
+ return convert_uint3(inV);
+}
+
+uint4 __attribute__((kernel)) testConvertUint4Ulong4Uint4(ulong4 inV) {
+ return convert_uint4(inV);
+}
+
+double2 __attribute__((kernel)) testConvertDouble2Float2Double2(float2 inV) {
+ return convert_double2(inV);
+}
+
+double3 __attribute__((kernel)) testConvertDouble3Float3Double3(float3 inV) {
+ return convert_double3(inV);
+}
+
+double4 __attribute__((kernel)) testConvertDouble4Float4Double4(float4 inV) {
+ return convert_double4(inV);
+}
+
+double2 __attribute__((kernel)) testConvertDouble2Char2Double2(char2 inV) {
+ return convert_double2(inV);
+}
+
+double3 __attribute__((kernel)) testConvertDouble3Char3Double3(char3 inV) {
+ return convert_double3(inV);
+}
+
+double4 __attribute__((kernel)) testConvertDouble4Char4Double4(char4 inV) {
+ return convert_double4(inV);
+}
+
+double2 __attribute__((kernel)) testConvertDouble2Uchar2Double2(uchar2 inV) {
+ return convert_double2(inV);
+}
+
+double3 __attribute__((kernel)) testConvertDouble3Uchar3Double3(uchar3 inV) {
+ return convert_double3(inV);
+}
+
+double4 __attribute__((kernel)) testConvertDouble4Uchar4Double4(uchar4 inV) {
+ return convert_double4(inV);
+}
+
+double2 __attribute__((kernel)) testConvertDouble2Short2Double2(short2 inV) {
+ return convert_double2(inV);
+}
+
+double3 __attribute__((kernel)) testConvertDouble3Short3Double3(short3 inV) {
+ return convert_double3(inV);
+}
+
+double4 __attribute__((kernel)) testConvertDouble4Short4Double4(short4 inV) {
+ return convert_double4(inV);
+}
+
+double2 __attribute__((kernel)) testConvertDouble2Ushort2Double2(ushort2 inV) {
+ return convert_double2(inV);
+}
+
+double3 __attribute__((kernel)) testConvertDouble3Ushort3Double3(ushort3 inV) {
+ return convert_double3(inV);
+}
+
+double4 __attribute__((kernel)) testConvertDouble4Ushort4Double4(ushort4 inV) {
+ return convert_double4(inV);
+}
+
+double2 __attribute__((kernel)) testConvertDouble2Int2Double2(int2 inV) {
+ return convert_double2(inV);
+}
+
+double3 __attribute__((kernel)) testConvertDouble3Int3Double3(int3 inV) {
+ return convert_double3(inV);
+}
+
+double4 __attribute__((kernel)) testConvertDouble4Int4Double4(int4 inV) {
+ return convert_double4(inV);
+}
+
+double2 __attribute__((kernel)) testConvertDouble2Uint2Double2(uint2 inV) {
+ return convert_double2(inV);
+}
+
+double3 __attribute__((kernel)) testConvertDouble3Uint3Double3(uint3 inV) {
+ return convert_double3(inV);
+}
+
+double4 __attribute__((kernel)) testConvertDouble4Uint4Double4(uint4 inV) {
+ return convert_double4(inV);
+}
+
+long2 __attribute__((kernel)) testConvertLong2Float2Long2(float2 inV) {
+ return convert_long2(inV);
+}
+
+long3 __attribute__((kernel)) testConvertLong3Float3Long3(float3 inV) {
+ return convert_long3(inV);
+}
+
+long4 __attribute__((kernel)) testConvertLong4Float4Long4(float4 inV) {
+ return convert_long4(inV);
+}
+
+long2 __attribute__((kernel)) testConvertLong2Char2Long2(char2 inV) {
+ return convert_long2(inV);
+}
+
+long3 __attribute__((kernel)) testConvertLong3Char3Long3(char3 inV) {
+ return convert_long3(inV);
+}
+
+long4 __attribute__((kernel)) testConvertLong4Char4Long4(char4 inV) {
+ return convert_long4(inV);
+}
+
+long2 __attribute__((kernel)) testConvertLong2Uchar2Long2(uchar2 inV) {
+ return convert_long2(inV);
+}
+
+long3 __attribute__((kernel)) testConvertLong3Uchar3Long3(uchar3 inV) {
+ return convert_long3(inV);
+}
+
+long4 __attribute__((kernel)) testConvertLong4Uchar4Long4(uchar4 inV) {
+ return convert_long4(inV);
+}
+
+long2 __attribute__((kernel)) testConvertLong2Short2Long2(short2 inV) {
+ return convert_long2(inV);
+}
+
+long3 __attribute__((kernel)) testConvertLong3Short3Long3(short3 inV) {
+ return convert_long3(inV);
+}
+
+long4 __attribute__((kernel)) testConvertLong4Short4Long4(short4 inV) {
+ return convert_long4(inV);
+}
+
+long2 __attribute__((kernel)) testConvertLong2Ushort2Long2(ushort2 inV) {
+ return convert_long2(inV);
+}
+
+long3 __attribute__((kernel)) testConvertLong3Ushort3Long3(ushort3 inV) {
+ return convert_long3(inV);
+}
+
+long4 __attribute__((kernel)) testConvertLong4Ushort4Long4(ushort4 inV) {
+ return convert_long4(inV);
+}
+
+long2 __attribute__((kernel)) testConvertLong2Int2Long2(int2 inV) {
+ return convert_long2(inV);
+}
+
+long3 __attribute__((kernel)) testConvertLong3Int3Long3(int3 inV) {
+ return convert_long3(inV);
+}
+
+long4 __attribute__((kernel)) testConvertLong4Int4Long4(int4 inV) {
+ return convert_long4(inV);
+}
+
+long2 __attribute__((kernel)) testConvertLong2Uint2Long2(uint2 inV) {
+ return convert_long2(inV);
+}
+
+long3 __attribute__((kernel)) testConvertLong3Uint3Long3(uint3 inV) {
+ return convert_long3(inV);
+}
+
+long4 __attribute__((kernel)) testConvertLong4Uint4Long4(uint4 inV) {
+ return convert_long4(inV);
+}
+
+ulong2 __attribute__((kernel)) testConvertUlong2Float2Ulong2(float2 inV) {
+ return convert_ulong2(inV);
+}
+
+ulong3 __attribute__((kernel)) testConvertUlong3Float3Ulong3(float3 inV) {
+ return convert_ulong3(inV);
+}
+
+ulong4 __attribute__((kernel)) testConvertUlong4Float4Ulong4(float4 inV) {
+ return convert_ulong4(inV);
+}
+
+ulong2 __attribute__((kernel)) testConvertUlong2Char2Ulong2(char2 inV) {
+ return convert_ulong2(inV);
+}
+
+ulong3 __attribute__((kernel)) testConvertUlong3Char3Ulong3(char3 inV) {
+ return convert_ulong3(inV);
+}
+
+ulong4 __attribute__((kernel)) testConvertUlong4Char4Ulong4(char4 inV) {
+ return convert_ulong4(inV);
+}
+
+ulong2 __attribute__((kernel)) testConvertUlong2Uchar2Ulong2(uchar2 inV) {
+ return convert_ulong2(inV);
+}
+
+ulong3 __attribute__((kernel)) testConvertUlong3Uchar3Ulong3(uchar3 inV) {
+ return convert_ulong3(inV);
+}
+
+ulong4 __attribute__((kernel)) testConvertUlong4Uchar4Ulong4(uchar4 inV) {
+ return convert_ulong4(inV);
+}
+
+ulong2 __attribute__((kernel)) testConvertUlong2Short2Ulong2(short2 inV) {
+ return convert_ulong2(inV);
+}
+
+ulong3 __attribute__((kernel)) testConvertUlong3Short3Ulong3(short3 inV) {
+ return convert_ulong3(inV);
+}
+
+ulong4 __attribute__((kernel)) testConvertUlong4Short4Ulong4(short4 inV) {
+ return convert_ulong4(inV);
+}
+
+ulong2 __attribute__((kernel)) testConvertUlong2Ushort2Ulong2(ushort2 inV) {
+ return convert_ulong2(inV);
+}
+
+ulong3 __attribute__((kernel)) testConvertUlong3Ushort3Ulong3(ushort3 inV) {
+ return convert_ulong3(inV);
+}
+
+ulong4 __attribute__((kernel)) testConvertUlong4Ushort4Ulong4(ushort4 inV) {
+ return convert_ulong4(inV);
+}
+
+ulong2 __attribute__((kernel)) testConvertUlong2Int2Ulong2(int2 inV) {
+ return convert_ulong2(inV);
+}
+
+ulong3 __attribute__((kernel)) testConvertUlong3Int3Ulong3(int3 inV) {
+ return convert_ulong3(inV);
+}
+
+ulong4 __attribute__((kernel)) testConvertUlong4Int4Ulong4(int4 inV) {
+ return convert_ulong4(inV);
+}
+
+ulong2 __attribute__((kernel)) testConvertUlong2Uint2Ulong2(uint2 inV) {
+ return convert_ulong2(inV);
+}
+
+ulong3 __attribute__((kernel)) testConvertUlong3Uint3Ulong3(uint3 inV) {
+ return convert_ulong3(inV);
+}
+
+ulong4 __attribute__((kernel)) testConvertUlong4Uint4Ulong4(uint4 inV) {
+ return convert_ulong4(inV);
+}
diff --git a/tests/src/android/renderscript/cts/TestMax.rs b/tests/src/android/renderscript/cts/TestMax.rs
index 87d8040..dff7927 100644
--- a/tests/src/android/renderscript/cts/TestMax.rs
+++ b/tests/src/android/renderscript/cts/TestMax.rs
@@ -47,117 +47,157 @@
return max(inV1, inV2);
}
-uchar __attribute__((kernel)) testMaxUcharUcharUchar(uchar inV1, unsigned int x) {
- uchar inV2 = rsGetElementAt_uchar(gAllocInV2, x);
- return max(inV1, inV2);
-}
-
-short __attribute__((kernel)) testMaxShortShortShort(short inV1, unsigned int x) {
- short inV2 = rsGetElementAt_short(gAllocInV2, x);
- return max(inV1, inV2);
-}
-
-ushort __attribute__((kernel)) testMaxUshortUshortUshort(ushort inV1, unsigned int x) {
- ushort inV2 = rsGetElementAt_ushort(gAllocInV2, x);
- return max(inV1, inV2);
-}
-
-int __attribute__((kernel)) testMaxIntIntInt(int inV1, unsigned int x) {
- int inV2 = rsGetElementAt_int(gAllocInV2, x);
- return max(inV1, inV2);
-}
-
-uint __attribute__((kernel)) testMaxUintUintUint(uint inV1, unsigned int x) {
- uint inV2 = rsGetElementAt_uint(gAllocInV2, x);
- return max(inV1, inV2);
-}
-
char2 __attribute__((kernel)) testMaxChar2Char2Char2(char2 inV1, unsigned int x) {
char2 inV2 = rsGetElementAt_char2(gAllocInV2, x);
return max(inV1, inV2);
}
-uchar2 __attribute__((kernel)) testMaxUchar2Uchar2Uchar2(uchar2 inV1, unsigned int x) {
- uchar2 inV2 = rsGetElementAt_uchar2(gAllocInV2, x);
- return max(inV1, inV2);
-}
-
-short2 __attribute__((kernel)) testMaxShort2Short2Short2(short2 inV1, unsigned int x) {
- short2 inV2 = rsGetElementAt_short2(gAllocInV2, x);
- return max(inV1, inV2);
-}
-
-ushort2 __attribute__((kernel)) testMaxUshort2Ushort2Ushort2(ushort2 inV1, unsigned int x) {
- ushort2 inV2 = rsGetElementAt_ushort2(gAllocInV2, x);
- return max(inV1, inV2);
-}
-
-int2 __attribute__((kernel)) testMaxInt2Int2Int2(int2 inV1, unsigned int x) {
- int2 inV2 = rsGetElementAt_int2(gAllocInV2, x);
- return max(inV1, inV2);
-}
-
-uint2 __attribute__((kernel)) testMaxUint2Uint2Uint2(uint2 inV1, unsigned int x) {
- uint2 inV2 = rsGetElementAt_uint2(gAllocInV2, x);
- return max(inV1, inV2);
-}
-
char3 __attribute__((kernel)) testMaxChar3Char3Char3(char3 inV1, unsigned int x) {
char3 inV2 = rsGetElementAt_char3(gAllocInV2, x);
return max(inV1, inV2);
}
-uchar3 __attribute__((kernel)) testMaxUchar3Uchar3Uchar3(uchar3 inV1, unsigned int x) {
- uchar3 inV2 = rsGetElementAt_uchar3(gAllocInV2, x);
- return max(inV1, inV2);
-}
-
-short3 __attribute__((kernel)) testMaxShort3Short3Short3(short3 inV1, unsigned int x) {
- short3 inV2 = rsGetElementAt_short3(gAllocInV2, x);
- return max(inV1, inV2);
-}
-
-ushort3 __attribute__((kernel)) testMaxUshort3Ushort3Ushort3(ushort3 inV1, unsigned int x) {
- ushort3 inV2 = rsGetElementAt_ushort3(gAllocInV2, x);
- return max(inV1, inV2);
-}
-
-int3 __attribute__((kernel)) testMaxInt3Int3Int3(int3 inV1, unsigned int x) {
- int3 inV2 = rsGetElementAt_int3(gAllocInV2, x);
- return max(inV1, inV2);
-}
-
-uint3 __attribute__((kernel)) testMaxUint3Uint3Uint3(uint3 inV1, unsigned int x) {
- uint3 inV2 = rsGetElementAt_uint3(gAllocInV2, x);
- return max(inV1, inV2);
-}
-
char4 __attribute__((kernel)) testMaxChar4Char4Char4(char4 inV1, unsigned int x) {
char4 inV2 = rsGetElementAt_char4(gAllocInV2, x);
return max(inV1, inV2);
}
+uchar __attribute__((kernel)) testMaxUcharUcharUchar(uchar inV1, unsigned int x) {
+ uchar inV2 = rsGetElementAt_uchar(gAllocInV2, x);
+ return max(inV1, inV2);
+}
+
+uchar2 __attribute__((kernel)) testMaxUchar2Uchar2Uchar2(uchar2 inV1, unsigned int x) {
+ uchar2 inV2 = rsGetElementAt_uchar2(gAllocInV2, x);
+ return max(inV1, inV2);
+}
+
+uchar3 __attribute__((kernel)) testMaxUchar3Uchar3Uchar3(uchar3 inV1, unsigned int x) {
+ uchar3 inV2 = rsGetElementAt_uchar3(gAllocInV2, x);
+ return max(inV1, inV2);
+}
+
uchar4 __attribute__((kernel)) testMaxUchar4Uchar4Uchar4(uchar4 inV1, unsigned int x) {
uchar4 inV2 = rsGetElementAt_uchar4(gAllocInV2, x);
return max(inV1, inV2);
}
+short __attribute__((kernel)) testMaxShortShortShort(short inV1, unsigned int x) {
+ short inV2 = rsGetElementAt_short(gAllocInV2, x);
+ return max(inV1, inV2);
+}
+
+short2 __attribute__((kernel)) testMaxShort2Short2Short2(short2 inV1, unsigned int x) {
+ short2 inV2 = rsGetElementAt_short2(gAllocInV2, x);
+ return max(inV1, inV2);
+}
+
+short3 __attribute__((kernel)) testMaxShort3Short3Short3(short3 inV1, unsigned int x) {
+ short3 inV2 = rsGetElementAt_short3(gAllocInV2, x);
+ return max(inV1, inV2);
+}
+
short4 __attribute__((kernel)) testMaxShort4Short4Short4(short4 inV1, unsigned int x) {
short4 inV2 = rsGetElementAt_short4(gAllocInV2, x);
return max(inV1, inV2);
}
+ushort __attribute__((kernel)) testMaxUshortUshortUshort(ushort inV1, unsigned int x) {
+ ushort inV2 = rsGetElementAt_ushort(gAllocInV2, x);
+ return max(inV1, inV2);
+}
+
+ushort2 __attribute__((kernel)) testMaxUshort2Ushort2Ushort2(ushort2 inV1, unsigned int x) {
+ ushort2 inV2 = rsGetElementAt_ushort2(gAllocInV2, x);
+ return max(inV1, inV2);
+}
+
+ushort3 __attribute__((kernel)) testMaxUshort3Ushort3Ushort3(ushort3 inV1, unsigned int x) {
+ ushort3 inV2 = rsGetElementAt_ushort3(gAllocInV2, x);
+ return max(inV1, inV2);
+}
+
ushort4 __attribute__((kernel)) testMaxUshort4Ushort4Ushort4(ushort4 inV1, unsigned int x) {
ushort4 inV2 = rsGetElementAt_ushort4(gAllocInV2, x);
return max(inV1, inV2);
}
+int __attribute__((kernel)) testMaxIntIntInt(int inV1, unsigned int x) {
+ int inV2 = rsGetElementAt_int(gAllocInV2, x);
+ return max(inV1, inV2);
+}
+
+int2 __attribute__((kernel)) testMaxInt2Int2Int2(int2 inV1, unsigned int x) {
+ int2 inV2 = rsGetElementAt_int2(gAllocInV2, x);
+ return max(inV1, inV2);
+}
+
+int3 __attribute__((kernel)) testMaxInt3Int3Int3(int3 inV1, unsigned int x) {
+ int3 inV2 = rsGetElementAt_int3(gAllocInV2, x);
+ return max(inV1, inV2);
+}
+
int4 __attribute__((kernel)) testMaxInt4Int4Int4(int4 inV1, unsigned int x) {
int4 inV2 = rsGetElementAt_int4(gAllocInV2, x);
return max(inV1, inV2);
}
+uint __attribute__((kernel)) testMaxUintUintUint(uint inV1, unsigned int x) {
+ uint inV2 = rsGetElementAt_uint(gAllocInV2, x);
+ return max(inV1, inV2);
+}
+
+uint2 __attribute__((kernel)) testMaxUint2Uint2Uint2(uint2 inV1, unsigned int x) {
+ uint2 inV2 = rsGetElementAt_uint2(gAllocInV2, x);
+ return max(inV1, inV2);
+}
+
+uint3 __attribute__((kernel)) testMaxUint3Uint3Uint3(uint3 inV1, unsigned int x) {
+ uint3 inV2 = rsGetElementAt_uint3(gAllocInV2, x);
+ return max(inV1, inV2);
+}
+
uint4 __attribute__((kernel)) testMaxUint4Uint4Uint4(uint4 inV1, unsigned int x) {
uint4 inV2 = rsGetElementAt_uint4(gAllocInV2, x);
return max(inV1, inV2);
}
+
+long __attribute__((kernel)) testMaxLongLongLong(long inV1, unsigned int x) {
+ long inV2 = rsGetElementAt_long(gAllocInV2, x);
+ return max(inV1, inV2);
+}
+
+long2 __attribute__((kernel)) testMaxLong2Long2Long2(long2 inV1, unsigned int x) {
+ long2 inV2 = rsGetElementAt_long2(gAllocInV2, x);
+ return max(inV1, inV2);
+}
+
+long3 __attribute__((kernel)) testMaxLong3Long3Long3(long3 inV1, unsigned int x) {
+ long3 inV2 = rsGetElementAt_long3(gAllocInV2, x);
+ return max(inV1, inV2);
+}
+
+long4 __attribute__((kernel)) testMaxLong4Long4Long4(long4 inV1, unsigned int x) {
+ long4 inV2 = rsGetElementAt_long4(gAllocInV2, x);
+ return max(inV1, inV2);
+}
+
+ulong __attribute__((kernel)) testMaxUlongUlongUlong(ulong inV1, unsigned int x) {
+ ulong inV2 = rsGetElementAt_ulong(gAllocInV2, x);
+ return max(inV1, inV2);
+}
+
+ulong2 __attribute__((kernel)) testMaxUlong2Ulong2Ulong2(ulong2 inV1, unsigned int x) {
+ ulong2 inV2 = rsGetElementAt_ulong2(gAllocInV2, x);
+ return max(inV1, inV2);
+}
+
+ulong3 __attribute__((kernel)) testMaxUlong3Ulong3Ulong3(ulong3 inV1, unsigned int x) {
+ ulong3 inV2 = rsGetElementAt_ulong3(gAllocInV2, x);
+ return max(inV1, inV2);
+}
+
+ulong4 __attribute__((kernel)) testMaxUlong4Ulong4Ulong4(ulong4 inV1, unsigned int x) {
+ ulong4 inV2 = rsGetElementAt_ulong4(gAllocInV2, x);
+ return max(inV1, inV2);
+}
diff --git a/tests/src/android/renderscript/cts/TestMin.rs b/tests/src/android/renderscript/cts/TestMin.rs
index 26301ff..29a9aeb 100644
--- a/tests/src/android/renderscript/cts/TestMin.rs
+++ b/tests/src/android/renderscript/cts/TestMin.rs
@@ -47,117 +47,157 @@
return min(inV1, inV2);
}
-uchar __attribute__((kernel)) testMinUcharUcharUchar(uchar inV1, unsigned int x) {
- uchar inV2 = rsGetElementAt_uchar(gAllocInV2, x);
- return min(inV1, inV2);
-}
-
-short __attribute__((kernel)) testMinShortShortShort(short inV1, unsigned int x) {
- short inV2 = rsGetElementAt_short(gAllocInV2, x);
- return min(inV1, inV2);
-}
-
-ushort __attribute__((kernel)) testMinUshortUshortUshort(ushort inV1, unsigned int x) {
- ushort inV2 = rsGetElementAt_ushort(gAllocInV2, x);
- return min(inV1, inV2);
-}
-
-int __attribute__((kernel)) testMinIntIntInt(int inV1, unsigned int x) {
- int inV2 = rsGetElementAt_int(gAllocInV2, x);
- return min(inV1, inV2);
-}
-
-uint __attribute__((kernel)) testMinUintUintUint(uint inV1, unsigned int x) {
- uint inV2 = rsGetElementAt_uint(gAllocInV2, x);
- return min(inV1, inV2);
-}
-
char2 __attribute__((kernel)) testMinChar2Char2Char2(char2 inV1, unsigned int x) {
char2 inV2 = rsGetElementAt_char2(gAllocInV2, x);
return min(inV1, inV2);
}
-uchar2 __attribute__((kernel)) testMinUchar2Uchar2Uchar2(uchar2 inV1, unsigned int x) {
- uchar2 inV2 = rsGetElementAt_uchar2(gAllocInV2, x);
- return min(inV1, inV2);
-}
-
-short2 __attribute__((kernel)) testMinShort2Short2Short2(short2 inV1, unsigned int x) {
- short2 inV2 = rsGetElementAt_short2(gAllocInV2, x);
- return min(inV1, inV2);
-}
-
-ushort2 __attribute__((kernel)) testMinUshort2Ushort2Ushort2(ushort2 inV1, unsigned int x) {
- ushort2 inV2 = rsGetElementAt_ushort2(gAllocInV2, x);
- return min(inV1, inV2);
-}
-
-int2 __attribute__((kernel)) testMinInt2Int2Int2(int2 inV1, unsigned int x) {
- int2 inV2 = rsGetElementAt_int2(gAllocInV2, x);
- return min(inV1, inV2);
-}
-
-uint2 __attribute__((kernel)) testMinUint2Uint2Uint2(uint2 inV1, unsigned int x) {
- uint2 inV2 = rsGetElementAt_uint2(gAllocInV2, x);
- return min(inV1, inV2);
-}
-
char3 __attribute__((kernel)) testMinChar3Char3Char3(char3 inV1, unsigned int x) {
char3 inV2 = rsGetElementAt_char3(gAllocInV2, x);
return min(inV1, inV2);
}
-uchar3 __attribute__((kernel)) testMinUchar3Uchar3Uchar3(uchar3 inV1, unsigned int x) {
- uchar3 inV2 = rsGetElementAt_uchar3(gAllocInV2, x);
- return min(inV1, inV2);
-}
-
-short3 __attribute__((kernel)) testMinShort3Short3Short3(short3 inV1, unsigned int x) {
- short3 inV2 = rsGetElementAt_short3(gAllocInV2, x);
- return min(inV1, inV2);
-}
-
-ushort3 __attribute__((kernel)) testMinUshort3Ushort3Ushort3(ushort3 inV1, unsigned int x) {
- ushort3 inV2 = rsGetElementAt_ushort3(gAllocInV2, x);
- return min(inV1, inV2);
-}
-
-int3 __attribute__((kernel)) testMinInt3Int3Int3(int3 inV1, unsigned int x) {
- int3 inV2 = rsGetElementAt_int3(gAllocInV2, x);
- return min(inV1, inV2);
-}
-
-uint3 __attribute__((kernel)) testMinUint3Uint3Uint3(uint3 inV1, unsigned int x) {
- uint3 inV2 = rsGetElementAt_uint3(gAllocInV2, x);
- return min(inV1, inV2);
-}
-
char4 __attribute__((kernel)) testMinChar4Char4Char4(char4 inV1, unsigned int x) {
char4 inV2 = rsGetElementAt_char4(gAllocInV2, x);
return min(inV1, inV2);
}
+uchar __attribute__((kernel)) testMinUcharUcharUchar(uchar inV1, unsigned int x) {
+ uchar inV2 = rsGetElementAt_uchar(gAllocInV2, x);
+ return min(inV1, inV2);
+}
+
+uchar2 __attribute__((kernel)) testMinUchar2Uchar2Uchar2(uchar2 inV1, unsigned int x) {
+ uchar2 inV2 = rsGetElementAt_uchar2(gAllocInV2, x);
+ return min(inV1, inV2);
+}
+
+uchar3 __attribute__((kernel)) testMinUchar3Uchar3Uchar3(uchar3 inV1, unsigned int x) {
+ uchar3 inV2 = rsGetElementAt_uchar3(gAllocInV2, x);
+ return min(inV1, inV2);
+}
+
uchar4 __attribute__((kernel)) testMinUchar4Uchar4Uchar4(uchar4 inV1, unsigned int x) {
uchar4 inV2 = rsGetElementAt_uchar4(gAllocInV2, x);
return min(inV1, inV2);
}
+short __attribute__((kernel)) testMinShortShortShort(short inV1, unsigned int x) {
+ short inV2 = rsGetElementAt_short(gAllocInV2, x);
+ return min(inV1, inV2);
+}
+
+short2 __attribute__((kernel)) testMinShort2Short2Short2(short2 inV1, unsigned int x) {
+ short2 inV2 = rsGetElementAt_short2(gAllocInV2, x);
+ return min(inV1, inV2);
+}
+
+short3 __attribute__((kernel)) testMinShort3Short3Short3(short3 inV1, unsigned int x) {
+ short3 inV2 = rsGetElementAt_short3(gAllocInV2, x);
+ return min(inV1, inV2);
+}
+
short4 __attribute__((kernel)) testMinShort4Short4Short4(short4 inV1, unsigned int x) {
short4 inV2 = rsGetElementAt_short4(gAllocInV2, x);
return min(inV1, inV2);
}
+ushort __attribute__((kernel)) testMinUshortUshortUshort(ushort inV1, unsigned int x) {
+ ushort inV2 = rsGetElementAt_ushort(gAllocInV2, x);
+ return min(inV1, inV2);
+}
+
+ushort2 __attribute__((kernel)) testMinUshort2Ushort2Ushort2(ushort2 inV1, unsigned int x) {
+ ushort2 inV2 = rsGetElementAt_ushort2(gAllocInV2, x);
+ return min(inV1, inV2);
+}
+
+ushort3 __attribute__((kernel)) testMinUshort3Ushort3Ushort3(ushort3 inV1, unsigned int x) {
+ ushort3 inV2 = rsGetElementAt_ushort3(gAllocInV2, x);
+ return min(inV1, inV2);
+}
+
ushort4 __attribute__((kernel)) testMinUshort4Ushort4Ushort4(ushort4 inV1, unsigned int x) {
ushort4 inV2 = rsGetElementAt_ushort4(gAllocInV2, x);
return min(inV1, inV2);
}
+int __attribute__((kernel)) testMinIntIntInt(int inV1, unsigned int x) {
+ int inV2 = rsGetElementAt_int(gAllocInV2, x);
+ return min(inV1, inV2);
+}
+
+int2 __attribute__((kernel)) testMinInt2Int2Int2(int2 inV1, unsigned int x) {
+ int2 inV2 = rsGetElementAt_int2(gAllocInV2, x);
+ return min(inV1, inV2);
+}
+
+int3 __attribute__((kernel)) testMinInt3Int3Int3(int3 inV1, unsigned int x) {
+ int3 inV2 = rsGetElementAt_int3(gAllocInV2, x);
+ return min(inV1, inV2);
+}
+
int4 __attribute__((kernel)) testMinInt4Int4Int4(int4 inV1, unsigned int x) {
int4 inV2 = rsGetElementAt_int4(gAllocInV2, x);
return min(inV1, inV2);
}
+uint __attribute__((kernel)) testMinUintUintUint(uint inV1, unsigned int x) {
+ uint inV2 = rsGetElementAt_uint(gAllocInV2, x);
+ return min(inV1, inV2);
+}
+
+uint2 __attribute__((kernel)) testMinUint2Uint2Uint2(uint2 inV1, unsigned int x) {
+ uint2 inV2 = rsGetElementAt_uint2(gAllocInV2, x);
+ return min(inV1, inV2);
+}
+
+uint3 __attribute__((kernel)) testMinUint3Uint3Uint3(uint3 inV1, unsigned int x) {
+ uint3 inV2 = rsGetElementAt_uint3(gAllocInV2, x);
+ return min(inV1, inV2);
+}
+
uint4 __attribute__((kernel)) testMinUint4Uint4Uint4(uint4 inV1, unsigned int x) {
uint4 inV2 = rsGetElementAt_uint4(gAllocInV2, x);
return min(inV1, inV2);
}
+
+long __attribute__((kernel)) testMinLongLongLong(long inV1, unsigned int x) {
+ long inV2 = rsGetElementAt_long(gAllocInV2, x);
+ return min(inV1, inV2);
+}
+
+long2 __attribute__((kernel)) testMinLong2Long2Long2(long2 inV1, unsigned int x) {
+ long2 inV2 = rsGetElementAt_long2(gAllocInV2, x);
+ return min(inV1, inV2);
+}
+
+long3 __attribute__((kernel)) testMinLong3Long3Long3(long3 inV1, unsigned int x) {
+ long3 inV2 = rsGetElementAt_long3(gAllocInV2, x);
+ return min(inV1, inV2);
+}
+
+long4 __attribute__((kernel)) testMinLong4Long4Long4(long4 inV1, unsigned int x) {
+ long4 inV2 = rsGetElementAt_long4(gAllocInV2, x);
+ return min(inV1, inV2);
+}
+
+ulong __attribute__((kernel)) testMinUlongUlongUlong(ulong inV1, unsigned int x) {
+ ulong inV2 = rsGetElementAt_ulong(gAllocInV2, x);
+ return min(inV1, inV2);
+}
+
+ulong2 __attribute__((kernel)) testMinUlong2Ulong2Ulong2(ulong2 inV1, unsigned int x) {
+ ulong2 inV2 = rsGetElementAt_ulong2(gAllocInV2, x);
+ return min(inV1, inV2);
+}
+
+ulong3 __attribute__((kernel)) testMinUlong3Ulong3Ulong3(ulong3 inV1, unsigned int x) {
+ ulong3 inV2 = rsGetElementAt_ulong3(gAllocInV2, x);
+ return min(inV1, inV2);
+}
+
+ulong4 __attribute__((kernel)) testMinUlong4Ulong4Ulong4(ulong4 inV1, unsigned int x) {
+ ulong4 inV2 = rsGetElementAt_ulong4(gAllocInV2, x);
+ return min(inV1, inV2);
+}
diff --git a/tests/src/android/renderscript/cts/TestStep.rs b/tests/src/android/renderscript/cts/TestStep.rs
index 41f8462..b815d52 100644
--- a/tests/src/android/renderscript/cts/TestStep.rs
+++ b/tests/src/android/renderscript/cts/TestStep.rs
@@ -55,3 +55,18 @@
float inV = rsGetElementAt_float(gAllocInV, x);
return step(inEdge, inV);
}
+
+float2 __attribute__((kernel)) testStepFloatFloat2Float2(float inEdge, unsigned int x) {
+ float2 inV = rsGetElementAt_float2(gAllocInV, x);
+ return step(inEdge, inV);
+}
+
+float3 __attribute__((kernel)) testStepFloatFloat3Float3(float inEdge, unsigned int x) {
+ float3 inV = rsGetElementAt_float3(gAllocInV, x);
+ return step(inEdge, inV);
+}
+
+float4 __attribute__((kernel)) testStepFloatFloat4Float4(float inEdge, unsigned int x) {
+ float4 inV = rsGetElementAt_float4(gAllocInV, x);
+ return step(inEdge, inV);
+}
diff --git a/tests/src/android/webkit/cts/WebViewOnUiThread.java b/tests/src/android/webkit/cts/WebViewOnUiThread.java
index f638ba8..c1032a6 100644
--- a/tests/src/android/webkit/cts/WebViewOnUiThread.java
+++ b/tests/src/android/webkit/cts/WebViewOnUiThread.java
@@ -387,6 +387,7 @@
* onNewPicture and onProgressChange to reach 100.
* Test fails if the load timeout elapses.
* @param url The URL to load.
+ * @param extraHeaders The additional headers to be used in the HTTP request.
*/
public void loadUrlAndWaitForCompletion(final String url,
final Map<String, String> extraHeaders) {
@@ -416,6 +417,15 @@
});
}
+ public void postUrlAndWaitForCompletion(final String url, final byte[] postData) {
+ callAndWait(new Runnable() {
+ @Override
+ public void run() {
+ mWebView.postUrl(url, postData);
+ }
+ });
+ }
+
public void loadDataAndWaitForCompletion(final String data,
final String mimeType, final String encoding) {
callAndWait(new Runnable() {
@@ -694,6 +704,15 @@
});
}
+ public WebView createWebView() {
+ return getValue(new ValueGetter<WebView>() {
+ @Override
+ public WebView capture() {
+ return new WebView(mWebView.getContext());
+ }
+ });
+ }
+
public PrintDocumentAdapter createPrintDocumentAdapter() {
return getValue(new ValueGetter<PrintDocumentAdapter>() {
@Override
diff --git a/tests/systemAppTest/test/Android.mk b/tests/systemAppTest/test/Android.mk
index 3354886..9be491c 100644
--- a/tests/systemAppTest/test/Android.mk
+++ b/tests/systemAppTest/test/Android.mk
@@ -24,8 +24,6 @@
LOCAL_MODULE_PATH := $(TARGET_OUT_APPS_PRIVILEGED)
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner ctsdeviceutil
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/acceleration/Android.mk b/tests/tests/acceleration/Android.mk
index 30a1f51..d417371 100644
--- a/tests/tests/acceleration/Android.mk
+++ b/tests/tests/acceleration/Android.mk
@@ -20,8 +20,6 @@
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/acceleration/AndroidManifest.xml b/tests/tests/acceleration/AndroidManifest.xml
index d08827e..0dd2722 100644
--- a/tests/tests/acceleration/AndroidManifest.xml
+++ b/tests/tests/acceleration/AndroidManifest.xml
@@ -24,8 +24,11 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.acceleration.stub"
- android:label="Tests for the Hardware Acceleration APIs." />
+ android:label="Tests for the Hardware Acceleration APIs." >
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/tests/accessibility/Android.mk b/tests/tests/accessibility/Android.mk
index abd6f4b..9f98b16 100644
--- a/tests/tests/accessibility/Android.mk
+++ b/tests/tests/accessibility/Android.mk
@@ -20,8 +20,6 @@
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := CtsAccessibilityTestCases
diff --git a/tests/tests/accessibility/AndroidManifest.xml b/tests/tests/accessibility/AndroidManifest.xml
index 53b9cc3..319fb49 100644
--- a/tests/tests/accessibility/AndroidManifest.xml
+++ b/tests/tests/accessibility/AndroidManifest.xml
@@ -24,8 +24,11 @@
<uses-library android:name="android.test.runner"/>
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="android.view.cts.accessibility"
- android:label="Tests for the accessibility APIs."/>
+ android:label="Tests for the accessibility APIs.">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/tests/accessibility/res/values/ids.xml b/tests/tests/accessibility/res/values/ids.xml
new file mode 100644
index 0000000..23edb2b
--- /dev/null
+++ b/tests/tests/accessibility/res/values/ids.xml
@@ -0,0 +1,18 @@
+<!-- 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.
+-->
+
+<resources>
+ <item type="id" name="foo_custom_action" />
+</resources>
diff --git a/tests/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java b/tests/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java
index ae7cc9b..7925d15 100644
--- a/tests/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java
+++ b/tests/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java
@@ -23,6 +23,11 @@
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+import android.view.cts.accessibility.R;
+
+import java.util.ArrayList;
+import java.util.List;
/**
* Class for testing {@link AccessibilityNodeInfo}.
@@ -94,6 +99,70 @@
}
/**
+ * Tests whether accessibility actions are properly added.
+ */
+ @SmallTest
+ public void testAddActions() {
+ List<AccessibilityAction> customActions = new ArrayList<AccessibilityAction>();
+ customActions.add(new AccessibilityAction(AccessibilityNodeInfo.ACTION_FOCUS, "Foo"));
+ customActions.add(new AccessibilityAction(R.id.foo_custom_action, "Foo"));
+
+ AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
+ info.addAction(AccessibilityNodeInfo.ACTION_FOCUS);
+ info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_FOCUS);
+ for (AccessibilityAction customAction : customActions) {
+ info.addAction(customAction);
+ }
+
+ assertSame(info.getActions(), (AccessibilityNodeInfo.ACTION_FOCUS
+ | AccessibilityNodeInfo.ACTION_CLEAR_FOCUS));
+
+ List<AccessibilityAction> allActions = new ArrayList<AccessibilityAction>();
+ allActions.add(AccessibilityAction.ACTION_CLEAR_FOCUS);
+ allActions.addAll(customActions);
+ assertEquals(info.getActionList(), allActions);
+ }
+
+ /**
+ * Tests whether we catch addition of an action with invalid id.
+ */
+ @SmallTest
+ public void testCreateInvalidActionId() {
+ try {
+ new AccessibilityAction(3, null);
+ } catch (IllegalArgumentException iae) {
+ /* expected */
+ }
+ }
+
+ /**
+ * Tests whether accessibility actions are properly removed.
+ */
+ @SmallTest
+ public void testRemoveActions() {
+ AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
+
+ info.addAction(AccessibilityNodeInfo.ACTION_FOCUS);
+ assertSame(info.getActions(), AccessibilityNodeInfo.ACTION_FOCUS);
+
+ info.removeAction(AccessibilityNodeInfo.ACTION_FOCUS);
+ assertSame(info.getActions(), 0);
+ assertTrue(info.getActionList().isEmpty());
+
+ AccessibilityAction customFocus = new AccessibilityAction(
+ AccessibilityNodeInfo.ACTION_FOCUS, "Foo");
+ info.addAction(AccessibilityNodeInfo.ACTION_FOCUS);
+ info.addAction(customFocus);
+ assertSame(info.getActionList().size(), 1);
+ assertEquals(info.getActionList().get(0), customFocus);
+ assertSame(info.getActions(), AccessibilityNodeInfo.ACTION_FOCUS);
+
+ info.removeAction(customFocus);
+ assertSame(info.getActions(), 0);
+ assertTrue(info.getActionList().isEmpty());
+ }
+
+ /**
* Fully populates the {@link AccessibilityNodeInfo} to marshal.
*
* @param info The node info to populate.
@@ -119,7 +188,10 @@
info.setPassword(true);
info.setScrollable(true);
info.setSelected(true);
+ info.addAction(AccessibilityNodeInfo.ACTION_FOCUS);
info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_FOCUS);
+ info.addAction(new AccessibilityAction(AccessibilityNodeInfo.ACTION_FOCUS, "Foo"));
+ info.addAction(new AccessibilityAction(R.id.foo_custom_action, "Foo"));
info.setAccessibilityFocused(true);
info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
info.setLabeledBy(new View(getContext()));
@@ -171,6 +243,8 @@
receivedInfo.isSelected());
assertSame("actions has incorrect value", expectedInfo.getActions(),
receivedInfo.getActions());
+ assertEquals("actionsSet has incorrect value", expectedInfo.getActionList(),
+ receivedInfo.getActionList());
assertSame("childCount has incorrect value", expectedInfo.getChildCount(),
receivedInfo.getChildCount());
assertSame("childCount has incorrect value", expectedInfo.getChildCount(),
diff --git a/tests/tests/accessibility/src/android/view/accessibility/cts/AccessibilityRecordTest.java b/tests/tests/accessibility/src/android/view/accessibility/cts/AccessibilityRecordTest.java
index 3aaf54e..7862cb4 100644
--- a/tests/tests/accessibility/src/android/view/accessibility/cts/AccessibilityRecordTest.java
+++ b/tests/tests/accessibility/src/android/view/accessibility/cts/AccessibilityRecordTest.java
@@ -204,7 +204,7 @@
static void assertNoNewNonStaticFieldsAdded(Class<?> clazz, int expectedCount) {
int nonStaticFieldCount = 0;
- while (clazz != null) {
+ while (clazz != Object.class) {
for (Field field : clazz.getDeclaredFields()) {
if ((field.getModifiers() & Modifier.STATIC) == 0) {
nonStaticFieldCount++;
diff --git a/tests/tests/accessibilityservice/Android.mk b/tests/tests/accessibilityservice/Android.mk
index 73cd288..b27dbcc 100644
--- a/tests/tests/accessibilityservice/Android.mk
+++ b/tests/tests/accessibilityservice/Android.mk
@@ -18,12 +18,12 @@
LOCAL_MODULE_TAGS := optional
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := CtsAccessibilityServiceTestCases
+LOCAL_SDK_VERSION := current
+
include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/accessibilityservice/AndroidManifest.xml b/tests/tests/accessibilityservice/AndroidManifest.xml
index ed94f9c..4039193 100644
--- a/tests/tests/accessibilityservice/AndroidManifest.xml
+++ b/tests/tests/accessibilityservice/AndroidManifest.xml
@@ -43,8 +43,11 @@
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.accessibilityservice"
- android:label="Tests for the accessibility APIs."/>
+ android:label="Tests for the accessibility APIs.">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/tests/accessibilityservice/res/values/ids.xml b/tests/tests/accessibilityservice/res/values/ids.xml
new file mode 100644
index 0000000..23edb2b
--- /dev/null
+++ b/tests/tests/accessibilityservice/res/values/ids.xml
@@ -0,0 +1,18 @@
+<!-- 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.
+-->
+
+<resources>
+ <item type="id" name="foo_custom_action" />
+</resources>
diff --git a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
index f0f0dd7..39b116a 100644
--- a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
+++ b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
@@ -46,10 +46,8 @@
/**
* Creates a new instance for testing {@link AccessibilityEndToEndActivity}.
- *
- * @throws Exception If any error occurs.
*/
- public AccessibilityEndToEndTest() throws Exception {
+ public AccessibilityEndToEndTest() {
super(AccessibilityEndToEndActivity.class);
}
diff --git a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryActivity.java b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryActivity.java
index b0cedf6..aa66a45 100644
--- a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryActivity.java
+++ b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryActivity.java
@@ -17,13 +17,15 @@
import android.os.Bundle;
import android.view.View;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import com.android.cts.accessibilityservice.R;
/**
* Activity for testing the accessibility APIs for querying of
* the screen content. These APIs allow exploring the screen and
* requesting an action to be performed on a given view from an
- * AccessiiblityService.
+ * AccessibilityService.
*/
public class AccessibilityWindowQueryActivity extends AccessibilityTestActivity {
@@ -42,5 +44,21 @@
return true;
}
});
+
+ findViewById(R.id.button5).setAccessibilityDelegate(new View.AccessibilityDelegate() {
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+ info.addAction(new AccessibilityAction(R.id.foo_custom_action, "Foo"));
+ }
+
+ @Override
+ public boolean performAccessibilityAction(View host, int action, Bundle args) {
+ if (action == R.id.foo_custom_action) {
+ return true;
+ }
+ return super.performAccessibilityAction(host, action, args);
+ }
+ });
}
}
diff --git a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
index 492b99b..ded27ff 100644
--- a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
+++ b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
@@ -30,6 +30,8 @@
import android.test.suitebuilder.annotation.MediumTest;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+import android.view.accessibility.AccessibilityWindowInfo;
import com.android.cts.accessibilityservice.R;
@@ -41,11 +43,13 @@
/**
* Test cases for testing the accessibility APIs for querying of the screen content.
* These APIs allow exploring the screen and requesting an action to be performed
- * on a given view from an AccessiiblityService.
+ * on a given view from an AccessibilityService.
*/
public class AccessibilityWindowQueryTest
extends AccessibilityActivityTestCase<AccessibilityWindowQueryActivity> {
+ private static final long TIMEOUT_WINDOW_STATE_IDLE = 500;
+
public AccessibilityWindowQueryTest() {
super(AccessibilityWindowQueryActivity.class);
}
@@ -69,62 +73,326 @@
@MediumTest
public void testTraverseWindow() throws Exception {
- try {
- AccessibilityServiceInfo info = getInstrumentation().getUiAutomation().getServiceInfo();
- info.flags |= AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
- getInstrumentation().getUiAutomation().setServiceInfo(info);
+ verifyNodesInAppWindow(getInstrumentation().getUiAutomation().getRootInActiveWindow());
+ }
- // make list of expected nodes
- List<String> classNameAndTextList = new ArrayList<String>();
- classNameAndTextList.add("android.widget.LinearLayout");
- classNameAndTextList.add("android.widget.LinearLayout");
- classNameAndTextList.add("android.widget.LinearLayout");
- classNameAndTextList.add("android.widget.LinearLayout");
- classNameAndTextList.add("android.widget.ButtonB1");
- classNameAndTextList.add("android.widget.ButtonB2");
- classNameAndTextList.add("android.widget.ButtonB3");
- classNameAndTextList.add("android.widget.ButtonB4");
- classNameAndTextList.add("android.widget.ButtonB5");
- classNameAndTextList.add("android.widget.ButtonB6");
- classNameAndTextList.add("android.widget.ButtonB7");
- classNameAndTextList.add("android.widget.ButtonB8");
- classNameAndTextList.add("android.widget.ButtonB9");
+ @MediumTest
+ public void testNoWindowsAccessIfFlagNotSet() throws Exception {
+ // Make sure the windows cannot be accessed.
+ UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+ assertTrue(uiAutomation.getWindows().isEmpty());
- String contentViewIdResName = "com.android.cts.accessibilityservice:id/added_content";
- boolean verifyContent = false;
+ // Find a button to click on.
+ final AccessibilityNodeInfo button1 = uiAutomation.getRootInActiveWindow()
+ .findAccessibilityNodeInfosByViewId(
+ "com.android.cts.accessibilityservice:id/button1").get(0);
- Queue<AccessibilityNodeInfo> fringe = new LinkedList<AccessibilityNodeInfo>();
- fringe.add(getInstrumentation().getUiAutomation().getRootInActiveWindow());
+ // Argh...
+ final List<AccessibilityEvent> events = new ArrayList<AccessibilityEvent>();
- // do a BFS traversal and check nodes
- while (!fringe.isEmpty()) {
- AccessibilityNodeInfo current = fringe.poll();
-
- if (!verifyContent
- && contentViewIdResName.equals(current.getViewIdResourceName())) {
- verifyContent = true;
- }
-
- if (verifyContent) {
- CharSequence text = current.getText();
- String receivedClassNameAndText = current.getClassName().toString()
- + ((text != null) ? text.toString() : "");
- String expectedClassNameAndText = classNameAndTextList.remove(0);
-
- assertEquals("Did not get the expected node info",
- expectedClassNameAndText, receivedClassNameAndText);
- }
-
- final int childCount = current.getChildCount();
- for (int i = 0; i < childCount; i++) {
- AccessibilityNodeInfo child = current.getChild(i);
- fringe.add(child);
- }
+ // Click the button.
+ uiAutomation.executeAndWaitForEvent(new Runnable() {
+ @Override
+ public void run() {
+ button1.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
+ },
+ new UiAutomation.AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent event) {
+ if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_CLICKED) {
+ events.add(event);
+ return true;
+ }
+ return false;
+ }
+ },
+ TIMEOUT_ASYNC_PROCESSING);
+
+ // Make sure the source window cannot be accessed.
+ AccessibilityEvent event = events.get(0);
+ assertNull(event.getSource().getWindow());
+ }
+
+ @MediumTest
+ public void testTraverseAllWindows() throws Exception {
+ setAccessInteractiveWindowsFlag();
+ try {
+ final UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+
+ getInstrumentation().getUiAutomation().waitForIdle(
+ TIMEOUT_WINDOW_STATE_IDLE,
+ TIMEOUT_ASYNC_PROCESSING);
+
+ List<AccessibilityWindowInfo> windows = uiAutomation.getWindows();
+
+ Rect boundsInScreen = new Rect();
+
+ // Verify the navigation bar window.
+ AccessibilityWindowInfo navBarWindow = windows.get(0);
+ navBarWindow.getBoundsInScreen(boundsInScreen);
+ assertFalse(boundsInScreen.isEmpty()); // Varies on screen size, just emptiness check.
+ assertSame(navBarWindow.getType(), AccessibilityWindowInfo.TYPE_SYSTEM);
+ assertFalse(navBarWindow.isFocused());
+ assertFalse(navBarWindow.isActive());
+ assertNull(navBarWindow.getParent());
+ assertSame(0, navBarWindow.getChildCount());
+ assertNotNull(navBarWindow.getRoot());
+
+ // Verify the status bar window.
+ AccessibilityWindowInfo statusBarWindow = windows.get(1);
+ statusBarWindow.getBoundsInScreen(boundsInScreen);
+ assertFalse(boundsInScreen.isEmpty()); // Varies on screen size, just emptiness check.
+ assertSame(statusBarWindow.getType(), AccessibilityWindowInfo.TYPE_SYSTEM);
+ assertFalse(statusBarWindow.isFocused());
+ assertFalse(statusBarWindow.isActive());
+ assertNull(statusBarWindow.getParent());
+ assertSame(0, statusBarWindow.getChildCount());
+ assertNotNull(statusBarWindow.getRoot());
+
+ // Verify the application window.
+ AccessibilityWindowInfo appWindow = windows.get(2);
+ appWindow.getBoundsInScreen(boundsInScreen);
+ assertFalse(boundsInScreen.isEmpty()); // Varies on screen size, just emptiness check.
+ assertSame(appWindow.getType(), AccessibilityWindowInfo.TYPE_APPLICATION);
+ assertTrue(appWindow.isFocused());
+ assertTrue(appWindow.isActive());
+ assertNull(appWindow.getParent());
+ assertSame(0, appWindow.getChildCount());
+ assertNotNull(appWindow.getRoot());
+
+ verifyNodesInAppWindow(appWindow.getRoot());
} finally {
- AccessibilityServiceInfo info = getInstrumentation().getUiAutomation().getServiceInfo();
- info.flags &= ~AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
- getInstrumentation().getUiAutomation().setServiceInfo(info);
+ clearAccessInteractiveWindowsFlag();
+ }
+ }
+
+ @MediumTest
+ public void testTraverseWindowFromEvent() throws Exception {
+ setAccessInteractiveWindowsFlag();
+ try {
+ UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+
+ // Find a button to click on.
+ final AccessibilityNodeInfo button1 = uiAutomation.getRootInActiveWindow()
+ .findAccessibilityNodeInfosByViewId(
+ "com.android.cts.accessibilityservice:id/button1").get(0);
+
+ // Argh...
+ final List<AccessibilityEvent> events = new ArrayList<AccessibilityEvent>();
+
+ // Click the button.
+ uiAutomation.executeAndWaitForEvent(new Runnable() {
+ @Override
+ public void run() {
+ button1.performAction(AccessibilityNodeInfo.ACTION_CLICK);
+ }
+ },
+ new UiAutomation.AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent event) {
+ if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_CLICKED) {
+ events.add(event);
+ return true;
+ }
+ return false;
+ }
+ },
+ TIMEOUT_ASYNC_PROCESSING);
+
+ // Get the source window.
+ AccessibilityEvent event = events.get(0);
+ AccessibilityWindowInfo window = event.getSource().getWindow();
+
+ // Verify the application window.
+ Rect boundsInScreen = new Rect();
+ window.getBoundsInScreen(boundsInScreen);
+ assertFalse(boundsInScreen.isEmpty()); // Varies on screen size, so just emptiness check.
+ assertSame(window.getType(), AccessibilityWindowInfo.TYPE_APPLICATION);
+ assertTrue(window.isFocused());
+ assertTrue(window.isActive());
+ assertNull(window.getParent());
+ assertSame(0, window.getChildCount());
+ assertNotNull(window.getRoot());
+
+ // Verify the window content.
+ verifyNodesInAppWindow(window.getRoot());
+ } finally {
+ clearAccessInteractiveWindowsFlag();
+ }
+ }
+
+ @MediumTest
+ public void testInteractWithAppWindow() throws Exception {
+ setAccessInteractiveWindowsFlag();
+ try {
+ UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+
+ // Find a button to click on.
+ final AccessibilityNodeInfo button1 = uiAutomation.getRootInActiveWindow()
+ .findAccessibilityNodeInfosByViewId(
+ "com.android.cts.accessibilityservice:id/button1").get(0);
+
+ // Argh...
+ final List<AccessibilityEvent> events = new ArrayList<AccessibilityEvent>();
+
+ // Click the button.
+ uiAutomation.executeAndWaitForEvent(new Runnable() {
+ @Override
+ public void run() {
+ button1.performAction(AccessibilityNodeInfo.ACTION_CLICK);
+ }
+ },
+ new UiAutomation.AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent event) {
+ if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_CLICKED) {
+ events.add(event);
+ return true;
+ }
+ return false;
+ }
+ },
+ TIMEOUT_ASYNC_PROCESSING);
+
+ // Get the source window.
+ AccessibilityEvent event = events.get(0);
+ AccessibilityWindowInfo window = event.getSource().getWindow();
+
+ // Find a another button from the event's window.
+ final AccessibilityNodeInfo button2 = window.getRoot()
+ .findAccessibilityNodeInfosByViewId(
+ "com.android.cts.accessibilityservice:id/button2").get(0);
+
+ // Click the second button.
+ uiAutomation.executeAndWaitForEvent(new Runnable() {
+ @Override
+ public void run() {
+ button2.performAction(AccessibilityNodeInfo.ACTION_CLICK);
+ }
+ },
+ new UiAutomation.AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent event) {
+ return event.getEventType() == AccessibilityEvent.TYPE_VIEW_CLICKED;
+ }
+ },
+ TIMEOUT_ASYNC_PROCESSING);
+ } finally {
+ clearAccessInteractiveWindowsFlag();
+ }
+ }
+
+ @MediumTest
+ public void testInteractWithNavBarWindow() throws Exception {
+ setAccessInteractiveWindowsFlag();
+ try {
+ UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+ AccessibilityWindowInfo window = uiAutomation.getWindows().get(0);
+ assertTrue(window.getRoot().performAction(
+ AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS));
+ } finally {
+ clearAccessInteractiveWindowsFlag();
+ }
+ }
+
+ @MediumTest
+ public void testInteractWithStatusBarWindow() throws Exception {
+ setAccessInteractiveWindowsFlag();
+ try {
+ UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+ AccessibilityWindowInfo window = uiAutomation.getWindows().get(1);
+ assertTrue(window.getRoot().performAction(
+ AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS));
+ } finally {
+ clearAccessInteractiveWindowsFlag();
+ }
+ }
+
+ @MediumTest
+ public void testSingleAccessibilityFocusAcrossWindows() throws Exception {
+ setAccessInteractiveWindowsFlag();
+ try {
+ UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+
+ uiAutomation.waitForIdle(TIMEOUT_WINDOW_STATE_IDLE, TIMEOUT_ASYNC_PROCESSING);
+
+ List<AccessibilityWindowInfo> windows = uiAutomation.getWindows();
+
+ AccessibilityNodeInfo firstWindowRoot = windows.get(0).getRoot();
+ AccessibilityNodeInfo secondWindowRoot = windows.get(1).getRoot();
+ AccessibilityNodeInfo thirdWindowRoot = windows.get(2).getRoot();
+
+
+ // Set focus in the first window.
+ assertTrue(firstWindowRoot.performAction(
+ AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS));
+
+ // Wait for things to settle.
+ uiAutomation.waitForIdle(TIMEOUT_WINDOW_STATE_IDLE, TIMEOUT_ASYNC_PROCESSING);
+
+ // Make sure there only one accessibility focus.
+ assertEquals(uiAutomation.findFocus(
+ AccessibilityNodeInfo.FOCUS_ACCESSIBILITY), firstWindowRoot);
+ assertEquals(firstWindowRoot.findFocus(
+ AccessibilityNodeInfo.FOCUS_ACCESSIBILITY), firstWindowRoot);
+ assertNull(secondWindowRoot.findFocus(
+ AccessibilityNodeInfo.FOCUS_ACCESSIBILITY));
+ assertNull(thirdWindowRoot.findFocus(
+ AccessibilityNodeInfo.FOCUS_ACCESSIBILITY));
+
+
+ // Set focus in the second window.
+ assertTrue(secondWindowRoot.performAction(
+ AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS));
+
+ // Wait for things to settle.
+ uiAutomation.waitForIdle(TIMEOUT_WINDOW_STATE_IDLE, TIMEOUT_ASYNC_PROCESSING);
+
+ // Make sure there only one accessibility focus.
+ assertEquals(uiAutomation.findFocus(
+ AccessibilityNodeInfo.FOCUS_ACCESSIBILITY), secondWindowRoot);
+ assertEquals(secondWindowRoot.findFocus(
+ AccessibilityNodeInfo.FOCUS_ACCESSIBILITY), secondWindowRoot);
+ assertNull(firstWindowRoot.findFocus(
+ AccessibilityNodeInfo.FOCUS_ACCESSIBILITY));
+ assertNull(thirdWindowRoot.findFocus(
+ AccessibilityNodeInfo.FOCUS_ACCESSIBILITY));
+
+
+ // Set focus in the third window.
+ assertTrue(thirdWindowRoot.performAction(
+ AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS));
+
+ // Wait for things to settle.
+ uiAutomation.waitForIdle(TIMEOUT_WINDOW_STATE_IDLE, TIMEOUT_ASYNC_PROCESSING);
+
+ // Make sure there only one accessibility focus.
+ assertEquals(uiAutomation.findFocus(
+ AccessibilityNodeInfo.FOCUS_ACCESSIBILITY), thirdWindowRoot);
+ assertEquals(thirdWindowRoot.findFocus(
+ AccessibilityNodeInfo.FOCUS_ACCESSIBILITY), thirdWindowRoot);
+ assertNull(firstWindowRoot.findFocus(
+ AccessibilityNodeInfo.FOCUS_ACCESSIBILITY));
+ assertNull(secondWindowRoot.findFocus(
+ AccessibilityNodeInfo.FOCUS_ACCESSIBILITY));
+
+
+ // Clear focus.
+ assertTrue(thirdWindowRoot.performAction(
+ AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS));
+
+ // Make sure there is not accessibility focus.
+ assertNull(uiAutomation.findFocus(
+ AccessibilityNodeInfo.FOCUS_ACCESSIBILITY));
+ assertNull(firstWindowRoot.findFocus(
+ AccessibilityNodeInfo.FOCUS_ACCESSIBILITY));
+ assertNull(secondWindowRoot.findFocus(
+ AccessibilityNodeInfo.FOCUS_ACCESSIBILITY));
+ assertNull(thirdWindowRoot.findFocus(
+ AccessibilityNodeInfo.FOCUS_ACCESSIBILITY));
+ } finally {
+ clearAccessInteractiveWindowsFlag();
}
}
@@ -267,6 +535,28 @@
assertNotNull(expected);
}
+
+ @MediumTest
+ public void testPerformCustomAction() throws Exception {
+ // find a view and make sure it is not selected
+ AccessibilityNodeInfo button = getInstrumentation().getUiAutomation()
+ .getRootInActiveWindow().findAccessibilityNodeInfosByText(
+ getString(R.string.button5)).get(0);
+
+ // find the custom action and perform it
+ List<AccessibilityAction> actions = button.getActionList();
+ final int actionCount = actions.size();
+ for (int i = 0; i < actionCount; i++) {
+ AccessibilityAction action = actions.get(i);
+ if (action.getId() == R.id.foo_custom_action) {
+ assertSame(action.getLabel(), "Foo");
+ // perform the action
+ assertTrue(button.performAction(action.getId()));
+ return;
+ }
+ }
+ }
+
@MediumTest
public void testGetEventSource() throws Exception {
// find a view and make sure it is not focused
@@ -426,6 +716,82 @@
}
}
+ private void setAccessInteractiveWindowsFlag () {
+ UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+ AccessibilityServiceInfo info = uiAutomation.getServiceInfo();
+ info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
+ uiAutomation.setServiceInfo(info);
+ }
+
+ private void clearAccessInteractiveWindowsFlag () {
+ UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+ AccessibilityServiceInfo info = uiAutomation.getServiceInfo();
+ info.flags &= ~AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
+ uiAutomation.setServiceInfo(info);
+ }
+
+ private void verifyNodesInAppWindow(AccessibilityNodeInfo root) throws Exception {
+ try {
+ AccessibilityServiceInfo info = getInstrumentation().getUiAutomation().getServiceInfo();
+ info.flags |= AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
+ getInstrumentation().getUiAutomation().setServiceInfo(info);
+
+ root.refresh();
+
+ // make list of expected nodes
+ List<String> classNameAndTextList = new ArrayList<String>();
+ classNameAndTextList.add("android.widget.LinearLayout");
+ classNameAndTextList.add("android.widget.LinearLayout");
+ classNameAndTextList.add("android.widget.LinearLayout");
+ classNameAndTextList.add("android.widget.LinearLayout");
+ classNameAndTextList.add("android.widget.ButtonB1");
+ classNameAndTextList.add("android.widget.ButtonB2");
+ classNameAndTextList.add("android.widget.ButtonB3");
+ classNameAndTextList.add("android.widget.ButtonB4");
+ classNameAndTextList.add("android.widget.ButtonB5");
+ classNameAndTextList.add("android.widget.ButtonB6");
+ classNameAndTextList.add("android.widget.ButtonB7");
+ classNameAndTextList.add("android.widget.ButtonB8");
+ classNameAndTextList.add("android.widget.ButtonB9");
+
+ String contentViewIdResName = "com.android.cts.accessibilityservice:id/added_content";
+ boolean verifyContent = false;
+
+ Queue<AccessibilityNodeInfo> fringe = new LinkedList<AccessibilityNodeInfo>();
+ fringe.add(root);
+
+ // do a BFS traversal and check nodes
+ while (!fringe.isEmpty()) {
+ AccessibilityNodeInfo current = fringe.poll();
+
+ if (!verifyContent
+ && contentViewIdResName.equals(current.getViewIdResourceName())) {
+ verifyContent = true;
+ }
+
+ if (verifyContent) {
+ CharSequence text = current.getText();
+ String receivedClassNameAndText = current.getClassName().toString()
+ + ((text != null) ? text.toString() : "");
+ String expectedClassNameAndText = classNameAndTextList.remove(0);
+
+ assertEquals("Did not get the expected node info",
+ expectedClassNameAndText, receivedClassNameAndText);
+ }
+
+ final int childCount = current.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ AccessibilityNodeInfo child = current.getChild(i);
+ fringe.add(child);
+ }
+ }
+ } finally {
+ AccessibilityServiceInfo info = getInstrumentation().getUiAutomation().getServiceInfo();
+ info.flags &= ~AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
+ getInstrumentation().getUiAutomation().setServiceInfo(info);
+ }
+ }
+
@Override
protected void scrubClass(Class<?> testCaseClass) {
/* intentionally do not scrub */
diff --git a/tests/tests/accounts/Android.mk b/tests/tests/accounts/Android.mk
index e4536d4..39dbfb1 100644
--- a/tests/tests/accounts/Android.mk
+++ b/tests/tests/accounts/Android.mk
@@ -21,12 +21,12 @@
# and when built explicitly put it in the data partition
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES += android-common ctstestrunner
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := CtsAccountManagerTestCases
+LOCAL_SDK_VERSION := current
+
include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/accounts/AndroidManifest.xml b/tests/tests/accounts/AndroidManifest.xml
index 6020636..cf7f7d8 100644
--- a/tests/tests/accounts/AndroidManifest.xml
+++ b/tests/tests/accounts/AndroidManifest.xml
@@ -39,9 +39,12 @@
</service>
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="android.accounts.cts"
- android:label="CTS tests for android.accounts"/>
+ android:label="CTS tests for android.accounts">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/tests/admin/Android.mk b/tests/tests/admin/Android.mk
index c3645cc..7a5ae34 100644
--- a/tests/tests/admin/Android.mk
+++ b/tests/tests/admin/Android.mk
@@ -20,8 +20,6 @@
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner mockito-target
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/admin/AndroidManifest.xml b/tests/tests/admin/AndroidManifest.xml
index 7ce29aa..bbd7918 100644
--- a/tests/tests/admin/AndroidManifest.xml
+++ b/tests/tests/admin/AndroidManifest.xml
@@ -26,8 +26,11 @@
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="android.deviceadmin.cts"
- android:label="Tests for the admin APIs."/>
+ android:label="Tests for the admin APIs.">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/tests/animation/Android.mk b/tests/tests/animation/Android.mk
index a83bb65..3d8daf7 100644
--- a/tests/tests/animation/Android.mk
+++ b/tests/tests/animation/Android.mk
@@ -24,9 +24,6 @@
# When built, explicitly put it in the data partition.
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-# All tests should include android.test.runner.
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/animation/AndroidManifest.xml b/tests/tests/animation/AndroidManifest.xml
index 2212643..fdc5ad9 100644
--- a/tests/tests/animation/AndroidManifest.xml
+++ b/tests/tests/animation/AndroidManifest.xml
@@ -27,8 +27,11 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.animation"
- android:label="CTS tests for android.animation package"/>
+ android:label="CTS tests for android.animation package">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/tests/app/AndroidManifest.xml b/tests/tests/app/AndroidManifest.xml
index acfc3c8..134df64 100644
--- a/tests/tests/app/AndroidManifest.xml
+++ b/tests/tests/app/AndroidManifest.xml
@@ -23,9 +23,12 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.stub"
- android:label="CTS tests of android.app"/>
+ android:label="CTS tests of android.app">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/tests/app/src/android/app/cts/InstrumentationTest.java b/tests/tests/app/src/android/app/cts/InstrumentationTest.java
index 30cdd5f..16a4ee1 100644
--- a/tests/tests/app/src/android/app/cts/InstrumentationTest.java
+++ b/tests/tests/app/src/android/app/cts/InstrumentationTest.java
@@ -667,6 +667,24 @@
@Override
public void takeInputQueue(InputQueue.Callback queue) {
}
+
+ @Override
+ public void setStatusBarColor(int color) {
+ }
+
+ @Override
+ public int getStatusBarColor() {
+ return 0;
+ }
+
+ @Override
+ public void setNavigationBarColor(int color) {
+ }
+
+ @Override
+ public int getNavigationBarColor() {
+ return 0;
+ }
}
}
diff --git a/tests/tests/app/src/android/app/cts/ServiceTest.java b/tests/tests/app/src/android/app/cts/ServiceTest.java
index f9a81e6..675b7ae 100644
--- a/tests/tests/app/src/android/app/cts/ServiceTest.java
+++ b/tests/tests/app/src/android/app/cts/ServiceTest.java
@@ -16,8 +16,6 @@
package android.app.cts;
-
-import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -51,9 +49,11 @@
private IBinder mStateReceiver;
private static class EmptyConnection implements ServiceConnection {
+ @Override
public void onServiceConnected(ComponentName name, IBinder service) {
}
+ @Override
public void onServiceDisconnected(ComponentName name) {
}
}
@@ -74,6 +74,7 @@
mMonitor = v;
}
+ @Override
public void onServiceConnected(ComponentName name, IBinder service) {
if (mSetReporter) {
Parcel data = Parcel.obtain();
@@ -109,6 +110,7 @@
}
}
+ @Override
public void onServiceDisconnected(ComponentName name) {
if (mMonitor) {
if (mExpectedServiceState == STATE_DESTROY) {
@@ -150,15 +152,6 @@
waitForResultOrThrow(DELAY, "service to be destroyed");
}
- private void startExpectNoPermission(Intent service) {
- try {
- mContext.startService(service);
- fail("Expected security exception when starting " + service);
- } catch (SecurityException e) {
- // expected
- }
- }
-
/**
* test the service lifecycle, a service can be used in two ways:
* 1 It can be started and allowed to run until someone stops it or it stops itself.
@@ -294,18 +287,6 @@
waitForResultOrThrow(DELAY, "disconnecting from service");
}
- private void bindExpectNoPermission(Intent service) {
- TestConnection conn = new TestConnection(false, false);
- try {
- mContext.bindService(service, conn, Context.BIND_AUTO_CREATE);
- fail("Expected security exception when binding " + service);
- } catch (SecurityException e) {
- // expected
- } finally {
- mContext.unbindService(conn);
- }
- }
-
@Override
protected void setUp() throws Exception {
super.setUp();
@@ -313,8 +294,10 @@
mLocalService = new Intent(mContext, LocalService.class);
mLocalDeniedService = new Intent(mContext, LocalDeniedService.class);
mLocalGrantedService = new Intent(mContext, LocalGrantedService.class);
- mLocalService_ApplicationHasPermission = new Intent(LocalService.SERVICE_LOCAL_GRANTED);
- mLocalService_ApplicationDoesNotHavePermission = new Intent(LocalService.SERVICE_LOCAL_DENIED);
+ mLocalService_ApplicationHasPermission = new Intent(
+ LocalService.SERVICE_LOCAL_GRANTED, null /*uri*/, mContext, LocalService.class);
+ mLocalService_ApplicationDoesNotHavePermission = new Intent(
+ LocalService.SERVICE_LOCAL_DENIED, null /*uri*/, mContext, LocalService.class);
mStateReceiver = new MockBinder();
}
@@ -392,7 +375,8 @@
}
public void testLocalStartAction() throws Exception {
- startExpectResult(new Intent(LocalService.SERVICE_LOCAL));
+ startExpectResult(new Intent(
+ LocalService.SERVICE_LOCAL, null /*uri*/, mContext, LocalService.class));
}
public void testLocalBindClass() throws Exception {
@@ -401,7 +385,8 @@
@MediumTest
public void testLocalBindAction() throws Exception {
- bindExpectResult(new Intent(LocalService.SERVICE_LOCAL));
+ bindExpectResult(new Intent(
+ LocalService.SERVICE_LOCAL, null /*uri*/, mContext, LocalService.class));
}
@MediumTest
@@ -411,7 +396,8 @@
@MediumTest
public void testLocalBindAutoAction() throws Exception {
- bindAutoExpectResult(new Intent(LocalService.SERVICE_LOCAL));
+ bindAutoExpectResult(new Intent(
+ LocalService.SERVICE_LOCAL, null /*uri*/, mContext, LocalService.class));
}
@MediumTest
diff --git a/tests/tests/bluetooth/Android.mk b/tests/tests/bluetooth/Android.mk
index 701730d..4f837fc 100644
--- a/tests/tests/bluetooth/Android.mk
+++ b/tests/tests/bluetooth/Android.mk
@@ -18,16 +18,12 @@
LOCAL_PACKAGE_NAME := CtsBluetoothTestCases
-
# Don't include this package in any target.
LOCAL_MODULE_TAGS := optional
# When built, explicitly put it in the data partition.
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-# All tests should include android.test.runner.
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/bluetooth/AndroidManifest.xml b/tests/tests/bluetooth/AndroidManifest.xml
index 9caa267..c9ad122 100644
--- a/tests/tests/bluetooth/AndroidManifest.xml
+++ b/tests/tests/bluetooth/AndroidManifest.xml
@@ -26,9 +26,12 @@
</application>
<!-- This is a self-instrumenting test package. -->
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.bluetooth"
- android:label="CTS tests of bluetooth component"/>
+ android:label="CTS tests of bluetooth component">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/tests/calendarcommon/Android.mk b/tests/tests/calendarcommon/Android.mk
index c825c32..fa4b6c5 100644
--- a/tests/tests/calendarcommon/Android.mk
+++ b/tests/tests/calendarcommon/Android.mk
@@ -25,9 +25,6 @@
# When built, explicitly put it in the data partition.
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-# All tests should include android.test.runner.
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/calendarcommon/AndroidManifest.xml b/tests/tests/calendarcommon/AndroidManifest.xml
index dc95af5..17520a3 100644
--- a/tests/tests/calendarcommon/AndroidManifest.xml
+++ b/tests/tests/calendarcommon/AndroidManifest.xml
@@ -23,9 +23,12 @@
</application>
<!-- This is a self-instrumenting test package. -->
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.calendarcommon2"
- android:label="CTS tests of calendarcommon"/>
+ android:label="CTS tests of calendarcommon">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
<uses-sdk android:minSdkVersion="15" android:targetSdkVersion="15"></uses-sdk>
diff --git a/tests/tests/calendarcommon/src/android/calendarcommon2/cts/Calendarcommon2Test.java b/tests/tests/calendarcommon/src/android/calendarcommon2/cts/Calendarcommon2Test.java
index a17e3b8..24a04a5 100644
--- a/tests/tests/calendarcommon/src/android/calendarcommon2/cts/Calendarcommon2Test.java
+++ b/tests/tests/calendarcommon/src/android/calendarcommon2/cts/Calendarcommon2Test.java
@@ -16,7 +16,6 @@
package android.calendarcommon2.cts;
-import android.test.InstrumentationCtsTestRunner;
import android.test.InstrumentationTestCase;
import android.test.suitebuilder.annotation.MediumTest;
import com.android.calendarcommon2.RecurrenceSet;
diff --git a/tests/tests/content/AndroidManifest.xml b/tests/tests/content/AndroidManifest.xml
index 5af05eb..8d57e49 100644
--- a/tests/tests/content/AndroidManifest.xml
+++ b/tests/tests/content/AndroidManifest.xml
@@ -39,8 +39,11 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.stub"
- android:label="CTS tests of android.content"/>
+ android:label="CTS tests of android.content">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/tests/content/src/android/content/res/cts/ConfigTest.java b/tests/tests/content/src/android/content/res/cts/ConfigTest.java
index bcfabe1..2f892b0 100644
--- a/tests/tests/content/src/android/content/res/cts/ConfigTest.java
+++ b/tests/tests/content/src/android/content/res/cts/ConfigTest.java
@@ -35,6 +35,8 @@
enum Properties {
LANGUAGE,
COUNTRY,
+ SCRIPT,
+ VARIANT,
MCC,
MNC,
TOUCHSCREEN,
@@ -88,7 +90,7 @@
public TotalConfig() {
mConfig = new Configuration();
mMetrics = new DisplayMetrics();
- mConfig.locale = new Locale("++", "++");
+ mConfig.locale = Locale.ROOT;
}
public void setProperty(final Properties p, final int value) {
@@ -149,12 +151,28 @@
public void setProperty(final Properties p, final String value) {
switch(p) {
case LANGUAGE:
- final String oldCountry = mConfig.locale.getCountry();
- mConfig.locale = new Locale(value, oldCountry);
+ mConfig.locale = new Locale.Builder()
+ .setLocale(mConfig.locale)
+ .setLanguage(value)
+ .build();
break;
case COUNTRY:
- final String oldLanguage = mConfig.locale.getLanguage();
- mConfig.locale = new Locale(oldLanguage, value);
+ mConfig.locale = new Locale.Builder()
+ .setLocale(mConfig.locale)
+ .setRegion(value)
+ .build();
+ break;
+ case SCRIPT:
+ mConfig.locale = new Locale.Builder()
+ .setLocale(mConfig.locale)
+ .setScript(value)
+ .build();
+ break;
+ case VARIANT:
+ mConfig.locale = new Locale.Builder()
+ .setLocale(mConfig.locale)
+ .setVariant(value)
+ .build();
break;
default:
assert(false);
@@ -1131,4 +1149,69 @@
assertEquals("base", mContext.getResources().getString(R.string.version_old));
assertEquals("v3", mContext.getResources().getString(R.string.version_v3));
}
+
+ @MediumTest
+ public void testExtendedLocales() {
+ TotalConfig config = makeClassicConfig();
+ // BCP 47 Locale kok
+ config.setProperty(Properties.LANGUAGE, "kok");
+ Resources res = config.getResources();
+ checkValue(res, R.configVarying.simple, "simple kok");
+ checkValue(res, R.configVarying.bag,
+ R.styleable.TestConfig, new String[]{"bag kok"});
+
+ // BCP 47 Locale kok-IN
+ config.setProperty(Properties.COUNTRY, "IN");
+ res = config.getResources();
+ checkValue(res, R.configVarying.simple, "simple kok IN");
+ checkValue(res, R.configVarying.bag,
+ R.styleable.TestConfig, new String[]{"bag kok IN"});
+
+ // BCP 47 Locale kok-419
+ config.setProperty(Properties.COUNTRY, "419");
+ res = config.getResources();
+ checkValue(res, R.configVarying.simple, "simple kok 419");
+ checkValue(res, R.configVarying.bag,
+ R.styleable.TestConfig, new String[]{"bag kok 419"});
+
+
+ // BCP 47 Locale kok-419-VARIANT
+ config.setProperty(Properties.VARIANT, "VARIANT");
+ res = config.getResources();
+ checkValue(res, R.configVarying.simple, "simple kok 419 VARIANT");
+ checkValue(res, R.configVarying.bag,
+ R.styleable.TestConfig, new String[]{"bag kok 419 VARIANT"});
+
+ // BCP 47 Locale kok-Knda
+ config = makeClassicConfig();
+ config.setProperty(Properties.LANGUAGE, "kok");
+ config.setProperty(Properties.SCRIPT, "Knda");
+ res = config.getResources();
+ checkValue(res, R.configVarying.simple, "simple kok Knda");
+ checkValue(res, R.configVarying.bag,
+ R.styleable.TestConfig, new String[]{"bag kok Knda"});
+
+ // BCP 47 Locale kok-Knda-419
+ config.setProperty(Properties.COUNTRY, "419");
+ res = config.getResources();
+ checkValue(res, R.configVarying.simple, "simple kok Knda 419");
+ checkValue(res, R.configVarying.bag,
+ R.styleable.TestConfig, new String[]{"bag kok Knda 419"});
+
+ // BCP 47 Locale kok-Knda-419-VARIANT
+ config.setProperty(Properties.VARIANT, "VARIANT");
+ res = config.getResources();
+ checkValue(res, R.configVarying.simple, "simple kok Knda 419 VARIANT");
+ checkValue(res, R.configVarying.bag,
+ R.styleable.TestConfig, new String[]{"bag kok Knda 419 VARIANT"});
+
+ // BCP 47 Locale kok-VARIANT
+ config = makeClassicConfig();
+ config.setProperty(Properties.LANGUAGE, "kok");
+ config.setProperty(Properties.VARIANT, "VARIANT");
+ res = config.getResources();
+ checkValue(res, R.configVarying.simple, "simple kok VARIANT");
+ checkValue(res, R.configVarying.bag,
+ R.styleable.TestConfig, new String[]{"bag kok VARIANT"});
+ }
}
diff --git a/tests/tests/database/AndroidManifest.xml b/tests/tests/database/AndroidManifest.xml
index 602f783..fefcc1f 100644
--- a/tests/tests/database/AndroidManifest.xml
+++ b/tests/tests/database/AndroidManifest.xml
@@ -23,9 +23,12 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.database"
- android:label="CTS tests of android.database"/>
+ android:label="CTS tests of android.database">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/tests/display/Android.mk b/tests/tests/display/Android.mk
index ec5b40d..a48a8e3 100644
--- a/tests/tests/display/Android.mk
+++ b/tests/tests/display/Android.mk
@@ -21,8 +21,6 @@
# and when built explicitly put it in the data partition
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/display/AndroidManifest.xml b/tests/tests/display/AndroidManifest.xml
index d1386d1..0b24754 100644
--- a/tests/tests/display/AndroidManifest.xml
+++ b/tests/tests/display/AndroidManifest.xml
@@ -24,9 +24,13 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.display"
- android:label="CTS tests of android.view.display"/>
+ android:label="CTS tests of android.view.display">
+ <meta-data
+ android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/tests/dpi/Android.mk b/tests/tests/dpi/Android.mk
index a9dbcc3..fde990b 100644
--- a/tests/tests/dpi/Android.mk
+++ b/tests/tests/dpi/Android.mk
@@ -17,8 +17,6 @@
include $(CLEAR_VARS)
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
LOCAL_SRC_FILES := $(call all-java-files-under, src)
@@ -44,8 +42,8 @@
LOCAL_MODULE_TAGS := optional
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_MODULE := android.cts.dpi
+LOCAL_SDK_VERSION := current
+
include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tests/tests/dpi/AndroidManifest.xml b/tests/tests/dpi/AndroidManifest.xml
index bacfe4a..0197056 100644
--- a/tests/tests/dpi/AndroidManifest.xml
+++ b/tests/tests/dpi/AndroidManifest.xml
@@ -26,7 +26,10 @@
android:configChanges="orientation" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.dpi"
- android:label="CTS tests for DPI"/>
+ android:label="CTS tests for DPI">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/tests/dpi2/Android.mk b/tests/tests/dpi2/Android.mk
index 92ba992..03a687d 100644
--- a/tests/tests/dpi2/Android.mk
+++ b/tests/tests/dpi2/Android.mk
@@ -17,7 +17,6 @@
include $(CLEAR_VARS)
-LOCAL_JAVA_LIBRARIES := android.test.runner
# We use the DefaultManifestAttributesTest from the android.cts.dpi package.
LOCAL_STATIC_JAVA_LIBRARIES := android.cts.dpi ctstestrunner
@@ -29,8 +28,6 @@
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-# We would set LOCAL_SDK_VERSION := 3 here, but the build system
-# doesn't currently support setting LOCAL_SDK_VERSION to anything but
-# current.
+LOCAL_SDK_VERSION := current
include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/dpi2/AndroidManifest.xml b/tests/tests/dpi2/AndroidManifest.xml
index 0364b10..6dbdc23 100644
--- a/tests/tests/dpi2/AndroidManifest.xml
+++ b/tests/tests/dpi2/AndroidManifest.xml
@@ -27,7 +27,10 @@
properly for the screen size attributes. -->
<uses-sdk android:targetSdkVersion="3" />
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.dpi2"
- android:label="CTS tests for DPI"/>
+ android:label="CTS tests for DPI">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/tests/dreams/Android.mk b/tests/tests/dreams/Android.mk
index c630d8a..eca3d83 100644
--- a/tests/tests/dreams/Android.mk
+++ b/tests/tests/dreams/Android.mk
@@ -24,14 +24,11 @@
# When built, explicitly put it in the data partition.
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-# All tests should include android.test.runner.
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-# Need access to ServiceManager
+# Need access to ServiceManager - see b/13307221
#LOCAL_SDK_VERSION := current
include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/dreams/AndroidManifest.xml b/tests/tests/dreams/AndroidManifest.xml
index fb3e564..b395a4f 100644
--- a/tests/tests/dreams/AndroidManifest.xml
+++ b/tests/tests/dreams/AndroidManifest.xml
@@ -22,9 +22,12 @@
</application>
<!-- This is a self-instrumenting test package. -->
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.dreams"
- android:label="CTS tests for the android.service.dreams package"/>
+ android:label="CTS tests for the android.service.dreams package">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/tests/drm/Android.mk b/tests/tests/drm/Android.mk
index 8b76cd8..9404f4b 100644
--- a/tests/tests/drm/Android.mk
+++ b/tests/tests/drm/Android.mk
@@ -21,8 +21,6 @@
# and when built explicitly put it in the data partition
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
LOCAL_SRC_FILES := $(call all-java-files-under, src)
@@ -35,8 +33,7 @@
libctsdrm_jni \
libdrmtestplugin
-# uncomment when dalvik.annotation.Test* are removed or part of SDK
-#LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := current
include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/drm/AndroidManifest.xml b/tests/tests/drm/AndroidManifest.xml
index 1fc8968..dd70f02 100644
--- a/tests/tests/drm/AndroidManifest.xml
+++ b/tests/tests/drm/AndroidManifest.xml
@@ -22,9 +22,12 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.stub"
- android:label="CTS tests of android.drm"/>
+ android:label="CTS tests of android.drm">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/tests/effect/Android.mk b/tests/tests/effect/Android.mk
index 9e27769..6a9778e 100644
--- a/tests/tests/effect/Android.mk
+++ b/tests/tests/effect/Android.mk
@@ -24,9 +24,6 @@
# When built, explicitly put it in the data partition.
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-# All tests should include android.test.runner.
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/effect/AndroidManifest.xml b/tests/tests/effect/AndroidManifest.xml
index 1a346ae..481be14 100644
--- a/tests/tests/effect/AndroidManifest.xml
+++ b/tests/tests/effect/AndroidManifest.xml
@@ -23,9 +23,12 @@
</application>
<!-- This is a self-instrumenting test package. -->
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.effect"
- android:label="CTS tests of android.media.effect component"/>
+ android:label="CTS tests of android.media.effect component">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/tests/gesture/Android.mk b/tests/tests/gesture/Android.mk
index 5d44cfc..4a97931 100755
--- a/tests/tests/gesture/Android.mk
+++ b/tests/tests/gesture/Android.mk
@@ -21,8 +21,6 @@
# and when built explicitly put it in the data partition
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/gesture/AndroidManifest.xml b/tests/tests/gesture/AndroidManifest.xml
index 39e2b90..b288cd2 100755
--- a/tests/tests/gesture/AndroidManifest.xml
+++ b/tests/tests/gesture/AndroidManifest.xml
@@ -24,9 +24,12 @@
</application>
<!-- self-instrumenting test package. -->
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.gesture"
- android:label="CTS tests of android.gesture"/>
+ android:label="CTS tests of android.gesture">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/tests/graphics/Android.mk b/tests/tests/graphics/Android.mk
index 811267a..cad81b2 100644
--- a/tests/tests/graphics/Android.mk
+++ b/tests/tests/graphics/Android.mk
@@ -21,8 +21,6 @@
# and when built explicitly put it in the data partition
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
LOCAL_SRC_FILES := $(call all-java-files-under, src)
@@ -31,7 +29,6 @@
LOCAL_INSTRUMENTATION_FOR := CtsTestStubs
-# uncomment when dalvik.annotation.Test* are removed or part of SDK
-#LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := current
include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/graphics/AndroidManifest.xml b/tests/tests/graphics/AndroidManifest.xml
index c052a15..0371093 100644
--- a/tests/tests/graphics/AndroidManifest.xml
+++ b/tests/tests/graphics/AndroidManifest.xml
@@ -23,9 +23,12 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.stub"
- android:label="CTS tests of android.graphics"/>
+ android:label="CTS tests of android.graphics">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/tests/graphics/src/android/graphics/cts/BitmapFactoryTest.java b/tests/tests/graphics/src/android/graphics/cts/BitmapFactoryTest.java
index 91d827c..edb8d73 100644
--- a/tests/tests/graphics/src/android/graphics/cts/BitmapFactoryTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/BitmapFactoryTest.java
@@ -39,7 +39,7 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.io.OutputStream;
+import java.io.RandomAccessFile;
public class BitmapFactoryTest extends InstrumentationTestCase {
private Resources mRes;
@@ -58,10 +58,6 @@
R.drawable.baseline_jpeg, R.drawable.png_test, R.drawable.gif_test,
R.drawable.bmp_test, R.drawable.webp_test
};
- private static String[] NAMES_TEMP_FILES = new String[] {
- "baseline_temp.jpg", "png_temp.png", "gif_temp.gif",
- "bmp_temp.bmp", "webp_temp.webp"
- };
// The width and height of the above image.
private static int WIDTHS[] = new int[] { 1280, 640, 320, 320, 640 };
@@ -72,6 +68,10 @@
Config.ARGB_4444};
private static int[] COLOR_TOLS = new int[] {16, 49, 576};
+ private static Config[] COLOR_CONFIGS_RGBA = new Config[] {Config.ARGB_8888,
+ Config.ARGB_4444};
+ private static int[] COLOR_TOLS_RGBA = new int[] {72, 124};
+
private static int[] RAW_COLORS = new int[] {
// raw data from R.drawable.premul_data
Color.argb(255, 0, 0, 0),
@@ -200,11 +200,15 @@
Bitmap bPng = BitmapFactory.decodeStream(iStreamPng, null, options);
assertNotNull(bPng);
assertEquals(bPng.getConfig(), COLOR_CONFIGS[k]);
+ assertFalse(bPng.isPremultiplied());
+ assertFalse(bPng.hasAlpha());
InputStream iStreamWebp1 = obtainInputStream(R.drawable.webp_test);
Bitmap bWebp1 = BitmapFactory.decodeStream(iStreamWebp1, null, options);
assertNotNull(bWebp1);
- compareBitmaps(bPng, bWebp1, COLOR_TOLS[k], true);
+ assertFalse(bWebp1.isPremultiplied());
+ assertFalse(bWebp1.hasAlpha());
+ compareBitmaps(bPng, bWebp1, COLOR_TOLS[k], true, bPng.isPremultiplied());
// Compress the PNG image to WebP format (Quality=90) and decode it back.
// This will test end-to-end WebP encoding and decoding.
@@ -213,7 +217,46 @@
InputStream iStreamWebp2 = new ByteArrayInputStream(oStreamWebp.toByteArray());
Bitmap bWebp2 = BitmapFactory.decodeStream(iStreamWebp2, null, options);
assertNotNull(bWebp2);
- compareBitmaps(bPng, bWebp2, COLOR_TOLS[k], true);
+ assertFalse(bWebp2.isPremultiplied());
+ assertFalse(bWebp2.hasAlpha());
+ compareBitmaps(bPng, bWebp2, COLOR_TOLS[k], true, bPng.isPremultiplied());
+ }
+ }
+
+ public void testDecodeStream5() throws IOException {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ for (int k = 0; k < COLOR_CONFIGS_RGBA.length; ++k) {
+ options.inPreferredConfig = COLOR_CONFIGS_RGBA[k];
+
+ // Decode the PNG & WebP (google_logo) images. The WebP image has
+ // been encoded from PNG image.
+ InputStream iStreamPng = obtainInputStream(R.drawable.google_logo_1);
+ Bitmap bPng = BitmapFactory.decodeStream(iStreamPng, null, options);
+ assertNotNull(bPng);
+ assertEquals(bPng.getConfig(), COLOR_CONFIGS_RGBA[k]);
+ assertTrue(bPng.isPremultiplied());
+ assertTrue(bPng.hasAlpha());
+
+ // Decode the corresponding WebP (transparent) image (google_logo_2.webp).
+ InputStream iStreamWebP1 = obtainInputStream(R.drawable.google_logo_2);
+ Bitmap bWebP1 = BitmapFactory.decodeStream(iStreamWebP1, null, options);
+ assertNotNull(bWebP1);
+ assertEquals(bWebP1.getConfig(), COLOR_CONFIGS_RGBA[k]);
+ assertTrue(bWebP1.isPremultiplied());
+ assertTrue(bWebP1.hasAlpha());
+ compareBitmaps(bPng, bWebP1, COLOR_TOLS_RGBA[k], true, bPng.isPremultiplied());
+
+ // Compress the PNG image to WebP format (Quality=90) and decode it back.
+ // This will test end-to-end WebP encoding and decoding.
+ ByteArrayOutputStream oStreamWebp = new ByteArrayOutputStream();
+ assertTrue(bPng.compress(CompressFormat.WEBP, 90, oStreamWebp));
+ InputStream iStreamWebp2 = new ByteArrayInputStream(oStreamWebp.toByteArray());
+ Bitmap bWebP2 = BitmapFactory.decodeStream(iStreamWebp2, null, options);
+ assertNotNull(bWebP2);
+ assertEquals(bWebP2.getConfig(), COLOR_CONFIGS_RGBA[k]);
+ assertTrue(bWebP2.isPremultiplied());
+ assertTrue(bWebP2.hasAlpha());
+ compareBitmaps(bPng, bWebP2, COLOR_TOLS_RGBA[k], true, bPng.isPremultiplied());
}
}
@@ -240,6 +283,50 @@
assertEquals(START_WIDTH, b.getWidth());
}
+ public void testDecodeFileDescriptor3() throws IOException {
+ // Arbitrary offsets to use. If the offset of the FD matches the offset of the image,
+ // decoding should succeed, but if they do not match, decoding should fail.
+ long ACTUAL_OFFSETS[] = new long[] { 0, 17 };
+ for (int RES_ID : RES_IDS) {
+ for (int j = 0; j < ACTUAL_OFFSETS.length; ++j) {
+ // FIXME: The purgeable test should attempt to purge the memory
+ // to force a re-decode.
+ for (boolean TEST_PURGEABLE : new boolean[] { false, true }) {
+ BitmapFactory.Options opts = new BitmapFactory.Options();
+ opts.inPurgeable = TEST_PURGEABLE;
+ opts.inInputShareable = TEST_PURGEABLE;
+
+ long actualOffset = ACTUAL_OFFSETS[j];
+ String path = obtainPath(RES_ID, actualOffset);
+ RandomAccessFile file = new RandomAccessFile(path, "r");
+ FileDescriptor fd = file.getFD();
+ assertTrue(fd.valid());
+
+ // Set the offset to ACTUAL_OFFSET
+ file.seek(actualOffset);
+ assertEquals(file.getFilePointer(), actualOffset);
+
+ // Now decode. This should be successful and leave the offset
+ // unchanged.
+ Bitmap b = BitmapFactory.decodeFileDescriptor(fd, null, opts);
+ assertNotNull(b);
+ assertEquals(file.getFilePointer(), actualOffset);
+
+ // Now use the other offset. It should fail to decode, and
+ // the offset should remain unchanged.
+ long otherOffset = ACTUAL_OFFSETS[(j + 1) % ACTUAL_OFFSETS.length];
+ assertFalse(otherOffset == actualOffset);
+ file.seek(otherOffset);
+ assertEquals(file.getFilePointer(), otherOffset);
+
+ b = BitmapFactory.decodeFileDescriptor(fd, null, opts);
+ assertNull(b);
+ assertEquals(file.getFilePointer(), otherOffset);
+ }
+ }
+ }
+ }
+
public void testDecodeFile1() throws IOException {
Bitmap b = BitmapFactory.decodeFile(obtainPath(), mOpt1);
assertNotNull(b);
@@ -278,6 +365,50 @@
assertTrue(pass.isMutable());
}
+ /**
+ * Create bitmap sized to load unscaled resources: start, pass, and alpha
+ */
+ private Bitmap createBitmapForReuse(int pixelCount) {
+ Bitmap bitmap = Bitmap.createBitmap(pixelCount, 1, Config.ARGB_8888);
+ bitmap.eraseColor(Color.BLACK);
+ bitmap.setHasAlpha(false);
+ return bitmap;
+ }
+
+ /**
+ * Decode resource with ResId into reuse bitmap without scaling, verifying expected hasAlpha
+ */
+ private void decodeResourceWithReuse(Bitmap reuse, int resId, boolean hasAlpha) {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inMutable = true;
+ options.inSampleSize = 1;
+ options.inScaled = false;
+ options.inBitmap = reuse;
+ Bitmap output = BitmapFactory.decodeResource(mRes, resId, options);
+ assertSame(reuse, output);
+ assertEquals(output.hasAlpha(), hasAlpha);
+ }
+
+ public void testDecodeReuseHasAlpha() throws IOException {
+ final int bitmapSize = 31; // size in pixels of start, pass, and alpha resources
+ final int pixelCount = bitmapSize * bitmapSize;
+
+ // Test reuse, hasAlpha false and true
+ Bitmap bitmap = createBitmapForReuse(pixelCount);
+ decodeResourceWithReuse(bitmap, R.drawable.start, false);
+ decodeResourceWithReuse(bitmap, R.drawable.alpha, true);
+
+ // Test pre-reconfigure, hasAlpha false and true
+ bitmap = createBitmapForReuse(pixelCount);
+ bitmap.reconfigure(bitmapSize, bitmapSize, Config.ARGB_8888);
+ bitmap.setHasAlpha(true);
+ decodeResourceWithReuse(bitmap, R.drawable.start, false);
+
+ bitmap = createBitmapForReuse(pixelCount);
+ bitmap.reconfigure(bitmapSize, bitmapSize, Config.ARGB_8888);
+ decodeResourceWithReuse(bitmap, R.drawable.alpha, true);
+ }
+
public void testDecodeReuseFormats() throws IOException {
// reuse should support all image formats
for (int i = 0; i < RES_IDS.length; ++i) {
@@ -401,6 +532,47 @@
assertFalse(purgeableBitmap.getAllocationByteCount() == 0);
}
+ private int mDefaultCreationDensity;
+ private void verifyScaled(Bitmap b) {
+ assertEquals(b.getWidth(), START_WIDTH * 2);
+ assertEquals(b.getDensity(), 2);
+ }
+
+ private void verifyUnscaled(Bitmap b) {
+ assertEquals(b.getWidth(), START_WIDTH);
+ assertEquals(b.getDensity(), mDefaultCreationDensity);
+ }
+
+ public void testDecodeScaling() {
+ BitmapFactory.Options defaultOpt = new BitmapFactory.Options();
+
+ BitmapFactory.Options unscaledOpt = new BitmapFactory.Options();
+ unscaledOpt.inScaled = false;
+
+ BitmapFactory.Options scaledOpt = new BitmapFactory.Options();
+ scaledOpt.inScaled = true;
+ scaledOpt.inDensity = 1;
+ scaledOpt.inTargetDensity = 2;
+
+ mDefaultCreationDensity = Bitmap.createBitmap(1, 1, Config.ARGB_8888).getDensity();
+
+ byte[] bytes = obtainArray();
+
+ verifyUnscaled(BitmapFactory.decodeByteArray(bytes, 0, bytes.length));
+ verifyUnscaled(BitmapFactory.decodeByteArray(bytes, 0, bytes.length, null));
+ verifyUnscaled(BitmapFactory.decodeByteArray(bytes, 0, bytes.length, unscaledOpt));
+ verifyUnscaled(BitmapFactory.decodeByteArray(bytes, 0, bytes.length, defaultOpt));
+
+ verifyUnscaled(BitmapFactory.decodeStream(obtainInputStream()));
+ verifyUnscaled(BitmapFactory.decodeStream(obtainInputStream(), null, null));
+ verifyUnscaled(BitmapFactory.decodeStream(obtainInputStream(), null, unscaledOpt));
+ verifyUnscaled(BitmapFactory.decodeStream(obtainInputStream(), null, defaultOpt));
+
+ // scaling should only occur if Options are passed with inScaled=true
+ verifyScaled(BitmapFactory.decodeByteArray(bytes, 0, bytes.length, scaledOpt));
+ verifyScaled(BitmapFactory.decodeStream(obtainInputStream(), null, scaledOpt));
+ }
+
private byte[] obtainArray() {
ByteArrayOutputStream stm = new ByteArrayOutputStream();
Options opt = new BitmapFactory.Options();
@@ -426,17 +598,32 @@
}
private String obtainPath() throws IOException {
+ return obtainPath(R.drawable.start, 0);
+ }
+
+ /*
+ * Create a new file and return a path to it.
+ * @param resId Original file. It will be copied into the new file.
+ * @param offset Number of zeroes to write to the new file before the
+ * copied file. This allows testing decodeFileDescriptor
+ * with an offset. Must be less than or equal to 1024
+ */
+ private String obtainPath(int resId, long offset) throws IOException {
File dir = getInstrumentation().getTargetContext().getFilesDir();
dir.mkdirs();
+ // The suffix does not necessarily represent theactual file type.
File file = new File(dir, "test.jpg");
if (!file.createNewFile()) {
if (!file.exists()) {
fail("Failed to create new File!");
}
}
- InputStream is = obtainInputStream();
+ InputStream is = obtainInputStream(resId);
FileOutputStream fOutput = new FileOutputStream(file);
byte[] dataBuffer = new byte[1024];
+ // Write a bunch of zeroes before the image.
+ assertTrue(offset <= 1024);
+ fOutput.write(dataBuffer, 0, (int) offset);
int readLength = 0;
while ((readLength = is.read(dataBuffer)) != -1) {
fOutput.write(dataBuffer, 0, readLength);
@@ -450,7 +637,7 @@
// lessThanMargin is to indicate whether we expect the mean square error
// to be "less than" or "no less than".
private void compareBitmaps(Bitmap expected, Bitmap actual,
- int mseMargin, boolean lessThanMargin) {
+ int mseMargin, boolean lessThanMargin, boolean isPremultiplied) {
final int width = expected.getWidth();
final int height = expected.getHeight();
@@ -463,30 +650,49 @@
int[] expectedColors = new int [width * height];
int[] actualColors = new int [width * height];
+ // Bitmap.getPixels() returns colors with non-premultiplied ARGB values.
expected.getPixels(expectedColors, 0, width, 0, 0, width, height);
actual.getPixels(actualColors, 0, width, 0, 0, width, height);
for (int row = 0; row < height; ++row) {
for (int col = 0; col < width; ++col) {
int idx = row * width + col;
- mse += distance(expectedColors[idx], actualColors[idx]);
+ mse += distance(expectedColors[idx], actualColors[idx], isPremultiplied);
}
}
mse /= width * height;
if (lessThanMargin) {
- assertTrue("MSE too large for normal case: " + mse,
+ assertTrue("MSE " + mse + "larger than the threshold: " + mseMargin,
mse <= mseMargin);
} else {
- assertFalse("MSE too small for abnormal case: " + mse,
+ assertFalse("MSE " + mse + "smaller than the threshold: " + mseMargin,
mse <= mseMargin);
}
}
- private double distance(int expect, int actual) {
- final int r = Color.red(actual) - Color.red(expect);
- final int g = Color.green(actual) - Color.green(expect);
- final int b = Color.blue(actual) - Color.blue(expect);
- return r * r + g * g + b * b;
+ private int multiplyAlpha(int color, int alpha) {
+ return (color * alpha + 127) / 255;
+ }
+
+ // For the Bitmap with Alpha, multiply the Alpha values to get the effective
+ // RGB colors and then compute the color-distance.
+ private double distance(int expect, int actual, boolean isPremultiplied) {
+ if (isPremultiplied) {
+ final int a1 = Color.alpha(actual);
+ final int a2 = Color.alpha(expect);
+ final int r = multiplyAlpha(Color.red(actual), a1) -
+ multiplyAlpha(Color.red(expect), a2);
+ final int g = multiplyAlpha(Color.green(actual), a1) -
+ multiplyAlpha(Color.green(expect), a2);
+ final int b = multiplyAlpha(Color.blue(actual), a1) -
+ multiplyAlpha(Color.blue(expect), a2);
+ return r * r + g * g + b * b;
+ } else {
+ final int r = Color.red(actual) - Color.red(expect);
+ final int g = Color.green(actual) - Color.green(expect);
+ final int b = Color.blue(actual) - Color.blue(expect);
+ return r * r + g * g + b * b;
+ }
}
}
diff --git a/tests/tests/graphics/src/android/graphics/cts/BitmapRegionDecoderTest.java b/tests/tests/graphics/src/android/graphics/cts/BitmapRegionDecoderTest.java
index c981db3..5ef710f 100644
--- a/tests/tests/graphics/src/android/graphics/cts/BitmapRegionDecoderTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/BitmapRegionDecoderTest.java
@@ -88,7 +88,7 @@
private int mMseMargin = 3 * (1 * 1);
// MSE margin for WebP Region-Decoding for 'Config.RGB_565' is little bigger.
- private int mMseMarginWebPConfigRgb565 = 5;
+ private int mMseMarginWebPConfigRgb565 = 8;
@Override
diff --git a/tests/tests/graphics/src/android/graphics/cts/CanvasTest.java b/tests/tests/graphics/src/android/graphics/cts/CanvasTest.java
index 4102957..6e86563 100644
--- a/tests/tests/graphics/src/android/graphics/cts/CanvasTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/CanvasTest.java
@@ -43,6 +43,7 @@
import com.android.cts.stub.R;
+import java.util.Vector;
public class CanvasTest extends InstrumentationTestCase {
private final static int PAINT_COLOR = 0xff00ff00;
@@ -267,6 +268,86 @@
}
}
+ public void testSaveFlags1() {
+ int[] flags = {
+ Canvas.MATRIX_SAVE_FLAG,
+ };
+ verifySaveFlagsSequence(flags);
+ }
+
+ public void testSaveFlags2() {
+ int[] flags = {
+ Canvas.CLIP_SAVE_FLAG,
+ };
+ verifySaveFlagsSequence(flags);
+ }
+
+ public void testSaveFlags3() {
+ int[] flags = {
+ Canvas.ALL_SAVE_FLAG,
+ Canvas.MATRIX_SAVE_FLAG,
+ Canvas.MATRIX_SAVE_FLAG,
+ };
+ verifySaveFlagsSequence(flags);
+ }
+
+ public void testSaveFlags4() {
+ int[] flags = {
+ Canvas.ALL_SAVE_FLAG,
+ Canvas.CLIP_SAVE_FLAG,
+ Canvas.CLIP_SAVE_FLAG,
+ };
+ verifySaveFlagsSequence(flags);
+ }
+
+ public void testSaveFlags5() {
+ int[] flags = {
+ Canvas.MATRIX_SAVE_FLAG,
+ Canvas.MATRIX_SAVE_FLAG,
+ Canvas.CLIP_SAVE_FLAG,
+ Canvas.CLIP_SAVE_FLAG,
+ Canvas.MATRIX_SAVE_FLAG,
+ Canvas.MATRIX_SAVE_FLAG,
+ };
+ verifySaveFlagsSequence(flags);
+ }
+
+ public void testSaveFlags6() {
+ int[] flags = {
+ Canvas.CLIP_SAVE_FLAG,
+ Canvas.CLIP_SAVE_FLAG,
+ Canvas.MATRIX_SAVE_FLAG,
+ Canvas.MATRIX_SAVE_FLAG,
+ Canvas.CLIP_SAVE_FLAG,
+ Canvas.CLIP_SAVE_FLAG,
+ };
+ verifySaveFlagsSequence(flags);
+ }
+
+ public void testSaveFlags7() {
+ int[] flags = {
+ Canvas.MATRIX_SAVE_FLAG,
+ Canvas.MATRIX_SAVE_FLAG,
+ Canvas.ALL_SAVE_FLAG,
+ Canvas.ALL_SAVE_FLAG,
+ Canvas.CLIP_SAVE_FLAG,
+ Canvas.CLIP_SAVE_FLAG,
+ };
+ verifySaveFlagsSequence(flags);
+ }
+
+ public void testSaveFlags8() {
+ int[] flags = {
+ Canvas.MATRIX_SAVE_FLAG,
+ Canvas.CLIP_SAVE_FLAG,
+ Canvas.ALL_SAVE_FLAG,
+ Canvas.MATRIX_SAVE_FLAG,
+ Canvas.CLIP_SAVE_FLAG,
+ Canvas.ALL_SAVE_FLAG,
+ };
+ verifySaveFlagsSequence(flags);
+ }
+
public void testSaveLayer1() {
final Paint p = new Paint();
final RectF rF = new RectF(0, 10, 31, 0);
@@ -1727,4 +1808,49 @@
assertEquals(0.0f, values[7]);
assertEquals(1.0f, values[8]);
}
+
+ private RectF getDeviceClip() {
+ final RectF clip = new RectF(mCanvas.getClipBounds());
+ mCanvas.getMatrix().mapRect(clip);
+ return clip;
+ }
+
+ // Loops through the passed flags, applying each in order with successive calls
+ // to save, verifying the clip and matrix values when restoring.
+ private void verifySaveFlagsSequence(int[] saveFlags) {
+ final Vector<RectF> clips = new Vector<RectF>();
+ final Vector<Matrix> matrices = new Vector<Matrix>();
+
+ assertTrue(BITMAP_WIDTH > saveFlags.length);
+ assertTrue(BITMAP_HEIGHT > saveFlags.length);
+
+ for (int i = 0; i < saveFlags.length; ++i) {
+ clips.add(getDeviceClip());
+ matrices.add(mCanvas.getMatrix());
+ mCanvas.save(saveFlags[i]);
+
+ mCanvas.translate(1, 1);
+ mCanvas.clipRect(0, 0, BITMAP_WIDTH - i - 1, BITMAP_HEIGHT - i - 1);
+
+ if (i > 0) {
+ // We are mutating the state on each iteration.
+ assertFalse(clips.elementAt(i).equals(clips.elementAt(i - 1)));
+ assertFalse(matrices.elementAt(i).equals(matrices.elementAt(i - 1)));
+ }
+ }
+
+ for (int i = saveFlags.length - 1; i >= 0; --i) {
+ // If clip/matrix flags are not set, the associated state should be preserved.
+ if ((saveFlags[i] & Canvas.CLIP_SAVE_FLAG) == 0) {
+ clips.elementAt(i).set(getDeviceClip());
+ }
+ if ((saveFlags[i] & Canvas.MATRIX_SAVE_FLAG) == 0) {
+ matrices.elementAt(i).set(mCanvas.getMatrix());
+ }
+
+ mCanvas.restore();
+ assertEquals(clips.elementAt(i), getDeviceClip());
+ assertEquals(matrices.elementAt(i), mCanvas.getMatrix());
+ }
+ }
}
diff --git a/tests/tests/graphics/src/android/graphics/cts/LayerRasterizerTest.java b/tests/tests/graphics/src/android/graphics/cts/LayerRasterizerTest.java
index 4309ac5..289ee77 100644
--- a/tests/tests/graphics/src/android/graphics/cts/LayerRasterizerTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/LayerRasterizerTest.java
@@ -17,32 +17,44 @@
package android.graphics.cts;
import junit.framework.TestCase;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
import android.graphics.LayerRasterizer;
import android.graphics.Paint;
+import android.graphics.Rasterizer;
public class LayerRasterizerTest extends TestCase {
+ private final static int BITMAP_WIDTH = 16;
+ private final static int BITMAP_HEIGHT = 16;
+
+ private void exerciseRasterizer(Rasterizer rasterizer) {
+ Bitmap bm = Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bm);
+ Paint paint = new Paint();
+
+ // just want to confirm that we don't crash or throw an exception
+ paint.setRasterizer(rasterizer);
+ canvas.drawCircle(BITMAP_WIDTH/2, BITMAP_WIDTH/2, BITMAP_WIDTH/2, paint);
+ }
public void testConstructor() {
-
- // new the LayerRasterizer instance
- new LayerRasterizer();
+ exerciseRasterizer(new LayerRasterizer());
}
public void testAddLayer1() {
- // new the LayerRasterizer instance
LayerRasterizer layerRasterizer = new LayerRasterizer();
Paint p = new Paint();
layerRasterizer.addLayer(p);
- // this function called a native function and this test just make sure
- // it doesn't throw out any exception.
+ exerciseRasterizer(layerRasterizer);
}
public void testAddLayer2() {
- // new the LayerRasterizer instance
LayerRasterizer layerRasterizer = new LayerRasterizer();
layerRasterizer.addLayer(new Paint(), 1.0f, 1.0f);
- // this function called a native function and this test just make sure
- // it doesn't throw out any exception.
+ exerciseRasterizer(layerRasterizer);
+ // explicitly add another layer and draw again
+ layerRasterizer.addLayer(new Paint(), 2.0f, 2.0f);
+ exerciseRasterizer(layerRasterizer);
}
}
diff --git a/tests/tests/graphics/src/android/graphics/cts/PaintFlagsDrawFilterTest.java b/tests/tests/graphics/src/android/graphics/cts/PaintFlagsDrawFilterTest.java
index 31dbf16..ee3ec7c 100644
--- a/tests/tests/graphics/src/android/graphics/cts/PaintFlagsDrawFilterTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/PaintFlagsDrawFilterTest.java
@@ -107,4 +107,82 @@
// underline is under the text or at least at the bottom of it
assertTrue(rect.top >= TEXT_Y);
}
+
+ // Tests that FILTER_BITMAP_FLAG is handled properly.
+ public void testPaintFlagsDrawFilter2() {
+ // Create a bitmap with alternating black and white pixels.
+ int kWidth = 5;
+ int kHeight = 5;
+ int colors[] = new int [] { Color.WHITE, Color.BLACK };
+ int k = 0;
+ Bitmap grid = Bitmap.createBitmap(kWidth, kHeight, Config.ARGB_8888);
+ for (int i = 0; i < kWidth; ++i) {
+ for (int j = 0; j < kHeight; ++j) {
+ grid.setPixel(i, j, colors[k]);
+ k = (k + 1) % 2;
+ }
+ }
+
+ // Setup a scaled canvas for drawing the bitmap, with and without FILTER_BITMAP_FLAG set.
+ // When the flag is set, there will be gray pixels. When the flag is not set, all pixels
+ // will be either black or white.
+ int kScale = 5;
+ Bitmap dst = Bitmap.createBitmap(kWidth * kScale, kHeight * kScale, Config.ARGB_8888);
+ Canvas canvas = new Canvas(dst);
+ canvas.scale(kScale, kScale);
+
+ // Drawn without FILTER_BITMAP_FLAG, all pixels will be black or white.
+ Paint simplePaint = new Paint();
+ canvas.drawBitmap(grid, 0, 0, simplePaint);
+
+ assertContainsOnlyBlackAndWhite(dst);
+
+ // Drawn with FILTER_BITMAP_FLAG, some pixels will be somewhere in between.
+ Paint filterBitmapPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
+ canvas.drawBitmap(grid, 0, 0, filterBitmapPaint);
+
+ assertContainsNonBW(dst);
+
+ // Drawing with a paint that FILTER_BITMAP_FLAG set and a DrawFilter that removes
+ // FILTER_BITMAP_FLAG should remove the effect of the flag, resulting in all pixels being
+ // either black or white.
+ canvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.FILTER_BITMAP_FLAG, 0));
+ canvas.drawBitmap(grid, 0, 0, filterBitmapPaint);
+
+ assertContainsOnlyBlackAndWhite(dst);
+
+ // Likewise, drawing with a DrawFilter that sets FILTER_BITMAP_FLAG should filter,
+ // resulting in gray pixels.
+ canvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.FILTER_BITMAP_FLAG));
+ canvas.drawBitmap(grid, 0, 0, simplePaint);
+
+ assertContainsNonBW(dst);
+ }
+
+ // Assert that at least one pixel is neither black nor white. This is used to verify that
+ // filtering was done, since the original bitmap only contained black and white pixels.
+ private void assertContainsNonBW(Bitmap bitmap) {
+ for (int i = 0; i < bitmap.getWidth(); ++i) {
+ for (int j = 0; j < bitmap.getHeight(); ++j) {
+ int color = bitmap.getPixel(i, j);
+ if (color != Color.BLACK && color != Color.WHITE) {
+ // Filtering must have been done.
+ return;
+ }
+ }
+ }
+ // Filtering did not happen.
+ assertTrue(false);
+ }
+
+ // Assert that every pixel is either black or white. Used to verify that no filtering was
+ // done, since the original bitmap contained only black and white pixels.
+ private void assertContainsOnlyBlackAndWhite(Bitmap bitmap) {
+ for (int i = 0; i < bitmap.getWidth(); ++i) {
+ for (int j = 0; j < bitmap.getHeight(); ++j) {
+ int color = bitmap.getPixel(i, j);
+ assertTrue(color == Color.BLACK || color == Color.WHITE);
+ }
+ }
+ }
}
diff --git a/tests/tests/graphics/src/android/graphics/cts/PictureTest.java b/tests/tests/graphics/src/android/graphics/cts/PictureTest.java
index 81f053d..17e9b63 100644
--- a/tests/tests/graphics/src/android/graphics/cts/PictureTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/PictureTest.java
@@ -24,18 +24,88 @@
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Picture;
+import android.graphics.Rect;
import android.graphics.Paint.Style;
-
+import android.graphics.Region.Op;
public class PictureTest extends TestCase {
private static final int TEST_WIDTH = 4; // must be >= 2
private static final int TEST_HEIGHT = 3; // must >= 2
- public void testPicture() throws Exception {
+ private final Rect mClipRect = new Rect(0, 0, 2, 2);
+ // This method tests out some edge cases w.r.t. Picture creation.
+ // In particular, this test verifies that, in the following situations,
+ // the created picture (effectively) has balanced saves and restores:
+ // - copy constructed picture from actively recording picture
+ // - writeToStream/createFromStream created picture from actively recording picture
+ // - actively recording picture after draw call
+ public void testSaveRestoreBalance() throws Exception {
+ Picture original = new Picture();
+ Canvas canvas = original.beginRecording(TEST_WIDTH, TEST_HEIGHT);
+ assertNotNull(canvas);
+ createImbalance(canvas);
+
+ int expectedSaveCount = canvas.getSaveCount();
+
+ Picture copy = new Picture(original);
+ checkBalance(copy);
+
+ assertEquals(expectedSaveCount, canvas.getSaveCount());
+
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ original.writeToStream(bout);
+
+ assertEquals(expectedSaveCount, canvas.getSaveCount());
+
+ Picture serialized = Picture.createFromStream(new ByteArrayInputStream(bout.toByteArray()));
+ // The serialization/deserialization process will balance the saves and restores
+ checkBalance(serialized);
+
+ assertEquals(expectedSaveCount, canvas.getSaveCount());
+
+ Bitmap bitmap = Bitmap.createBitmap(TEST_WIDTH, TEST_HEIGHT, Bitmap.Config.ARGB_8888);
+ Canvas drawDest = new Canvas(bitmap);
+ original.draw(drawDest);
+ checkBalance(original);
+ }
+
+ // Add an extra save with a transform and clip
+ private void createImbalance(Canvas canvas) {
+ canvas.save();
+ canvas.clipRect(mClipRect, Op.REPLACE);
+ canvas.translate(1.0f, 1.0f);
+ Paint paint = new Paint();
+ paint.setColor(Color.GREEN);
+ canvas.drawRect(0, 0, 10, 10, paint);
+ }
+
+ private void checkBalance(Picture picture) {
+ Bitmap bitmap = Bitmap.createBitmap(TEST_WIDTH, TEST_HEIGHT, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+
+ int beforeSaveCount = canvas.getSaveCount();
+
+ final Matrix beforeMatrix = canvas.getMatrix();
+
+ canvas.drawPicture(picture);
+
+ assertEquals(beforeSaveCount, canvas.getSaveCount());
+
+ assertTrue(beforeMatrix.equals(canvas.getMatrix()));
+
+ Rect afterClip = new Rect();
+
+ assertTrue(canvas.getClipBounds(afterClip));
+ assertEquals(TEST_WIDTH, afterClip.width());
+ assertEquals(TEST_HEIGHT, afterClip.height());
+ }
+
+ public void testPicture() throws Exception {
Picture picture = new Picture();
ByteArrayOutputStream bout = new ByteArrayOutputStream();
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/DrawableContainerTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/DrawableContainerTest.java
index 0672db6..26cdbb6 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/DrawableContainerTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/DrawableContainerTest.java
@@ -186,10 +186,10 @@
assertConstantStateNotSet();
assertNull(mDrawableContainer.getCurrent());
+ mDrawableContainer.setConstantState(mDrawableContainerState);
mDrawableContainer.setColorFilter(null);
mDrawableContainer.setColorFilter(new ColorFilter());
- mDrawableContainer.setConstantState(mDrawableContainerState);
MockDrawable dr = new MockDrawable();
addAndSelectDrawable(dr);
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/GradientDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/GradientDrawableTest.java
index f2d7b82..75639c2 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/GradientDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/GradientDrawableTest.java
@@ -18,11 +18,11 @@
import com.android.cts.stub.R;
-
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import android.content.res.Resources;
+import android.content.res.Resources.Theme;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.PixelFormat;
@@ -248,6 +248,26 @@
}
}
+ public void testInflateGradientRadius() throws XmlPullParserException, IOException {
+ Rect parentBounds = new Rect(0, 0, 100, 100);
+ Resources resources = mContext.getResources();
+
+ GradientDrawable gradientDrawable;
+ float radius;
+
+ gradientDrawable = (GradientDrawable) resources.getDrawable(
+ R.drawable.gradientdrawable_radius_base);
+ gradientDrawable.setBounds(parentBounds);
+ radius = gradientDrawable.getGradientRadius();
+ assertEquals(25.0f, radius, 0.0f);
+
+ gradientDrawable = (GradientDrawable) resources.getDrawable(
+ R.drawable.gradientdrawable_radius_parent);
+ gradientDrawable.setBounds(parentBounds);
+ radius = gradientDrawable.getGradientRadius();
+ assertEquals(50.0f, radius, 0.0f);
+ }
+
public void testGetIntrinsicWidth() {
GradientDrawable gradientDrawable = new GradientDrawable();
gradientDrawable.setSize(6, 4);
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/NinePatchDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/NinePatchDrawableTest.java
index b23c7fa..84cf41b 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/NinePatchDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/NinePatchDrawableTest.java
@@ -18,8 +18,6 @@
import com.android.cts.stub.R;
-import dalvik.annotation.KnownFailure;
-
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -239,9 +237,9 @@
assertEquals(9, mNinePatchDrawable.getMinimumHeight());
}
- @KnownFailure("Bug 2834281 - Bitmap#hasAlpha seems to return true for "
- + "images without alpha.")
- public void testGetOpacity() {
+ // Known failure: Bug 2834281 - Bitmap#hasAlpha seems to return true for
+ // images without alpha
+ public void suppress_testGetOpacity() {
assertEquals(PixelFormat.OPAQUE, mNinePatchDrawable.getOpacity());
mNinePatchDrawable = getNinePatchDrawable(R.drawable.ninepatch_1);
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/ThemedDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/ThemedDrawableTest.java
new file mode 100644
index 0000000..2cba287
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/ThemedDrawableTest.java
@@ -0,0 +1,149 @@
+/*
+ * 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 android.graphics.drawable.cts;
+
+import android.annotation.TargetApi;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.Shader.TileMode;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.LayerDrawable;
+import android.graphics.drawable.NinePatchDrawable;
+import android.graphics.drawable.RippleDrawable;
+import android.test.AndroidTestCase;
+import android.util.SparseIntArray;
+import android.view.ContextThemeWrapper;
+import android.view.Gravity;
+
+import com.android.cts.stub.R;
+
+@TargetApi(19)
+public class ThemedDrawableTest extends AndroidTestCase {
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mContext.setTheme(R.style.Theme_ThemedDrawableTest);
+ }
+
+ public void testBitmapDrawable() {
+ BitmapDrawable d = (BitmapDrawable) mContext.getDrawable(R.drawable.bitmapdrawable_theme);
+
+ internalTestBitmapDrawable(d);
+ }
+
+ private void internalTestBitmapDrawable(BitmapDrawable d) {
+ assertEquals(true, d.hasAntiAlias());
+ assertEquals(true, d.isAutoMirrored());
+ // assertEquals(true, d.hasDither());
+ // assertEquals(true, d.hasFilter());
+ assertEquals(Gravity.TOP, d.getGravity());
+ assertEquals(true, d.hasMipMap());
+ assertNotNull(d.getBitmap());
+ assertEquals(TileMode.MIRROR, d.getTileModeX());
+ assertEquals(TileMode.MIRROR, d.getTileModeY());
+ }
+
+ public void testColorDrawable() {
+ ColorDrawable d = (ColorDrawable) mContext.getDrawable(R.drawable.colordrawable_theme);
+
+ assertEquals(Color.BLACK, d.getColor());
+ }
+
+ public void testGradientDrawable() {
+ GradientDrawable d = (GradientDrawable) mContext.getDrawable(
+ R.drawable.gradientdrawable_theme);
+
+ // Corners
+ // assertEquals(1, d.getCornerRadius(0));
+ // assertEquals(1, d.getCornerRadius(1));
+ // assertEquals(1, d.getCornerRadius(2));
+ // assertEquals(1, d.getCornerRadius(3));
+
+ // Gradient
+ // int[] colors = d.getColors(null);
+ // for (int i = 0; i < color.length; i++) {
+ // assertEquals(Color.BLACK, colors[i]);
+ // }
+ // assertEquals(1.0f, d.getGradientAngle());
+ // assertEquals(1.0, d.getGradientCenterX());
+ // assertEquals(1.0, d.getGradientCenterY());
+ // assertEquals(1.0, d.getGradientRadius());
+ // assertEquals(false, d.getUseLevel());
+
+ // Padding
+ Rect padding = new Rect();
+ assertTrue(d.getPadding(padding));
+ assertEquals(1, padding.left);
+ assertEquals(1, padding.top);
+ assertEquals(1, padding.bottom);
+ assertEquals(1, padding.right);
+
+ // Size
+ assertEquals(1, d.getIntrinsicHeight());
+ assertEquals(1, d.getIntrinsicWidth());
+
+ // Solid
+ // assertEquals(true, d.hasSolidColor());
+ // assertEquals(Color.BLACK, d.getColor());
+
+ // Stroke
+ // assertEquals(1.0, d.getStrokeWidth());
+ // assertEquals(Color.BLACK, d.getStrokeColor());
+ // assertEquals(1.0, d.getStrokeDashWidth());
+ // assertEquals(1.0, d.getStrokeDashGap());
+ }
+
+ public void testNinePatchDrawable() {
+ NinePatchDrawable d = (NinePatchDrawable) mContext.getDrawable(
+ R.drawable.ninepatchdrawable_theme);
+
+ internalTestNinePatchDrawable(d);
+ }
+
+ private void internalTestNinePatchDrawable(NinePatchDrawable d) {
+ assertEquals(true, d.isAutoMirrored());
+ // assertEquals(true, d.hasDither());
+ // assertNotNull(d.getNinePatch());
+ }
+
+ public void testRippleDrawable() {
+ RippleDrawable d = (RippleDrawable) mContext.getDrawable(
+ R.drawable.rippledrawable_theme);
+
+ // assertEquals(Color.BLACK, d.getTint());
+ }
+
+ public void testLayerDrawable() {
+ LayerDrawable d = (LayerDrawable) mContext.getDrawable(R.drawable.layerdrawable_theme);
+
+ // Layer autoMirror values are set to the parent's autoMirror value, so
+ // make sure the container is using the expected value.
+ assertEquals(true, d.isAutoMirrored());
+
+ BitmapDrawable bitmapDrawable = (BitmapDrawable) d.getDrawable(0);
+ internalTestBitmapDrawable(bitmapDrawable);
+
+ NinePatchDrawable ninePatchDrawable = (NinePatchDrawable) d.getDrawable(1);
+ internalTestNinePatchDrawable(ninePatchDrawable);
+ }
+}
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/VectorDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/VectorDrawableTest.java
new file mode 100644
index 0000000..2f67560
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/VectorDrawableTest.java
@@ -0,0 +1,204 @@
+/*
+ * 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 android.graphics.drawable.cts;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.drawable.VectorDrawable;
+import android.test.AndroidTestCase;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Xml;
+
+import com.android.cts.stub.R;
+
+import org.xmlpull.v1.XmlPullParser;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+public class VectorDrawableTest extends AndroidTestCase {
+ private static final String LOGTAG = VectorDrawableTest.class.getSimpleName();
+ private int[] mIconResIds = new int[] {
+ R.drawable.vector_icon_create,
+ R.drawable.vector_icon_delete,
+ R.drawable.vector_icon_heart,
+ R.drawable.vector_icon_schedule,
+ R.drawable.vector_icon_settings,
+ R.drawable.vector_icon_random_path_1,
+ R.drawable.vector_icon_random_path_2,
+ R.drawable.vector_icon_repeated_cq,
+ R.drawable.vector_icon_repeated_st,
+ R.drawable.vector_icon_repeated_a_1,
+ R.drawable.vector_icon_repeated_a_2,
+ R.drawable.vector_icon_clip_path_1,
+ R.drawable.vector_icon_transformation_1,
+ R.drawable.vector_icon_transformation_2,
+ R.drawable.vector_icon_transformation_3,
+ R.drawable.vector_icon_transformation_4,
+ R.drawable.vector_icon_transformation_5,
+ R.drawable.vector_icon_transformation_6,
+ };
+
+ private int[] mGoldenImages = new int[] {
+ R.drawable.vector_icon_create_golden,
+ R.drawable.vector_icon_delete_golden,
+ R.drawable.vector_icon_heart_golden,
+ R.drawable.vector_icon_schedule_golden,
+ R.drawable.vector_icon_settings_golden,
+ R.drawable.vector_icon_random_path_1_golden,
+ R.drawable.vector_icon_random_path_2_golden,
+ R.drawable.vector_icon_repeated_cq_golden,
+ R.drawable.vector_icon_repeated_st_golden,
+ R.drawable.vector_icon_repeated_a_1_golden,
+ R.drawable.vector_icon_repeated_a_2_golden,
+ R.drawable.vector_icon_clip_path_1_golden,
+ R.drawable.vector_icon_transformation_1_golden,
+ R.drawable.vector_icon_transformation_2_golden,
+ R.drawable.vector_icon_transformation_3_golden,
+ R.drawable.vector_icon_transformation_4_golden,
+ R.drawable.vector_icon_transformation_5_golden,
+ R.drawable.vector_icon_transformation_6_golden,
+ };
+
+ private static final int IMAGE_WIDTH = 64;
+ private static final int IMAGE_HEIGHT = 64;
+ // A small value is actually making sure that the values are matching
+ // exactly with the golden image.
+ // We can increase the threshold if the Skia is drawing with some variance
+ // on different devices. So far, the tests show they are matching correctly.
+ private static final float PIXEL_ERROR_THRESHOLD = 0.00001f;
+
+ private static final boolean DBG_DUMP_PNG = false;
+
+ private Resources mResources;
+ private VectorDrawable mVectorDrawable;
+ private Bitmap mBitmap;
+ private Canvas mCanvas;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final int width = IMAGE_WIDTH;
+ final int height = IMAGE_HEIGHT;
+
+ mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ mCanvas = new Canvas(mBitmap);
+ mVectorDrawable = new VectorDrawable();
+ mVectorDrawable.setBounds(0, 0, width, height);
+
+ mResources = mContext.getResources();
+ }
+
+ public void testSimpleVectorDrawables() throws Exception {
+ verifyVectorDrawables(mIconResIds, mGoldenImages, 0);
+ }
+
+ private void verifyVectorDrawables(int[] resIds, int[] goldenImages, float fraction) throws Exception {
+ for (int i = 0; i < resIds.length; i++) {
+ // Setup VectorDrawable from xml file and draw into the bitmap.
+ // TODO: use the VectorDrawable.create() function if it is
+ // publicized.
+ XmlPullParser xpp = mResources.getXml(resIds[i]);
+ AttributeSet attrs = Xml.asAttributeSet(xpp);
+
+ mVectorDrawable.inflate(mResources, xpp, attrs);
+
+ mBitmap.eraseColor(0);
+ mVectorDrawable.draw(mCanvas);
+
+ if (DBG_DUMP_PNG) {
+ saveVectorDrawableIntoPNG(mBitmap, resIds, i);
+ } else {
+ // Start to compare
+ Bitmap golden = BitmapFactory.decodeResource(mResources, goldenImages[i]);
+ compareImages(mBitmap, golden, mResources.getString(resIds[i]));
+ }
+ }
+ }
+
+ // This is only for debugging or golden image (re)generation purpose.
+ private void saveVectorDrawableIntoPNG(Bitmap bitmap, int[] resIds, int index) throws IOException {
+ // Save the image to the disk.
+ FileOutputStream out = null;
+ try {
+ String outputFolder = "/sdcard/temp/";
+ File folder = new File(outputFolder);
+ if (!folder.exists()) {
+ folder.mkdir();
+ }
+ String originalFilePath = mResources.getString(resIds[index]);
+ File originalFile = new File(originalFilePath);
+ String fileFullName = originalFile.getName();
+ String fileTitle = fileFullName.substring(0, fileFullName.lastIndexOf("."));
+ String outputFilename = outputFolder + fileTitle + "_golden.png";
+ File outputFile = new File(outputFilename);
+ if (!outputFile.exists()) {
+ outputFile.createNewFile();
+ }
+
+ out = new FileOutputStream(outputFile, false);
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
+ Log.v(LOGTAG, "Write test No." + index + " to file successfully.");
+ } catch (Exception e) {
+ e.printStackTrace();
+ } finally {
+ if (out != null) {
+ out.close();
+ }
+ }
+ }
+
+ private void compareImages(Bitmap ideal, Bitmap given, String filename) {
+ int idealWidth = ideal.getWidth();
+ int idealHeight = ideal.getHeight();
+
+ assertTrue(idealWidth == given.getWidth());
+ assertTrue(idealHeight == given.getHeight());
+
+ int totalDiffPixelCount = 0;
+ float totalPixelCount = idealWidth * idealHeight;
+ for (int x = 0; x < idealWidth; x++) {
+ for (int y = 0; y < idealHeight; y++) {
+ int idealColor = ideal.getPixel(x, y);
+ int givenColor = given.getPixel(x, y);
+ if (idealColor == givenColor)
+ continue;
+
+ float totalError = 0;
+ totalError += Math.abs(Color.red(idealColor) - Color.red(givenColor));
+ totalError += Math.abs(Color.green(idealColor) - Color.green(givenColor));
+ totalError += Math.abs(Color.blue(idealColor) - Color.blue(givenColor));
+ totalError += Math.abs(Color.alpha(idealColor) - Color.alpha(givenColor));
+
+ if ((totalError / 1024.0f) >= PIXEL_ERROR_THRESHOLD) {
+ fail((filename + ": totalError is " + totalError));
+ }
+
+ totalDiffPixelCount++;
+ }
+ }
+ if ((totalDiffPixelCount / totalPixelCount) >= PIXEL_ERROR_THRESHOLD) {
+ fail((filename +": totalDiffPixelCount is " + totalDiffPixelCount));
+ }
+
+ }
+}
diff --git a/tests/tests/graphics2/Android.mk b/tests/tests/graphics2/Android.mk
index b3e7340..a3cdafa 100644
--- a/tests/tests/graphics2/Android.mk
+++ b/tests/tests/graphics2/Android.mk
@@ -21,8 +21,6 @@
# and when built explicitly put it in the data partition
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/graphics2/AndroidManifest.xml b/tests/tests/graphics2/AndroidManifest.xml
index 2392100..67557ad 100644
--- a/tests/tests/graphics2/AndroidManifest.xml
+++ b/tests/tests/graphics2/AndroidManifest.xml
@@ -24,7 +24,10 @@
<instrumentation
android:targetPackage="com.android.cts.graphics2"
- android:name="android.test.InstrumentationCtsTestRunner" />
+ android:name="android.support.test.runner.AndroidJUnitRunner" >
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
<application>
<uses-library android:name="android.test.runner" />
diff --git a/tests/tests/hardware/Android.mk b/tests/tests/hardware/Android.mk
index 2ad58ba..110b291 100644
--- a/tests/tests/hardware/Android.mk
+++ b/tests/tests/hardware/Android.mk
@@ -32,8 +32,6 @@
LOCAL_MODULE_TAGS := tests
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil ctstestrunner mockito-target android-ex-camera2
LOCAL_SRC_FILES := $(call all-java-files-under, src)
@@ -42,7 +40,8 @@
LOCAL_INSTRUMENTATION_FOR := CtsTestStubs
-# uncomment when dalvik.annotation.Test* are removed or part of SDK
+# uncomment when b/13281332 is fixed
#LOCAL_SDK_VERSION := current
+LOCAL_JAVA_LIBRARIES := android.test.runner
include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/hardware/AndroidManifest.xml b/tests/tests/hardware/AndroidManifest.xml
index 971d6c7..11ca9c0 100644
--- a/tests/tests/hardware/AndroidManifest.xml
+++ b/tests/tests/hardware/AndroidManifest.xml
@@ -23,9 +23,12 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.stub"
- android:label="CTS tests of android.hardware"/>
+ android:label="CTS tests of android.hardware">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/AllocationTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/AllocationTest.java
new file mode 100644
index 0000000..8baecac
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/AllocationTest.java
@@ -0,0 +1,848 @@
+/*
+ * Copyright 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 android.hardware.camera2.cts;
+
+import static android.graphics.ImageFormat.YUV_420_888;
+import static android.hardware.camera2.cts.helpers.Preconditions.*;
+import static android.hardware.camera2.cts.helpers.AssertHelpers.*;
+import static android.hardware.camera2.cts.CameraTestUtils.*;
+import static com.android.ex.camera2.blocking.BlockingStateListener.*;
+
+import android.content.Context;
+import android.graphics.ImageFormat;
+import android.graphics.RectF;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.params.ColorSpaceTransform;
+import android.hardware.camera2.params.RggbChannelVector;
+import android.util.Size;
+import android.hardware.camera2.cts.helpers.MaybeNull;
+import android.hardware.camera2.cts.helpers.StaticMetadata;
+import android.hardware.camera2.cts.rs.RenderScriptSingleton;
+import android.hardware.camera2.cts.rs.ScriptGraph;
+import android.hardware.camera2.cts.rs.ScriptYuvCrop;
+import android.hardware.camera2.cts.rs.ScriptYuvMeans1d;
+import android.hardware.camera2.cts.rs.ScriptYuvMeans2dTo1d;
+import android.hardware.camera2.cts.rs.ScriptYuvToRgb;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.renderscript.Allocation;
+import android.renderscript.Script.LaunchOptions;
+import android.test.AndroidTestCase;
+import android.util.Log;
+import android.util.Rational;
+import android.view.Surface;
+
+import com.android.ex.camera2.blocking.BlockingCameraManager.BlockingOpenException;
+import com.android.ex.camera2.blocking.BlockingStateListener;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Suite of tests for camera2 -> RenderScript APIs.
+ *
+ * <p>It uses CameraDevice as producer, camera sends the data to the surface provided by
+ * Allocation. Only the below format is tested:</p>
+ *
+ * <p>YUV_420_888: flexible YUV420, it is a mandatory format for camera.</p>
+ */
+public class AllocationTest extends AndroidTestCase {
+ private static final String TAG = "AllocationTest";
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+ private CameraManager mCameraManager;
+ private CameraDevice mCamera;
+ private BlockingStateListener mCameraListener;
+ private String[] mCameraIds;
+
+ private Handler mHandler;
+ private HandlerThread mHandlerThread;
+
+ private CameraIterable mCameraIterable;
+ private SizeIterable mSizeIterable;
+ private ResultIterable mResultIterable;
+
+ @Override
+ public synchronized void setContext(Context context) {
+ super.setContext(context);
+ mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
+ assertNotNull("Can't connect to camera manager!", mCameraManager);
+
+ RenderScriptSingleton.setContext(context);
+ // TODO: call clearContext
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mCameraIds = mCameraManager.getCameraIdList();
+ mHandlerThread = new HandlerThread("AllocationTest");
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+ mCameraListener = new BlockingStateListener();
+
+ mCameraIterable = new CameraIterable();
+ mSizeIterable = new SizeIterable();
+ mResultIterable = new ResultIterable();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ MaybeNull.close(mCamera);
+
+ // TODO: Clean up RenderScript context in a static test run finished method.
+ // Or alternatively count the # of test methods that are in this test,
+ // once we reach that count, it's time to call the last tear down
+
+ mHandlerThread.quitSafely();
+ mHandler = null;
+ super.tearDown();
+ }
+
+ /**
+ * Update the request with a default manual request template.
+ *
+ * @param request A builder for a CaptureRequest
+ * @param sensitivity ISO gain units (e.g. 100)
+ * @param expTimeNs Exposure time in nanoseconds
+ */
+ private static void setManualCaptureRequest(CaptureRequest.Builder request, int sensitivity,
+ long expTimeNs) {
+ final Rational ONE = new Rational(1, 1);
+ final Rational ZERO = new Rational(0, 1);
+
+ if (VERBOSE) {
+ Log.v(TAG, String.format("Create manual capture request, sensitivity = %d, expTime = %f",
+ sensitivity, expTimeNs / (1000.0 * 1000)));
+ }
+
+ request.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_OFF);
+ request.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_OFF);
+ request.set(CaptureRequest.CONTROL_AWB_MODE, CaptureRequest.CONTROL_AWB_MODE_OFF);
+ request.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_OFF);
+ request.set(CaptureRequest.CONTROL_EFFECT_MODE, CaptureRequest.CONTROL_EFFECT_MODE_OFF);
+ request.set(CaptureRequest.SENSOR_FRAME_DURATION, 0L);
+ request.set(CaptureRequest.SENSOR_SENSITIVITY, sensitivity);
+ request.set(CaptureRequest.SENSOR_EXPOSURE_TIME, expTimeNs);
+ request.set(CaptureRequest.COLOR_CORRECTION_MODE,
+ CaptureRequest.COLOR_CORRECTION_MODE_TRANSFORM_MATRIX);
+
+ // Identity transform
+ request.set(CaptureRequest.COLOR_CORRECTION_TRANSFORM,
+ new ColorSpaceTransform(new Rational[] {
+ ONE, ZERO, ZERO,
+ ZERO, ONE, ZERO,
+ ZERO, ZERO, ONE
+ }));
+
+ // Identity gains
+ request.set(CaptureRequest.COLOR_CORRECTION_GAINS,
+ new RggbChannelVector(1.0f, 1.0f, 1.0f, 1.0f ));
+ request.set(CaptureRequest.TONEMAP_MODE, CaptureRequest.TONEMAP_MODE_FAST);
+ }
+
+ /**
+ * Calculate the absolute crop window from a {@link Size},
+ * and configure {@link LaunchOptions} for it.
+ */
+ // TODO: split patch crop window and the application against a particular size into 2 classes
+ public static class Patch {
+ /**
+ * Create a new {@link Patch} from relative crop coordinates.
+ *
+ * <p>All float values must be normalized coordinates between [0, 1].</p>
+ *
+ * @param size Size of the original rectangle that is being cropped.
+ * @param xNorm The X coordinate defining the left side of the rectangle (in [0, 1]).
+ * @param yNorm The Y coordinate defining the top side of the rectangle (in [0, 1]).
+ * @param wNorm The width of the crop rectangle (normalized between [0, 1]).
+ * @param hNorm The height of the crop rectangle (normalized between [0, 1]).
+ *
+ * @throws NullPointerException if size was {@code null}.
+ * @throws AssertionError if any of the normalized coordinates were out of range
+ */
+ public Patch(Size size, float xNorm, float yNorm, float wNorm, float hNorm) {
+ checkNotNull("size", size);
+
+ assertInRange(xNorm, 0.0f, 1.0f);
+ assertInRange(yNorm, 0.0f, 1.0f);
+ assertInRange(wNorm, 0.0f, 1.0f);
+ assertInRange(hNorm, 0.0f, 1.0f);
+
+ wFull = size.getWidth();
+ hFull = size.getWidth();
+
+ xTile = (int)Math.ceil(xNorm * wFull);
+ yTile = (int)Math.ceil(yNorm * hFull);
+
+ wTile = (int)Math.ceil(wNorm * wFull);
+ hTile = (int)Math.ceil(hNorm * hFull);
+
+ mSourceSize = size;
+ }
+
+ /**
+ * Get the original size used to create this {@link Patch}.
+ *
+ * @return source size
+ */
+ public Size getSourceSize() {
+ return mSourceSize;
+ }
+
+ /**
+ * Get the cropped size after applying the normalized crop window.
+ *
+ * @return cropped size
+ */
+ public Size getSize() {
+ return new Size(wFull, hFull);
+ }
+
+ /**
+ * Get the {@link LaunchOptions} that can be used with a {@link android.renderscript.Script}
+ * to apply a kernel over a subset of an {@link Allocation}.
+ *
+ * @return launch options
+ */
+ public LaunchOptions getLaunchOptions() {
+ return (new LaunchOptions())
+ .setX(xTile, xTile + wTile)
+ .setY(yTile, yTile + hTile);
+ }
+
+ /**
+ * Get the cropped width after applying the normalized crop window.
+ *
+ * @return cropped width
+ */
+ public int getWidth() {
+ return wTile;
+ }
+
+ /**
+ * Get the cropped height after applying the normalized crop window.
+ *
+ * @return cropped height
+ */
+ public int getHeight() {
+ return hTile;
+ }
+
+ /**
+ * Convert to a {@link RectF} where each corner is represented by a
+ * normalized coordinate in between [0.0, 1.0] inclusive.
+ *
+ * @return a new rectangle
+ */
+ public RectF toRectF() {
+ return new RectF(
+ xTile * 1.0f / wFull,
+ yTile * 1.0f / hFull,
+ (xTile + wTile) * 1.0f / wFull,
+ (yTile + hTile) * 1.0f / hFull);
+ }
+
+ private final Size mSourceSize;
+ private final int wFull;
+ private final int hFull;
+ private final int xTile;
+ private final int yTile;
+ private final int wTile;
+ private final int hTile;
+ }
+
+ /**
+ * Convert a single YUV pixel (3 byte elements) to an RGB pixel.
+ *
+ * <p>The color channels must be in the following order:
+ * <ul><li>Y - 0th channel
+ * <li>U - 1st channel
+ * <li>V - 2nd channel
+ * </ul></p>
+ *
+ * <p>Each channel has data in the range 0-255.</p>
+ *
+ * <p>Output data is a 3-element pixel with each channel in the range of [0,1].
+ * Each channel is saturated to avoid over/underflow.</p>
+ *
+ * <p>The conversion is done using JFIF File Interchange Format's "Conversion to and from RGB":
+ * <ul>
+ * <li>R = Y + 1.042 (Cr - 128)
+ * <li>G = Y - 0.34414 (Cb - 128) - 0.71414 (Cr - 128)
+ * <li>B = Y + 1.772 (Cb - 128)
+ * </ul>
+ *
+ * Where Cr and Cb are aliases of V and U respectively.
+ * </p>
+ *
+ * @param yuvData An array of a YUV pixel (at least 3 bytes large)
+ *
+ * @return an RGB888 pixel with each channel in the range of [0,1]
+ */
+ private static float[] convertPixelYuvToRgb(byte[] yuvData) {
+ final int CHANNELS = 3; // yuv
+ final float COLOR_RANGE = 256f;
+
+ assertTrue("YUV pixel must be at least 3 bytes large", CHANNELS <= yuvData.length);
+
+ float[] rgb = new float[CHANNELS];
+
+ float y = yuvData[0] & 0xFF; // Y channel
+ float cb = yuvData[1] & 0xFF; // U channel
+ float cr = yuvData[2] & 0xFF; // V channel
+
+ // convert YUV -> RGB (from JFIF's "Conversion to and from RGB" section)
+ float r = y + 1.402f * (cr - 128);
+ float g = y - 0.34414f * (cb - 128) - 0.71414f * (cr - 128);
+ float b = y + 1.772f * (cb - 128);
+
+ // normalize [0,255] -> [0,1]
+ rgb[0] = r / COLOR_RANGE;
+ rgb[1] = g / COLOR_RANGE;
+ rgb[2] = b / COLOR_RANGE;
+
+ // Clamp to range [0,1]
+ for (int i = 0; i < CHANNELS; ++i) {
+ rgb[i] = Math.max(0.0f, Math.min(1.0f, rgb[i]));
+ }
+
+ if (VERBOSE) {
+ Log.v(TAG, String.format("RGB calculated (r,g,b) = (%f, %f, %f)", rgb[0], rgb[1],
+ rgb[2]));
+ }
+
+ return rgb;
+ }
+
+ /**
+ * Configure the camera with the target surface;
+ * create a capture request builder with {@code cameraTarget} as the sole surface target.
+ *
+ * <p>Outputs are configured with the new surface targets, and this function blocks until
+ * the camera has finished configuring.</p>
+ *
+ * <p>The capture request is created from the {@link CameraDevice#TEMPLATE_PREVIEW} template.
+ * No other keys are set.
+ * </p>
+ */
+ private CaptureRequest.Builder configureAndCreateRequestForSurface(Surface cameraTarget)
+ throws CameraAccessException {
+ List<Surface> outputSurfaces = new ArrayList<Surface>(/*capacity*/1);
+ assertNotNull("Failed to get Surface", cameraTarget);
+ outputSurfaces.add(cameraTarget);
+
+ mCamera.configureOutputs(outputSurfaces);
+ mCameraListener.waitForState(STATE_BUSY, CAMERA_BUSY_TIMEOUT_MS);
+ mCameraListener.waitForState(STATE_IDLE, CAMERA_IDLE_TIMEOUT_MS);
+
+ CaptureRequest.Builder captureBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ assertNotNull("Fail to create captureRequest", captureBuilder);
+ captureBuilder.addTarget(cameraTarget);
+
+ if (VERBOSE) Log.v(TAG, "configureAndCreateRequestForSurface - done");
+
+ return captureBuilder;
+ }
+
+ /**
+ * Submit a single request to the camera, block until the buffer is available.
+ *
+ * <p>Upon return from this function, script has been executed against the latest buffer.
+ * </p>
+ */
+ private void captureSingleShotAndExecute(CaptureRequest request, ScriptGraph graph)
+ throws CameraAccessException {
+ checkNotNull("request", request);
+ checkNotNull("graph", graph);
+
+ mCamera.capture(request, new CameraDevice.CaptureListener() {
+ @Override
+ public void onCaptureCompleted(CameraDevice camera, CaptureRequest request,
+ TotalCaptureResult result) {
+ if (VERBOSE) Log.v(TAG, "Capture completed");
+ }
+ }, mHandler);
+
+ if (VERBOSE) Log.v(TAG, "Waiting for single shot buffer");
+ graph.advanceInputWaiting();
+ if (VERBOSE) Log.v(TAG, "Got the buffer");
+ graph.execute();
+ }
+
+ private void stopCapture() throws CameraAccessException {
+ if (VERBOSE) Log.v(TAG, "Stopping capture and waiting for idle");
+ // Stop repeat, wait for captures to complete, and disconnect from surfaces
+ mCamera.configureOutputs(/*outputs*/null);
+ mCameraListener.waitForState(STATE_BUSY, CAMERA_BUSY_TIMEOUT_MS);
+ mCameraListener.waitForState(STATE_UNCONFIGURED, CAMERA_IDLE_TIMEOUT_MS);
+ }
+
+ /**
+ * Extremely dumb validator. Makes sure there is at least one non-zero RGB pixel value.
+ */
+ private void validateInputOutputNotZeroes(ScriptGraph scriptGraph, Size size) {
+ final int BPP = 8; // bits per pixel
+
+ int width = size.getWidth();
+ int height = size.getHeight();
+ /**
+ * Check the input allocation is sane.
+ * - Byte size matches what we expect.
+ * - The input is not all zeroes.
+ */
+
+ // Check that input data was updated first. If it wasn't, the rest of the test will fail.
+ byte[] data = scriptGraph.getInputData();
+ assertArrayNotAllZeroes("Input allocation data was not updated", data);
+
+ // Minimal required size to represent YUV 4:2:0 image
+ int packedSize =
+ width * height * ImageFormat.getBitsPerPixel(YUV_420_888) / BPP;
+ if (VERBOSE) Log.v(TAG, "Expected image size = " + packedSize);
+ int actualSize = data.length;
+ // Actual size may be larger due to strides or planes being non-contiguous
+ assertTrue(
+ String.format(
+ "YUV 420 packed size (%d) should be at least as large as the actual size " +
+ "(%d)", packedSize, actualSize), packedSize <= actualSize);
+ /**
+ * Check the output allocation by converting to RGBA.
+ * - Byte size matches what we expect
+ * - The output is not all zeroes
+ */
+ final int RGBA_CHANNELS = 4;
+
+ int actualSizeOut = scriptGraph.getOutputAllocation().getBytesSize();
+ int packedSizeOut = width * height * RGBA_CHANNELS;
+
+ byte[] dataOut = scriptGraph.getOutputData();
+ assertEquals("RGB mismatched byte[] and expected size",
+ packedSizeOut, dataOut.length);
+
+ if (VERBOSE) {
+ Log.v(TAG, "checkAllocationByConvertingToRgba - RGB data size " + dataOut.length);
+ }
+
+ assertArrayNotAllZeroes("RGBA data was not updated", dataOut);
+ // RGBA8888 stride should be equal to the width
+ assertEquals("RGBA 8888 mismatched byte[] and expected size", packedSizeOut, actualSizeOut);
+
+ if (VERBOSE) Log.v(TAG, "validating Buffer , size = " + actualSize);
+ }
+
+ public void testAllocationFromCameraFlexibleYuv() throws Exception {
+
+ /** number of frame (for streaming requests) to be verified. */
+ final int NUM_FRAME_VERIFIED = 1;
+
+ mCameraIterable.forEachCamera(new CameraBlock() {
+ @Override
+ public void run(CameraDevice camera) throws CameraAccessException {
+
+ // Iterate over each size in the camera
+ mSizeIterable.forEachSize(YUV_420_888, new SizeBlock() {
+ @Override
+ public void run(final Size size) throws CameraAccessException {
+ // Create a script graph that converts YUV to RGB
+ final ScriptGraph scriptGraph = ScriptGraph.create()
+ .configureInputWithSurface(size, YUV_420_888)
+ .chainScript(ScriptYuvToRgb.class)
+ .buildGraph();
+
+ if (VERBOSE) Log.v(TAG, "Prepared ScriptYuvToRgb for size " + size);
+
+ // Run the graph against camera input and validate we get some input
+ try {
+ CaptureRequest request =
+ configureAndCreateRequestForSurface(scriptGraph.getInputSurface()).build();
+
+ // Block until we get 1 result, then iterate over the result
+ mResultIterable.forEachResultRepeating(
+ request, NUM_FRAME_VERIFIED, new ResultBlock() {
+ @Override
+ public void run(CaptureResult result) throws CameraAccessException {
+ scriptGraph.advanceInputWaiting();
+ scriptGraph.execute();
+ validateInputOutputNotZeroes(scriptGraph, size);
+ scriptGraph.advanceInputAndDrop();
+ }
+ });
+
+ stopCapture();
+ } finally {
+ scriptGraph.close();
+ }
+ }
+ });
+ }
+ });
+ }
+
+ /**
+ * Take two shots and ensure per-frame-control with exposure/gain is working correctly.
+ *
+ * <p>Takes a shot with very low ISO and exposure time. Expect it to be black.</p>
+ *
+ * <p>Take a shot with very high ISO and exposure time. Expect it to be white.</p>
+ *
+ * @throws Exception
+ */
+ public void testBlackWhite() throws CameraAccessException {
+
+ /** low iso + low exposure (first shot) */
+ final float THRESHOLD_LOW = 0.025f;
+ /** high iso + high exposure (second shot) */
+ final float THRESHOLD_HIGH = 0.975f;
+
+ mCameraIterable.forEachCamera(/*fullHwLevel*/true, new CameraBlock() {
+ @Override
+ public void run(CameraDevice camera) throws CameraAccessException {
+
+ final Size maxSize = getMaxSize(
+ getSupportedSizeForFormat(YUV_420_888, camera.getId(), mCameraManager));
+ final StaticMetadata staticInfo =
+ new StaticMetadata(mCameraManager.getCameraCharacteristics(camera.getId()));
+
+ ScriptGraph scriptGraph = createGraphForYuvCroppedMeans(maxSize);
+
+ CaptureRequest.Builder req =
+ configureAndCreateRequestForSurface(scriptGraph.getInputSurface());
+
+ // Take a shot with very low ISO and exposure time. Expect it to be black.
+ int minimumSensitivity = staticInfo.getSensitivityMinimumOrDefault();
+ long minimumExposure = staticInfo.getExposureMinimumOrDefault();
+ setManualCaptureRequest(req, minimumSensitivity, minimumExposure);
+
+ CaptureRequest lowIsoExposureShot = req.build();
+ captureSingleShotAndExecute(lowIsoExposureShot, scriptGraph);
+
+ float[] blackMeans = convertPixelYuvToRgb(scriptGraph.getOutputData());
+
+ // Take a shot with very high ISO and exposure time. Expect it to be white.
+ int maximumSensitivity = staticInfo.getSensitivityMaximumOrDefault();
+ long maximumExposure = staticInfo.getExposureMaximumOrDefault();
+ setManualCaptureRequest(req, maximumSensitivity, maximumExposure);
+
+ CaptureRequest highIsoExposureShot = req.build();
+ captureSingleShotAndExecute(highIsoExposureShot, scriptGraph);
+
+ float[] whiteMeans = convertPixelYuvToRgb(scriptGraph.getOutputData());
+
+ // low iso + low exposure (first shot)
+ assertArrayWithinUpperBound("Black means too high", blackMeans, THRESHOLD_LOW);
+
+ // high iso + high exposure (second shot)
+ assertArrayWithinLowerBound("White means too low", whiteMeans, THRESHOLD_HIGH);
+ }
+ });
+ }
+
+ /**
+ * Test that the android.sensitivity.parameter is applied.
+ */
+ public void testParamSensitivity() throws CameraAccessException {
+ final float THRESHOLD_MAX_MIN_DIFF = 0.3f;
+ final float THRESHOLD_MAX_MIN_RATIO = 2.0f;
+ final int NUM_STEPS = 5;
+ final long EXPOSURE_TIME_NS = 2000000; // 2 seconds
+ final int RGB_CHANNELS = 3;
+
+ final List<float[]> rgbMeans = new ArrayList<float[]>();
+
+ mCameraIterable.forEachCamera(/*fullHwLevel*/true, new CameraBlock() {
+ @Override
+ public void run(CameraDevice camera) throws CameraAccessException {
+
+ final Size maxSize = getMaxSize(
+ getSupportedSizeForFormat(YUV_420_888, camera.getId(), mCameraManager));
+ final StaticMetadata staticInfo =
+ new StaticMetadata(mCameraManager.getCameraCharacteristics(camera.getId()));
+
+ final int sensitivityMin = staticInfo.getSensitivityMinimumOrDefault();
+ final int sensitivityMax = staticInfo.getSensitivityMaximumOrDefault();
+
+ // List each sensitivity from min-max in NUM_STEPS increments
+ int[] sensitivities = new int[NUM_STEPS];
+ for (int i = 0; i < NUM_STEPS; ++i) {
+ int delta = (sensitivityMax - sensitivityMin) / (NUM_STEPS - 1);
+ sensitivities[i] = sensitivityMin + delta * i;
+ }
+
+ ScriptGraph scriptGraph = createGraphForYuvCroppedMeans(maxSize);
+
+ CaptureRequest.Builder req =
+ configureAndCreateRequestForSurface(scriptGraph.getInputSurface());
+
+ // Take burst shots with increasing sensitivity one after other.
+ for (int i = 0; i < NUM_STEPS; ++i) {
+ setManualCaptureRequest(req, sensitivities[i], EXPOSURE_TIME_NS);
+ captureSingleShotAndExecute(req.build(), scriptGraph);
+ float[] means = convertPixelYuvToRgb(scriptGraph.getOutputData());
+ rgbMeans.add(means);
+
+ if (VERBOSE) {
+ Log.v(TAG, "testParamSensitivity - captured image " + i +
+ " with RGB means: " + Arrays.toString(means));
+ }
+ }
+
+ // Test that every consecutive image gets brighter.
+ for (int i = 0; i < rgbMeans.size() - 1; ++i) {
+ float[] curMeans = rgbMeans.get(i);
+ float[] nextMeans = rgbMeans.get(i+1);
+
+ assertArrayNotGreater(
+ String.format("Shot with sensitivity %d should not have higher " +
+ "average means than shot with sensitivity %d",
+ sensitivities[i], sensitivities[i+1]),
+ curMeans, nextMeans);
+ }
+
+ // Test the min-max diff and ratios are within expected thresholds
+ float[] lastMeans = rgbMeans.get(NUM_STEPS - 1);
+ float[] firstMeans = rgbMeans.get(/*location*/0);
+ for (int i = 0; i < RGB_CHANNELS; ++i) {
+ assertTrue(
+ String.format("Sensitivity max-min diff too small (max=%f, min=%f)",
+ lastMeans[i], firstMeans[i]),
+ lastMeans[i] - firstMeans[i] > THRESHOLD_MAX_MIN_DIFF);
+ assertTrue(
+ String.format("Sensitivity max-min ratio too small (max=%f, min=%f)",
+ lastMeans[i], firstMeans[i]),
+ lastMeans[i] / firstMeans[i] > THRESHOLD_MAX_MIN_RATIO);
+ }
+ }
+ });
+
+ }
+
+ /**
+ * Common script graph for manual-capture based tests that determine the average pixel
+ * values of a cropped sub-region.
+ *
+ * <p>Processing chain:
+ *
+ * <pre>
+ * input: YUV_420_888 surface
+ * output: mean YUV value of a central section of the image,
+ * YUV 4:4:4 encoded as U8_3
+ * steps:
+ * 1) crop [0.45,0.45] - [0.55, 0.55]
+ * 2) average columns
+ * 3) average rows
+ * </pre>
+ * </p>
+ */
+ private static ScriptGraph createGraphForYuvCroppedMeans(final Size size) {
+ ScriptGraph scriptGraph = ScriptGraph.create()
+ .configureInputWithSurface(size, YUV_420_888)
+ .configureScript(ScriptYuvCrop.class)
+ .set(ScriptYuvCrop.CROP_WINDOW,
+ new Patch(size, /*x*/0.45f, /*y*/0.45f, /*w*/0.1f, /*h*/0.1f).toRectF())
+ .buildScript()
+ .chainScript(ScriptYuvMeans2dTo1d.class)
+ .chainScript(ScriptYuvMeans1d.class)
+ // TODO: Make a script for YUV 444 -> RGB 888 conversion
+ .buildGraph();
+ return scriptGraph;
+ }
+
+ /*
+ * TODO: Refactor below code into separate classes and to not depend on AllocationTest
+ * inner variables.
+ *
+ * TODO: add javadocs to below methods
+ *
+ * TODO: Figure out if there's some elegant way to compose these forEaches together, so that
+ * the callers don't have to do a ton of nesting
+ */
+
+ interface CameraBlock {
+ void run(CameraDevice camera) throws CameraAccessException;
+ }
+
+ class CameraIterable {
+ public void forEachCamera(CameraBlock runnable)
+ throws CameraAccessException {
+ forEachCamera(/*fullHwLevel*/false, runnable);
+ }
+
+ public void forEachCamera(boolean fullHwLevel, CameraBlock runnable)
+ throws CameraAccessException {
+ assertNotNull("No camera manager", mCameraManager);
+ assertNotNull("No camera IDs", mCameraIds);
+
+ for (int i = 0; i < mCameraIds.length; i++) {
+ // Don't execute the runnable against non-FULL cameras if FULL is required
+ CameraCharacteristics properties =
+ mCameraManager.getCameraCharacteristics(mCameraIds[i]);
+ StaticMetadata staticInfo = new StaticMetadata(properties);
+ if (fullHwLevel && !staticInfo.isHardwareLevelFull()) {
+ Log.i(TAG, String.format(
+ "Skipping this test for camera %d, needs FULL hw level",
+ mCameraIds[i]));
+ continue;
+ }
+
+ // FIXME: hammerhead FFC thinks its FULL but doesnt have per-frame-control
+ if (fullHwLevel &&
+ staticInfo.getCharacteristics().get(CameraCharacteristics.LENS_FACING)
+ != CameraMetadata.LENS_FACING_BACK
+ && "hammerhead".equals(android.os.Build.PRODUCT)) {
+ Log.w(TAG,
+ "FIXME: Front facing camera claims to support per-frame-control " +
+ "but doesn't for product " + android.os.Build.PRODUCT);
+ continue;
+ }
+
+ // Open camera and execute test
+ Log.i(TAG, "Testing Camera " + mCameraIds[i]);
+ try {
+ openDevice(mCameraIds[i]);
+
+ runnable.run(mCamera);
+ } finally {
+ closeDevice(mCameraIds[i]);
+ }
+ }
+ }
+
+ private void openDevice(String cameraId) {
+ if (mCamera != null) {
+ throw new IllegalStateException("Already have open camera device");
+ }
+ try {
+ mCamera = openCamera(
+ mCameraManager, cameraId, mCameraListener, mHandler);
+ } catch (CameraAccessException e) {
+ fail("Fail to open camera synchronously, " + Log.getStackTraceString(e));
+ } catch (BlockingOpenException e) {
+ fail("Fail to open camera asynchronously, " + Log.getStackTraceString(e));
+ }
+ mCameraListener.waitForState(STATE_UNCONFIGURED, CAMERA_OPEN_TIMEOUT_MS);
+ }
+
+ private void closeDevice(String cameraId) {
+ if (mCamera != null) {
+ mCamera.close();
+ mCameraListener.waitForState(STATE_CLOSED, CAMERA_CLOSE_TIMEOUT_MS);
+ mCamera = null;
+ }
+ }
+ }
+
+ interface SizeBlock {
+ void run(Size size) throws CameraAccessException;
+ }
+
+ class SizeIterable {
+ public void forEachSize(int format, SizeBlock runnable) throws CameraAccessException {
+ assertNotNull("No camera opened", mCamera);
+ assertNotNull("No camera manager", mCameraManager);
+
+ CameraCharacteristics properties =
+ mCameraManager.getCameraCharacteristics(mCamera.getId());
+
+ assertNotNull("Can't get camera properties!", properties);
+
+ int[] availableFormats = properties.get(CameraCharacteristics.SCALER_AVAILABLE_FORMATS);
+ assertArrayNotEmpty(availableFormats,
+ "availableFormats should not be empty");
+ Arrays.sort(availableFormats);
+ assertTrue("Can't find the format " + format + " in supported formats " +
+ Arrays.toString(availableFormats),
+ Arrays.binarySearch(availableFormats, format) >= 0);
+
+ Size[] availableSizes = getSupportedSizeForFormat(format, mCamera.getId(),
+ mCameraManager);
+ assertArrayNotEmpty(availableSizes, "availableSizes should not be empty");
+
+ for (Size size : availableSizes) {
+
+ if (VERBOSE) {
+ Log.v(TAG, "Testing size " + size.toString() +
+ " for camera " + mCamera.getId());
+ }
+ runnable.run(size);
+ }
+ }
+ }
+
+ interface ResultBlock {
+ void run(CaptureResult result) throws CameraAccessException;
+ }
+
+ class ResultIterable {
+ public void forEachResultOnce(CaptureRequest request, ResultBlock block)
+ throws CameraAccessException {
+ forEachResult(request, /*count*/1, /*repeating*/false, block);
+ }
+
+ public void forEachResultRepeating(CaptureRequest request, int count, ResultBlock block)
+ throws CameraAccessException {
+ forEachResult(request, count, /*repeating*/true, block);
+ }
+
+ public void forEachResult(CaptureRequest request, int count, boolean repeating,
+ ResultBlock block) throws CameraAccessException {
+
+ // TODO: start capture, i.e. configureOutputs
+
+ SimpleCaptureListener listener = new SimpleCaptureListener();
+
+ if (!repeating) {
+ for (int i = 0; i < count; ++i) {
+ mCamera.capture(request, listener, mHandler);
+ }
+ } else {
+ mCamera.setRepeatingRequest(request, listener, mHandler);
+ }
+
+ // Assume that the device is already IDLE.
+ mCameraListener.waitForState(BlockingStateListener.STATE_ACTIVE,
+ CAMERA_ACTIVE_TIMEOUT_MS);
+
+ for (int i = 0; i < count; ++i) {
+ if (VERBOSE) {
+ Log.v(TAG, String.format("Testing with result %d of %d for camera %s",
+ i, count, mCamera.getId()));
+ }
+
+ CaptureResult result = listener.getCaptureResult(CAPTURE_RESULT_TIMEOUT_MS);
+ block.run(result);
+ }
+
+ if (repeating) {
+ mCamera.stopRepeating();
+ mCameraListener.waitForState(BlockingStateListener.STATE_IDLE,
+ CAMERA_IDLE_TIMEOUT_MS);
+ }
+
+ // TODO: Make a Configure decorator or some such for configureOutputs
+ }
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraCaptureResultTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraCaptureResultTest.java
deleted file mode 100644
index 6a708e3..0000000
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraCaptureResultTest.java
+++ /dev/null
@@ -1,336 +0,0 @@
-/*
- * Copyright 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.hardware.camera2.cts;
-
-import android.content.Context;
-import android.graphics.ImageFormat;
-import android.hardware.camera2.CameraCharacteristics;
-import android.hardware.camera2.CameraDevice;
-import android.hardware.camera2.CameraManager;
-import android.hardware.camera2.CameraMetadata;
-import android.hardware.camera2.CaptureFailure;
-import android.hardware.camera2.CaptureRequest;
-import android.hardware.camera2.CaptureResult;
-import android.hardware.camera2.Size;
-import static android.hardware.camera2.cts.CameraTestUtils.*;
-import android.media.ImageReader;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.test.AndroidTestCase;
-import android.util.Log;
-import android.view.Surface;
-
-import com.android.ex.camera2.blocking.BlockingStateListener;
-import static com.android.ex.camera2.blocking.BlockingStateListener.*;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-
-public class CameraCaptureResultTest extends AndroidTestCase {
- private static final String TAG = "CameraCaptureResultTest";
- private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
-
- private CameraManager mCameraManager;
- private HandlerThread mHandlerThread;
- private Handler mHandler;
- private ImageReader mImageReader;
- private Surface mSurface;
- private BlockingStateListener mCameraListener;
-
- private static final int MAX_NUM_IMAGES = 5;
- private static final int NUM_FRAMES_VERIFIED = 300;
- private static final long WAIT_FOR_RESULT_TIMEOUT_MS = 3000;
-
- // List that includes all public keys from CaptureResult
- List<CameraMetadata.Key<?>> mAllKeys;
-
- // List tracking the failed test keys.
- List<CameraMetadata.Key<?>> mFailedKeys = new ArrayList<CameraMetadata.Key<?>>();
-
- @Override
- public void setContext(Context context) {
- mAllKeys = getAllCaptureResultKeys();
- super.setContext(context);
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
- assertNotNull("Can't connect to camera manager", mCameraManager);
- mHandlerThread = new HandlerThread(TAG);
- mHandlerThread.start();
- mHandler = new Handler(mHandlerThread.getLooper());
- mCameraListener = new BlockingStateListener();
- mFailedKeys.clear();
- }
-
- @Override
- protected void tearDown() throws Exception {
- mHandlerThread.quitSafely();
- super.tearDown();
- }
-
- public void testCameraCaptureResultAllKeys() throws Exception {
- /**
- * Hardcode a key waiver list for the keys we want to skip the sanity check.
- * FIXME: We need get ride of this list, see bug 11116270.
- */
- List<CameraMetadata.Key<?>> waiverkeys = new ArrayList<CameraMetadata.Key<?>>();
- waiverkeys.add(CaptureResult.EDGE_MODE);
- waiverkeys.add(CaptureResult.JPEG_GPS_COORDINATES);
- waiverkeys.add(CaptureResult.JPEG_GPS_PROCESSING_METHOD);
- waiverkeys.add(CaptureResult.JPEG_GPS_TIMESTAMP);
- waiverkeys.add(CaptureResult.JPEG_ORIENTATION);
- waiverkeys.add(CaptureResult.JPEG_QUALITY);
- waiverkeys.add(CaptureResult.JPEG_THUMBNAIL_QUALITY);
- waiverkeys.add(CaptureResult.JPEG_THUMBNAIL_SIZE);
- waiverkeys.add(CaptureResult.SENSOR_TEMPERATURE);
- waiverkeys.add(CaptureResult.TONEMAP_CURVE_BLUE);
- waiverkeys.add(CaptureResult.TONEMAP_CURVE_GREEN);
- waiverkeys.add(CaptureResult.TONEMAP_CURVE_RED);
- waiverkeys.add(CaptureResult.TONEMAP_MODE);
- waiverkeys.add(CaptureResult.STATISTICS_PREDICTED_COLOR_GAINS);
- waiverkeys.add(CaptureResult.STATISTICS_PREDICTED_COLOR_TRANSFORM);
- waiverkeys.add(CaptureResult.STATISTICS_SCENE_FLICKER);
-
- String[] ids = mCameraManager.getCameraIdList();
- for (int i = 0; i < ids.length; i++) {
- CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
- assertNotNull("CameraCharacteristics shouldn't be null", props);
- Integer hwLevel = props.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
- if (hwLevel != CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL) {
- continue;
- }
- // TODO: check for LIMITED keys
-
- CameraDevice camera = null;
- try {
- Size[] sizes = CameraTestUtils.getSupportedSizeForFormat(
- ImageFormat.YUV_420_888, ids[i], mCameraManager);
- CameraTestUtils.assertArrayNotEmpty(sizes, "Available sizes shouldn't be empty");
- createDefaultSurface(sizes[0]);
-
- if (VERBOSE) {
- Log.v(TAG, "Testing camera " + ids[i] + "for size " + sizes[0].toString());
- }
-
- camera = CameraTestUtils.openCamera(
- mCameraManager, ids[i], mCameraListener, mHandler);
- assertNotNull(
- String.format("Failed to open camera device %s", ids[i]), camera);
- mCameraListener.waitForState(STATE_UNCONFIGURED, CAMERA_OPEN_TIMEOUT_MS);
-
- List<Surface> outputSurfaces = new ArrayList<Surface>(1);
- outputSurfaces.add(mSurface);
- camera.configureOutputs(outputSurfaces);
- mCameraListener.waitForState(STATE_BUSY, CAMERA_BUSY_TIMEOUT_MS);
- mCameraListener.waitForState(STATE_IDLE, CAMERA_IDLE_TIMEOUT_MS);
-
- CaptureRequest.Builder requestBuilder =
- camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
- assertNotNull("Failed to create capture request", requestBuilder);
- requestBuilder.addTarget(mSurface);
-
- // Enable face detection if supported
- byte[] faceModes = props.get(
- CameraCharacteristics.STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES);
- assertNotNull("Available face detection modes shouldn't be null", faceModes);
- for (int m = 0; m < faceModes.length; m++) {
- if (faceModes[m] == CameraMetadata.STATISTICS_FACE_DETECT_MODE_FULL) {
- if (VERBOSE) {
- Log.v(TAG, "testCameraCaptureResultAllKeys - " +
- "setting facedetection mode to full");
- }
- requestBuilder.set(CaptureRequest.STATISTICS_FACE_DETECT_MODE,
- (int)faceModes[m]);
- }
- }
-
- // Enable lensShading mode, it should be supported by full mode device.
- requestBuilder.set(CaptureRequest.STATISTICS_LENS_SHADING_MAP_MODE,
- CameraMetadata.STATISTICS_LENS_SHADING_MAP_MODE_ON);
-
- SimpleCaptureListener captureListener = new SimpleCaptureListener();
- camera.setRepeatingRequest(requestBuilder.build(), captureListener, mHandler);
-
- for (int m = 0; m < NUM_FRAMES_VERIFIED; m++) {
- if(VERBOSE) {
- Log.v(TAG, "Testing frame " + m);
- }
- validateCaptureResult(
- captureListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS),
- waiverkeys);
- }
-
- // Stop repeat, wait for captures to complete, and disconnect from surfaces
- camera.configureOutputs(/*outputs*/ null);
- mCameraListener.waitForState(STATE_BUSY, CAMERA_BUSY_TIMEOUT_MS);
- mCameraListener.waitForState(STATE_UNCONFIGURED, CAMERA_IDLE_TIMEOUT_MS);
- // Camera has disconnected, clear out the reader
- mSurface.release();
- mImageReader.close();
- } finally {
- if (camera != null) {
- camera.close();
- }
- }
-
- }
- }
-
- private void validateCaptureResult(CaptureResult result,
- List<CameraMetadata.Key<?>> skippedKeys) throws Exception {
- for (CameraMetadata.Key<?> key : mAllKeys) {
- if (!skippedKeys.contains(key) && result.get(key) == null) {
- mFailedKeys.add(key);
- }
- }
-
- StringBuffer failedKeyNames = new StringBuffer("Below Keys have null values:\n");
- for (CameraMetadata.Key<?> key : mFailedKeys) {
- failedKeyNames.append(key.getName() + "\n");
- }
-
- assertTrue("Some keys have null values, " + failedKeyNames.toString(),
- mFailedKeys.isEmpty());
- }
-
- private static class SimpleCaptureListener extends CameraDevice.CaptureListener {
- LinkedBlockingQueue<CaptureResult> mQueue = new LinkedBlockingQueue<CaptureResult>();
-
- @Override
- public void onCaptureStarted(CameraDevice camera, CaptureRequest request, long timestamp)
- {
- }
-
- @Override
- public void onCaptureCompleted(CameraDevice camera, CaptureRequest request,
- CaptureResult result) {
- try {
- mQueue.put(result);
- } catch (InterruptedException e) {
- throw new UnsupportedOperationException(
- "Can't handle InterruptedException in onCaptureCompleted");
- }
- }
-
- @Override
- public void onCaptureFailed(CameraDevice camera, CaptureRequest request,
- CaptureFailure failure) {
- }
-
- @Override
- public void onCaptureSequenceCompleted(CameraDevice camera, int sequenceId,
- int frameNumber) {
- }
-
- public CaptureResult getCaptureResult(long timeout) throws InterruptedException {
- CaptureResult result = mQueue.poll(timeout, TimeUnit.MILLISECONDS);
- assertNotNull("Wait for a capture result timed out in " + timeout + "ms", result);
- return result;
- }
- }
-
- private void createDefaultSurface(Size sz) {
- mImageReader =
- ImageReader.newInstance(sz.getWidth(),
- sz.getHeight(),
- ImageFormat.YUV_420_888,
- MAX_NUM_IMAGES);
- mImageReader.setOnImageAvailableListener(new ImageDropperListener(), mHandler);
- mSurface = mImageReader.getSurface();
- }
-
- /**
- * TODO: Use CameraCharacteristics.getAvailableCaptureResultKeys() once we can filter out
- * @hide keys.
- *
- */
-
- /*@O~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~
- * The key entries below this point are generated from metadata
- * definitions in /system/media/camera/docs. Do not modify by hand or
- * modify the comment blocks at the start or end.
- *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~*/
-
- private static List<CameraMetadata.Key<?>> getAllCaptureResultKeys() {
- ArrayList<CameraMetadata.Key<?>> resultKeys = new ArrayList<CameraMetadata.Key<?>>();
- resultKeys.add(CaptureResult.COLOR_CORRECTION_TRANSFORM);
- resultKeys.add(CaptureResult.COLOR_CORRECTION_GAINS);
- resultKeys.add(CaptureResult.CONTROL_AE_REGIONS);
- resultKeys.add(CaptureResult.CONTROL_AF_MODE);
- resultKeys.add(CaptureResult.CONTROL_AF_REGIONS);
- resultKeys.add(CaptureResult.CONTROL_AWB_MODE);
- resultKeys.add(CaptureResult.CONTROL_AWB_REGIONS);
- resultKeys.add(CaptureResult.CONTROL_MODE);
- resultKeys.add(CaptureResult.CONTROL_AE_STATE);
- resultKeys.add(CaptureResult.CONTROL_AF_STATE);
- resultKeys.add(CaptureResult.CONTROL_AWB_STATE);
- resultKeys.add(CaptureResult.EDGE_MODE);
- resultKeys.add(CaptureResult.FLASH_MODE);
- resultKeys.add(CaptureResult.FLASH_STATE);
- resultKeys.add(CaptureResult.JPEG_GPS_COORDINATES);
- resultKeys.add(CaptureResult.JPEG_GPS_PROCESSING_METHOD);
- resultKeys.add(CaptureResult.JPEG_GPS_TIMESTAMP);
- resultKeys.add(CaptureResult.JPEG_ORIENTATION);
- resultKeys.add(CaptureResult.JPEG_QUALITY);
- resultKeys.add(CaptureResult.JPEG_THUMBNAIL_QUALITY);
- resultKeys.add(CaptureResult.JPEG_THUMBNAIL_SIZE);
- resultKeys.add(CaptureResult.LENS_APERTURE);
- resultKeys.add(CaptureResult.LENS_FILTER_DENSITY);
- resultKeys.add(CaptureResult.LENS_FOCAL_LENGTH);
- resultKeys.add(CaptureResult.LENS_FOCUS_DISTANCE);
- resultKeys.add(CaptureResult.LENS_OPTICAL_STABILIZATION_MODE);
- resultKeys.add(CaptureResult.LENS_FOCUS_RANGE);
- resultKeys.add(CaptureResult.LENS_STATE);
- resultKeys.add(CaptureResult.NOISE_REDUCTION_MODE);
- resultKeys.add(CaptureResult.REQUEST_FRAME_COUNT);
- resultKeys.add(CaptureResult.SCALER_CROP_REGION);
- resultKeys.add(CaptureResult.SENSOR_EXPOSURE_TIME);
- resultKeys.add(CaptureResult.SENSOR_FRAME_DURATION);
- resultKeys.add(CaptureResult.SENSOR_SENSITIVITY);
- resultKeys.add(CaptureResult.SENSOR_TIMESTAMP);
- resultKeys.add(CaptureResult.SENSOR_TEMPERATURE);
- resultKeys.add(CaptureResult.STATISTICS_FACE_DETECT_MODE);
- resultKeys.add(CaptureResult.STATISTICS_FACE_IDS);
- resultKeys.add(CaptureResult.STATISTICS_FACE_LANDMARKS);
- resultKeys.add(CaptureResult.STATISTICS_FACE_RECTANGLES);
- resultKeys.add(CaptureResult.STATISTICS_FACE_SCORES);
- resultKeys.add(CaptureResult.STATISTICS_LENS_SHADING_MAP);
- resultKeys.add(CaptureResult.STATISTICS_PREDICTED_COLOR_GAINS);
- resultKeys.add(CaptureResult.STATISTICS_PREDICTED_COLOR_TRANSFORM);
- resultKeys.add(CaptureResult.STATISTICS_SCENE_FLICKER);
- resultKeys.add(CaptureResult.TONEMAP_CURVE_BLUE);
- resultKeys.add(CaptureResult.TONEMAP_CURVE_GREEN);
- resultKeys.add(CaptureResult.TONEMAP_CURVE_RED);
- resultKeys.add(CaptureResult.TONEMAP_MODE);
- resultKeys.add(CaptureResult.BLACK_LEVEL_LOCK);
-
- // Add STATISTICS_FACES key separately here because it is not
- // defined in metadata xml file.
- resultKeys.add(CaptureResult.STATISTICS_FACES);
-
- return resultKeys;
- }
-
- /*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~
- * End generated code
- *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/
-}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraCharacteristicsTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraCharacteristicsTest.java
index 1b892ba..effb6ba 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraCharacteristicsTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraCharacteristicsTest.java
@@ -27,7 +27,7 @@
import android.content.Context;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraManager;
-import android.hardware.camera2.CameraMetadata.Key;
+import android.hardware.camera2.CameraCharacteristics.Key;
import android.test.AndroidTestCase;
import android.util.Log;
@@ -80,6 +80,29 @@
}
}
+ public void testCameraCharacteristicsAndroidControlAeAvailableModes() throws Exception {
+ String[] ids = mCameraManager.getCameraIdList();
+ for (int i = 0; i < ids.length; i++) {
+ CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
+ assertNotNull(String.format("Can't get camera characteristics from: ID %s", ids[i]),
+ props);
+
+ {
+
+ assertNotNull("Invalid property: android.control.aeAvailableModes",
+ props.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES));
+
+ List<Key<?>> allKeys = props.getKeys();
+ assertNotNull(String.format("Can't get camera characteristics keys from: ID %s",
+ ids[i], props));
+ assertTrue("Key not in keys list: android.control.aeAvailableModes", allKeys.contains(
+ CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES));
+
+ }
+
+ }
+ }
+
public void testCameraCharacteristicsAndroidControlAeAvailableTargetFpsRanges() throws Exception {
String[] ids = mCameraManager.getCameraIdList();
for (int i = 0; i < ids.length; i++) {
@@ -264,7 +287,7 @@
}
}
- public void testCameraCharacteristicsAndroidControlMaxRegions() throws Exception {
+ public void testCameraCharacteristicsAndroidControlMaxRegionsAe() throws Exception {
String[] ids = mCameraManager.getCameraIdList();
for (int i = 0; i < ids.length; i++) {
CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
@@ -273,14 +296,83 @@
{
- assertNotNull("Invalid property: android.control.maxRegions",
- props.get(CameraCharacteristics.CONTROL_MAX_REGIONS));
+ assertNotNull("Invalid property: android.control.maxRegionsAe",
+ props.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AE));
List<Key<?>> allKeys = props.getKeys();
assertNotNull(String.format("Can't get camera characteristics keys from: ID %s",
ids[i], props));
- assertTrue("Key not in keys list: android.control.maxRegions", allKeys.contains(
- CameraCharacteristics.CONTROL_MAX_REGIONS));
+ assertTrue("Key not in keys list: android.control.maxRegionsAe", allKeys.contains(
+ CameraCharacteristics.CONTROL_MAX_REGIONS_AE));
+
+ }
+
+ }
+ }
+
+ public void testCameraCharacteristicsAndroidControlMaxRegionsAwb() throws Exception {
+ String[] ids = mCameraManager.getCameraIdList();
+ for (int i = 0; i < ids.length; i++) {
+ CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
+ assertNotNull(String.format("Can't get camera characteristics from: ID %s", ids[i]),
+ props);
+
+ {
+
+ assertNotNull("Invalid property: android.control.maxRegionsAwb",
+ props.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AWB));
+
+ List<Key<?>> allKeys = props.getKeys();
+ assertNotNull(String.format("Can't get camera characteristics keys from: ID %s",
+ ids[i], props));
+ assertTrue("Key not in keys list: android.control.maxRegionsAwb", allKeys.contains(
+ CameraCharacteristics.CONTROL_MAX_REGIONS_AWB));
+
+ }
+
+ }
+ }
+
+ public void testCameraCharacteristicsAndroidControlMaxRegionsAf() throws Exception {
+ String[] ids = mCameraManager.getCameraIdList();
+ for (int i = 0; i < ids.length; i++) {
+ CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
+ assertNotNull(String.format("Can't get camera characteristics from: ID %s", ids[i]),
+ props);
+
+ {
+
+ assertNotNull("Invalid property: android.control.maxRegionsAf",
+ props.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AF));
+
+ List<Key<?>> allKeys = props.getKeys();
+ assertNotNull(String.format("Can't get camera characteristics keys from: ID %s",
+ ids[i], props));
+ assertTrue("Key not in keys list: android.control.maxRegionsAf", allKeys.contains(
+ CameraCharacteristics.CONTROL_MAX_REGIONS_AF));
+
+ }
+
+ }
+ }
+
+ public void testCameraCharacteristicsAndroidEdgeAvailableEdgeModes() throws Exception {
+ String[] ids = mCameraManager.getCameraIdList();
+ for (int i = 0; i < ids.length; i++) {
+ CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
+ assertNotNull(String.format("Can't get camera characteristics from: ID %s", ids[i]),
+ props);
+
+ {
+
+ assertNotNull("Invalid property: android.edge.availableEdgeModes",
+ props.get(CameraCharacteristics.EDGE_AVAILABLE_EDGE_MODES));
+
+ List<Key<?>> allKeys = props.getKeys();
+ assertNotNull(String.format("Can't get camera characteristics keys from: ID %s",
+ ids[i], props));
+ assertTrue("Key not in keys list: android.edge.availableEdgeModes", allKeys.contains(
+ CameraCharacteristics.EDGE_AVAILABLE_EDGE_MODES));
}
@@ -310,6 +402,29 @@
}
}
+ public void testCameraCharacteristicsAndroidHotPixelAvailableHotPixelModes() throws Exception {
+ String[] ids = mCameraManager.getCameraIdList();
+ for (int i = 0; i < ids.length; i++) {
+ CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
+ assertNotNull(String.format("Can't get camera characteristics from: ID %s", ids[i]),
+ props);
+
+ {
+
+ assertNotNull("Invalid property: android.hotPixel.availableHotPixelModes",
+ props.get(CameraCharacteristics.HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES));
+
+ List<Key<?>> allKeys = props.getKeys();
+ assertNotNull(String.format("Can't get camera characteristics keys from: ID %s",
+ ids[i], props));
+ assertTrue("Key not in keys list: android.hotPixel.availableHotPixelModes", allKeys.contains(
+ CameraCharacteristics.HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES));
+
+ }
+
+ }
+ }
+
public void testCameraCharacteristicsAndroidJpegAvailableThumbnailSizes() throws Exception {
String[] ids = mCameraManager.getCameraIdList();
for (int i = 0; i < ids.length; i++) {
@@ -455,6 +570,10 @@
assertNotNull(String.format("Can't get camera characteristics from: ID %s", ids[i]),
props);
+ Integer hwLevel = props.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
+ assertNotNull("No hardware level reported! android.info.supportedHardwareLevel",
+ hwLevel);
+ if (hwLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL)
{
assertNotNull("Invalid property: android.lens.info.hyperfocalDistance",
@@ -494,7 +613,7 @@
}
}
- public void testCameraCharacteristicsAndroidLensInfoShadingMapSize() throws Exception {
+ public void testCameraCharacteristicsAndroidLensInfoFocusDistanceCalibration() throws Exception {
String[] ids = mCameraManager.getCameraIdList();
for (int i = 0; i < ids.length; i++) {
CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
@@ -503,21 +622,21 @@
{
- assertNotNull("Invalid property: android.lens.info.shadingMapSize",
- props.get(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE));
+ assertNotNull("Invalid property: android.lens.info.focusDistanceCalibration",
+ props.get(CameraCharacteristics.LENS_INFO_FOCUS_DISTANCE_CALIBRATION));
List<Key<?>> allKeys = props.getKeys();
assertNotNull(String.format("Can't get camera characteristics keys from: ID %s",
ids[i], props));
- assertTrue("Key not in keys list: android.lens.info.shadingMapSize", allKeys.contains(
- CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE));
+ assertTrue("Key not in keys list: android.lens.info.focusDistanceCalibration", allKeys.contains(
+ CameraCharacteristics.LENS_INFO_FOCUS_DISTANCE_CALIBRATION));
}
}
}
- public void testCameraCharacteristicsAndroidRequestMaxNumOutputStreams() throws Exception {
+ public void testCameraCharacteristicsAndroidNoiseReductionAvailableNoiseReductionModes() throws Exception {
String[] ids = mCameraManager.getCameraIdList();
for (int i = 0; i < ids.length; i++) {
CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
@@ -526,21 +645,21 @@
{
- assertNotNull("Invalid property: android.request.maxNumOutputStreams",
- props.get(CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_STREAMS));
+ assertNotNull("Invalid property: android.noiseReduction.availableNoiseReductionModes",
+ props.get(CameraCharacteristics.NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES));
List<Key<?>> allKeys = props.getKeys();
assertNotNull(String.format("Can't get camera characteristics keys from: ID %s",
ids[i], props));
- assertTrue("Key not in keys list: android.request.maxNumOutputStreams", allKeys.contains(
- CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_STREAMS));
+ assertTrue("Key not in keys list: android.noiseReduction.availableNoiseReductionModes", allKeys.contains(
+ CameraCharacteristics.NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES));
}
}
}
- public void testCameraCharacteristicsAndroidScalerAvailableFormats() throws Exception {
+ public void testCameraCharacteristicsAndroidRequestMaxNumOutputRaw() throws Exception {
String[] ids = mCameraManager.getCameraIdList();
for (int i = 0; i < ids.length; i++) {
CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
@@ -549,21 +668,21 @@
{
- assertNotNull("Invalid property: android.scaler.availableFormats",
- props.get(CameraCharacteristics.SCALER_AVAILABLE_FORMATS));
+ assertNotNull("Invalid property: android.request.maxNumOutputRaw",
+ props.get(CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_RAW));
List<Key<?>> allKeys = props.getKeys();
assertNotNull(String.format("Can't get camera characteristics keys from: ID %s",
ids[i], props));
- assertTrue("Key not in keys list: android.scaler.availableFormats", allKeys.contains(
- CameraCharacteristics.SCALER_AVAILABLE_FORMATS));
+ assertTrue("Key not in keys list: android.request.maxNumOutputRaw", allKeys.contains(
+ CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_RAW));
}
}
}
- public void testCameraCharacteristicsAndroidScalerAvailableJpegMinDurations() throws Exception {
+ public void testCameraCharacteristicsAndroidRequestMaxNumOutputProc() throws Exception {
String[] ids = mCameraManager.getCameraIdList();
for (int i = 0; i < ids.length; i++) {
CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
@@ -572,21 +691,21 @@
{
- assertNotNull("Invalid property: android.scaler.availableJpegMinDurations",
- props.get(CameraCharacteristics.SCALER_AVAILABLE_JPEG_MIN_DURATIONS));
+ assertNotNull("Invalid property: android.request.maxNumOutputProc",
+ props.get(CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_PROC));
List<Key<?>> allKeys = props.getKeys();
assertNotNull(String.format("Can't get camera characteristics keys from: ID %s",
ids[i], props));
- assertTrue("Key not in keys list: android.scaler.availableJpegMinDurations", allKeys.contains(
- CameraCharacteristics.SCALER_AVAILABLE_JPEG_MIN_DURATIONS));
+ assertTrue("Key not in keys list: android.request.maxNumOutputProc", allKeys.contains(
+ CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_PROC));
}
}
}
- public void testCameraCharacteristicsAndroidScalerAvailableJpegSizes() throws Exception {
+ public void testCameraCharacteristicsAndroidRequestMaxNumOutputProcStalling() throws Exception {
String[] ids = mCameraManager.getCameraIdList();
for (int i = 0; i < ids.length; i++) {
CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
@@ -595,14 +714,106 @@
{
- assertNotNull("Invalid property: android.scaler.availableJpegSizes",
- props.get(CameraCharacteristics.SCALER_AVAILABLE_JPEG_SIZES));
+ assertNotNull("Invalid property: android.request.maxNumOutputProcStalling",
+ props.get(CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_PROC_STALLING));
List<Key<?>> allKeys = props.getKeys();
assertNotNull(String.format("Can't get camera characteristics keys from: ID %s",
ids[i], props));
- assertTrue("Key not in keys list: android.scaler.availableJpegSizes", allKeys.contains(
- CameraCharacteristics.SCALER_AVAILABLE_JPEG_SIZES));
+ assertTrue("Key not in keys list: android.request.maxNumOutputProcStalling", allKeys.contains(
+ CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_PROC_STALLING));
+
+ }
+
+ }
+ }
+
+ public void testCameraCharacteristicsAndroidRequestMaxNumInputStreams() throws Exception {
+ String[] ids = mCameraManager.getCameraIdList();
+ for (int i = 0; i < ids.length; i++) {
+ CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
+ assertNotNull(String.format("Can't get camera characteristics from: ID %s", ids[i]),
+ props);
+
+ {
+
+ assertNotNull("Invalid property: android.request.maxNumInputStreams",
+ props.get(CameraCharacteristics.REQUEST_MAX_NUM_INPUT_STREAMS));
+
+ List<Key<?>> allKeys = props.getKeys();
+ assertNotNull(String.format("Can't get camera characteristics keys from: ID %s",
+ ids[i], props));
+ assertTrue("Key not in keys list: android.request.maxNumInputStreams", allKeys.contains(
+ CameraCharacteristics.REQUEST_MAX_NUM_INPUT_STREAMS));
+
+ }
+
+ }
+ }
+
+ public void testCameraCharacteristicsAndroidRequestPipelineMaxDepth() throws Exception {
+ String[] ids = mCameraManager.getCameraIdList();
+ for (int i = 0; i < ids.length; i++) {
+ CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
+ assertNotNull(String.format("Can't get camera characteristics from: ID %s", ids[i]),
+ props);
+
+ {
+
+ assertNotNull("Invalid property: android.request.pipelineMaxDepth",
+ props.get(CameraCharacteristics.REQUEST_PIPELINE_MAX_DEPTH));
+
+ List<Key<?>> allKeys = props.getKeys();
+ assertNotNull(String.format("Can't get camera characteristics keys from: ID %s",
+ ids[i], props));
+ assertTrue("Key not in keys list: android.request.pipelineMaxDepth", allKeys.contains(
+ CameraCharacteristics.REQUEST_PIPELINE_MAX_DEPTH));
+
+ }
+
+ }
+ }
+
+ public void testCameraCharacteristicsAndroidRequestPartialResultCount() throws Exception {
+ String[] ids = mCameraManager.getCameraIdList();
+ for (int i = 0; i < ids.length; i++) {
+ CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
+ assertNotNull(String.format("Can't get camera characteristics from: ID %s", ids[i]),
+ props);
+
+ {
+
+ assertNotNull("Invalid property: android.request.partialResultCount",
+ props.get(CameraCharacteristics.REQUEST_PARTIAL_RESULT_COUNT));
+
+ List<Key<?>> allKeys = props.getKeys();
+ assertNotNull(String.format("Can't get camera characteristics keys from: ID %s",
+ ids[i], props));
+ assertTrue("Key not in keys list: android.request.partialResultCount", allKeys.contains(
+ CameraCharacteristics.REQUEST_PARTIAL_RESULT_COUNT));
+
+ }
+
+ }
+ }
+
+ public void testCameraCharacteristicsAndroidRequestAvailableCapabilities() throws Exception {
+ String[] ids = mCameraManager.getCameraIdList();
+ for (int i = 0; i < ids.length; i++) {
+ CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
+ assertNotNull(String.format("Can't get camera characteristics from: ID %s", ids[i]),
+ props);
+
+ {
+
+ assertNotNull("Invalid property: android.request.availableCapabilities",
+ props.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES));
+
+ List<Key<?>> allKeys = props.getKeys();
+ assertNotNull(String.format("Can't get camera characteristics keys from: ID %s",
+ ids[i], props));
+ assertTrue("Key not in keys list: android.request.availableCapabilities", allKeys.contains(
+ CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES));
}
@@ -632,7 +843,7 @@
}
}
- public void testCameraCharacteristicsAndroidScalerAvailableProcessedMinDurations() throws Exception {
+ public void testCameraCharacteristicsAndroidScalerStreamConfigurationMap() throws Exception {
String[] ids = mCameraManager.getCameraIdList();
for (int i = 0; i < ids.length; i++) {
CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
@@ -641,21 +852,21 @@
{
- assertNotNull("Invalid property: android.scaler.availableProcessedMinDurations",
- props.get(CameraCharacteristics.SCALER_AVAILABLE_PROCESSED_MIN_DURATIONS));
+ assertNotNull("Invalid property: android.scaler.streamConfigurationMap",
+ props.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP));
List<Key<?>> allKeys = props.getKeys();
assertNotNull(String.format("Can't get camera characteristics keys from: ID %s",
ids[i], props));
- assertTrue("Key not in keys list: android.scaler.availableProcessedMinDurations", allKeys.contains(
- CameraCharacteristics.SCALER_AVAILABLE_PROCESSED_MIN_DURATIONS));
+ assertTrue("Key not in keys list: android.scaler.streamConfigurationMap", allKeys.contains(
+ CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP));
}
}
}
- public void testCameraCharacteristicsAndroidScalerAvailableProcessedSizes() throws Exception {
+ public void testCameraCharacteristicsAndroidScalerCroppingType() throws Exception {
String[] ids = mCameraManager.getCameraIdList();
for (int i = 0; i < ids.length; i++) {
CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
@@ -664,21 +875,67 @@
{
- assertNotNull("Invalid property: android.scaler.availableProcessedSizes",
- props.get(CameraCharacteristics.SCALER_AVAILABLE_PROCESSED_SIZES));
+ assertNotNull("Invalid property: android.scaler.croppingType",
+ props.get(CameraCharacteristics.SCALER_CROPPING_TYPE));
List<Key<?>> allKeys = props.getKeys();
assertNotNull(String.format("Can't get camera characteristics keys from: ID %s",
ids[i], props));
- assertTrue("Key not in keys list: android.scaler.availableProcessedSizes", allKeys.contains(
- CameraCharacteristics.SCALER_AVAILABLE_PROCESSED_SIZES));
+ assertTrue("Key not in keys list: android.scaler.croppingType", allKeys.contains(
+ CameraCharacteristics.SCALER_CROPPING_TYPE));
}
}
}
- public void testCameraCharacteristicsAndroidSensorBaseGainFactor() throws Exception {
+ public void testCameraCharacteristicsAndroidSensorReferenceIlluminant1() throws Exception {
+ String[] ids = mCameraManager.getCameraIdList();
+ for (int i = 0; i < ids.length; i++) {
+ CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
+ assertNotNull(String.format("Can't get camera characteristics from: ID %s", ids[i]),
+ props);
+
+ {
+
+ assertNotNull("Invalid property: android.sensor.referenceIlluminant1",
+ props.get(CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT1));
+
+ List<Key<?>> allKeys = props.getKeys();
+ assertNotNull(String.format("Can't get camera characteristics keys from: ID %s",
+ ids[i], props));
+ assertTrue("Key not in keys list: android.sensor.referenceIlluminant1", allKeys.contains(
+ CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT1));
+
+ }
+
+ }
+ }
+
+ public void testCameraCharacteristicsAndroidSensorReferenceIlluminant2() throws Exception {
+ String[] ids = mCameraManager.getCameraIdList();
+ for (int i = 0; i < ids.length; i++) {
+ CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
+ assertNotNull(String.format("Can't get camera characteristics from: ID %s", ids[i]),
+ props);
+
+ {
+
+ assertNotNull("Invalid property: android.sensor.referenceIlluminant2",
+ props.get(CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT2));
+
+ List<Key<?>> allKeys = props.getKeys();
+ assertNotNull(String.format("Can't get camera characteristics keys from: ID %s",
+ ids[i], props));
+ assertTrue("Key not in keys list: android.sensor.referenceIlluminant2", allKeys.contains(
+ CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT2));
+
+ }
+
+ }
+ }
+
+ public void testCameraCharacteristicsAndroidSensorCalibrationTransform1() throws Exception {
String[] ids = mCameraManager.getCameraIdList();
for (int i = 0; i < ids.length; i++) {
CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
@@ -691,14 +948,176 @@
if (hwLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL)
{
- assertNotNull("Invalid property: android.sensor.baseGainFactor",
- props.get(CameraCharacteristics.SENSOR_BASE_GAIN_FACTOR));
+ assertNotNull("Invalid property: android.sensor.calibrationTransform1",
+ props.get(CameraCharacteristics.SENSOR_CALIBRATION_TRANSFORM1));
List<Key<?>> allKeys = props.getKeys();
assertNotNull(String.format("Can't get camera characteristics keys from: ID %s",
ids[i], props));
- assertTrue("Key not in keys list: android.sensor.baseGainFactor", allKeys.contains(
- CameraCharacteristics.SENSOR_BASE_GAIN_FACTOR));
+ assertTrue("Key not in keys list: android.sensor.calibrationTransform1", allKeys.contains(
+ CameraCharacteristics.SENSOR_CALIBRATION_TRANSFORM1));
+
+ }
+
+ }
+ }
+
+ public void testCameraCharacteristicsAndroidSensorCalibrationTransform2() throws Exception {
+ String[] ids = mCameraManager.getCameraIdList();
+ for (int i = 0; i < ids.length; i++) {
+ CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
+ assertNotNull(String.format("Can't get camera characteristics from: ID %s", ids[i]),
+ props);
+
+ Integer hwLevel = props.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
+ assertNotNull("No hardware level reported! android.info.supportedHardwareLevel",
+ hwLevel);
+ if (hwLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL)
+ {
+
+ assertNotNull("Invalid property: android.sensor.calibrationTransform2",
+ props.get(CameraCharacteristics.SENSOR_CALIBRATION_TRANSFORM2));
+
+ List<Key<?>> allKeys = props.getKeys();
+ assertNotNull(String.format("Can't get camera characteristics keys from: ID %s",
+ ids[i], props));
+ assertTrue("Key not in keys list: android.sensor.calibrationTransform2", allKeys.contains(
+ CameraCharacteristics.SENSOR_CALIBRATION_TRANSFORM2));
+
+ }
+
+ }
+ }
+
+ public void testCameraCharacteristicsAndroidSensorColorTransform1() throws Exception {
+ String[] ids = mCameraManager.getCameraIdList();
+ for (int i = 0; i < ids.length; i++) {
+ CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
+ assertNotNull(String.format("Can't get camera characteristics from: ID %s", ids[i]),
+ props);
+
+ Integer hwLevel = props.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
+ assertNotNull("No hardware level reported! android.info.supportedHardwareLevel",
+ hwLevel);
+ if (hwLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL)
+ {
+
+ assertNotNull("Invalid property: android.sensor.colorTransform1",
+ props.get(CameraCharacteristics.SENSOR_COLOR_TRANSFORM1));
+
+ List<Key<?>> allKeys = props.getKeys();
+ assertNotNull(String.format("Can't get camera characteristics keys from: ID %s",
+ ids[i], props));
+ assertTrue("Key not in keys list: android.sensor.colorTransform1", allKeys.contains(
+ CameraCharacteristics.SENSOR_COLOR_TRANSFORM1));
+
+ }
+
+ }
+ }
+
+ public void testCameraCharacteristicsAndroidSensorColorTransform2() throws Exception {
+ String[] ids = mCameraManager.getCameraIdList();
+ for (int i = 0; i < ids.length; i++) {
+ CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
+ assertNotNull(String.format("Can't get camera characteristics from: ID %s", ids[i]),
+ props);
+
+ Integer hwLevel = props.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
+ assertNotNull("No hardware level reported! android.info.supportedHardwareLevel",
+ hwLevel);
+ if (hwLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL)
+ {
+
+ assertNotNull("Invalid property: android.sensor.colorTransform2",
+ props.get(CameraCharacteristics.SENSOR_COLOR_TRANSFORM2));
+
+ List<Key<?>> allKeys = props.getKeys();
+ assertNotNull(String.format("Can't get camera characteristics keys from: ID %s",
+ ids[i], props));
+ assertTrue("Key not in keys list: android.sensor.colorTransform2", allKeys.contains(
+ CameraCharacteristics.SENSOR_COLOR_TRANSFORM2));
+
+ }
+
+ }
+ }
+
+ public void testCameraCharacteristicsAndroidSensorForwardMatrix1() throws Exception {
+ String[] ids = mCameraManager.getCameraIdList();
+ for (int i = 0; i < ids.length; i++) {
+ CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
+ assertNotNull(String.format("Can't get camera characteristics from: ID %s", ids[i]),
+ props);
+
+ Integer hwLevel = props.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
+ assertNotNull("No hardware level reported! android.info.supportedHardwareLevel",
+ hwLevel);
+ if (hwLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL)
+ {
+
+ assertNotNull("Invalid property: android.sensor.forwardMatrix1",
+ props.get(CameraCharacteristics.SENSOR_FORWARD_MATRIX1));
+
+ List<Key<?>> allKeys = props.getKeys();
+ assertNotNull(String.format("Can't get camera characteristics keys from: ID %s",
+ ids[i], props));
+ assertTrue("Key not in keys list: android.sensor.forwardMatrix1", allKeys.contains(
+ CameraCharacteristics.SENSOR_FORWARD_MATRIX1));
+
+ }
+
+ }
+ }
+
+ public void testCameraCharacteristicsAndroidSensorForwardMatrix2() throws Exception {
+ String[] ids = mCameraManager.getCameraIdList();
+ for (int i = 0; i < ids.length; i++) {
+ CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
+ assertNotNull(String.format("Can't get camera characteristics from: ID %s", ids[i]),
+ props);
+
+ Integer hwLevel = props.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
+ assertNotNull("No hardware level reported! android.info.supportedHardwareLevel",
+ hwLevel);
+ if (hwLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL)
+ {
+
+ assertNotNull("Invalid property: android.sensor.forwardMatrix2",
+ props.get(CameraCharacteristics.SENSOR_FORWARD_MATRIX2));
+
+ List<Key<?>> allKeys = props.getKeys();
+ assertNotNull(String.format("Can't get camera characteristics keys from: ID %s",
+ ids[i], props));
+ assertTrue("Key not in keys list: android.sensor.forwardMatrix2", allKeys.contains(
+ CameraCharacteristics.SENSOR_FORWARD_MATRIX2));
+
+ }
+
+ }
+ }
+
+ public void testCameraCharacteristicsAndroidSensorBlackLevelPattern() throws Exception {
+ String[] ids = mCameraManager.getCameraIdList();
+ for (int i = 0; i < ids.length; i++) {
+ CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
+ assertNotNull(String.format("Can't get camera characteristics from: ID %s", ids[i]),
+ props);
+
+ Integer hwLevel = props.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
+ assertNotNull("No hardware level reported! android.info.supportedHardwareLevel",
+ hwLevel);
+ if (hwLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL)
+ {
+
+ assertNotNull("Invalid property: android.sensor.blackLevelPattern",
+ props.get(CameraCharacteristics.SENSOR_BLACK_LEVEL_PATTERN));
+
+ List<Key<?>> allKeys = props.getKeys();
+ assertNotNull(String.format("Can't get camera characteristics keys from: ID %s",
+ ids[i], props));
+ assertTrue("Key not in keys list: android.sensor.blackLevelPattern", allKeys.contains(
+ CameraCharacteristics.SENSOR_BLACK_LEVEL_PATTERN));
}
@@ -755,6 +1174,33 @@
}
}
+ public void testCameraCharacteristicsAndroidSensorAvailableTestPatternModes() throws Exception {
+ String[] ids = mCameraManager.getCameraIdList();
+ for (int i = 0; i < ids.length; i++) {
+ CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
+ assertNotNull(String.format("Can't get camera characteristics from: ID %s", ids[i]),
+ props);
+
+ Integer hwLevel = props.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
+ assertNotNull("No hardware level reported! android.info.supportedHardwareLevel",
+ hwLevel);
+ if (hwLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL)
+ {
+
+ assertNotNull("Invalid property: android.sensor.availableTestPatternModes",
+ props.get(CameraCharacteristics.SENSOR_AVAILABLE_TEST_PATTERN_MODES));
+
+ List<Key<?>> allKeys = props.getKeys();
+ assertNotNull(String.format("Can't get camera characteristics keys from: ID %s",
+ ids[i], props));
+ assertTrue("Key not in keys list: android.sensor.availableTestPatternModes", allKeys.contains(
+ CameraCharacteristics.SENSOR_AVAILABLE_TEST_PATTERN_MODES));
+
+ }
+
+ }
+ }
+
public void testCameraCharacteristicsAndroidSensorInfoActiveArraySize() throws Exception {
String[] ids = mCameraManager.getCameraIdList();
for (int i = 0; i < ids.length; i++) {
@@ -801,6 +1247,29 @@
}
}
+ public void testCameraCharacteristicsAndroidSensorInfoColorFilterArrangement() throws Exception {
+ String[] ids = mCameraManager.getCameraIdList();
+ for (int i = 0; i < ids.length; i++) {
+ CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
+ assertNotNull(String.format("Can't get camera characteristics from: ID %s", ids[i]),
+ props);
+
+ {
+
+ assertNotNull("Invalid property: android.sensor.info.colorFilterArrangement",
+ props.get(CameraCharacteristics.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT));
+
+ List<Key<?>> allKeys = props.getKeys();
+ assertNotNull(String.format("Can't get camera characteristics keys from: ID %s",
+ ids[i], props));
+ assertTrue("Key not in keys list: android.sensor.info.colorFilterArrangement", allKeys.contains(
+ CameraCharacteristics.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT));
+
+ }
+
+ }
+ }
+
public void testCameraCharacteristicsAndroidSensorInfoExposureTimeRange() throws Exception {
String[] ids = mCameraManager.getCameraIdList();
for (int i = 0; i < ids.length; i++) {
@@ -870,6 +1339,52 @@
}
}
+ public void testCameraCharacteristicsAndroidSensorInfoPixelArraySize() throws Exception {
+ String[] ids = mCameraManager.getCameraIdList();
+ for (int i = 0; i < ids.length; i++) {
+ CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
+ assertNotNull(String.format("Can't get camera characteristics from: ID %s", ids[i]),
+ props);
+
+ {
+
+ assertNotNull("Invalid property: android.sensor.info.pixelArraySize",
+ props.get(CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE));
+
+ List<Key<?>> allKeys = props.getKeys();
+ assertNotNull(String.format("Can't get camera characteristics keys from: ID %s",
+ ids[i], props));
+ assertTrue("Key not in keys list: android.sensor.info.pixelArraySize", allKeys.contains(
+ CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE));
+
+ }
+
+ }
+ }
+
+ public void testCameraCharacteristicsAndroidSensorInfoWhiteLevel() throws Exception {
+ String[] ids = mCameraManager.getCameraIdList();
+ for (int i = 0; i < ids.length; i++) {
+ CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
+ assertNotNull(String.format("Can't get camera characteristics from: ID %s", ids[i]),
+ props);
+
+ {
+
+ assertNotNull("Invalid property: android.sensor.info.whiteLevel",
+ props.get(CameraCharacteristics.SENSOR_INFO_WHITE_LEVEL));
+
+ List<Key<?>> allKeys = props.getKeys();
+ assertNotNull(String.format("Can't get camera characteristics keys from: ID %s",
+ ids[i], props));
+ assertTrue("Key not in keys list: android.sensor.info.whiteLevel", allKeys.contains(
+ CameraCharacteristics.SENSOR_INFO_WHITE_LEVEL));
+
+ }
+
+ }
+ }
+
public void testCameraCharacteristicsAndroidStatisticsInfoAvailableFaceDetectModes() throws Exception {
String[] ids = mCameraManager.getCameraIdList();
for (int i = 0; i < ids.length; i++) {
@@ -916,6 +1431,29 @@
}
}
+ public void testCameraCharacteristicsAndroidStatisticsInfoAvailableHotPixelMapModes() throws Exception {
+ String[] ids = mCameraManager.getCameraIdList();
+ for (int i = 0; i < ids.length; i++) {
+ CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
+ assertNotNull(String.format("Can't get camera characteristics from: ID %s", ids[i]),
+ props);
+
+ {
+
+ assertNotNull("Invalid property: android.statistics.info.availableHotPixelMapModes",
+ props.get(CameraCharacteristics.STATISTICS_INFO_AVAILABLE_HOT_PIXEL_MAP_MODES));
+
+ List<Key<?>> allKeys = props.getKeys();
+ assertNotNull(String.format("Can't get camera characteristics keys from: ID %s",
+ ids[i], props));
+ assertTrue("Key not in keys list: android.statistics.info.availableHotPixelMapModes", allKeys.contains(
+ CameraCharacteristics.STATISTICS_INFO_AVAILABLE_HOT_PIXEL_MAP_MODES));
+
+ }
+
+ }
+ }
+
public void testCameraCharacteristicsAndroidTonemapMaxCurvePoints() throws Exception {
String[] ids = mCameraManager.getCameraIdList();
for (int i = 0; i < ids.length; i++) {
@@ -939,6 +1477,29 @@
}
}
+ public void testCameraCharacteristicsAndroidTonemapAvailableToneMapModes() throws Exception {
+ String[] ids = mCameraManager.getCameraIdList();
+ for (int i = 0; i < ids.length; i++) {
+ CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
+ assertNotNull(String.format("Can't get camera characteristics from: ID %s", ids[i]),
+ props);
+
+ {
+
+ assertNotNull("Invalid property: android.tonemap.availableToneMapModes",
+ props.get(CameraCharacteristics.TONEMAP_AVAILABLE_TONE_MAP_MODES));
+
+ List<Key<?>> allKeys = props.getKeys();
+ assertNotNull(String.format("Can't get camera characteristics keys from: ID %s",
+ ids[i], props));
+ assertTrue("Key not in keys list: android.tonemap.availableToneMapModes", allKeys.contains(
+ CameraCharacteristics.TONEMAP_AVAILABLE_TONE_MAP_MODES));
+
+ }
+
+ }
+ }
+
public void testCameraCharacteristicsAndroidInfoSupportedHardwareLevel() throws Exception {
String[] ids = mCameraManager.getCameraIdList();
for (int i = 0; i < ids.length; i++) {
@@ -961,5 +1522,28 @@
}
}
+
+ public void testCameraCharacteristicsAndroidSyncMaxLatency() throws Exception {
+ String[] ids = mCameraManager.getCameraIdList();
+ for (int i = 0; i < ids.length; i++) {
+ CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
+ assertNotNull(String.format("Can't get camera characteristics from: ID %s", ids[i]),
+ props);
+
+ {
+
+ assertNotNull("Invalid property: android.sync.maxLatency",
+ props.get(CameraCharacteristics.SYNC_MAX_LATENCY));
+
+ List<Key<?>> allKeys = props.getKeys();
+ assertNotNull(String.format("Can't get camera characteristics keys from: ID %s",
+ ids[i], props));
+ assertTrue("Key not in keys list: android.sync.maxLatency", allKeys.contains(
+ CameraCharacteristics.SYNC_MAX_LATENCY));
+
+ }
+
+ }
+ }
}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraDeviceTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraDeviceTest.java
index f68b10a..4b63d37 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraDeviceTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraDeviceTest.java
@@ -19,62 +19,43 @@
import static android.hardware.camera2.cts.CameraTestUtils.*;
import static com.android.ex.camera2.blocking.BlockingStateListener.*;
import static org.mockito.Mockito.*;
+import static android.hardware.camera2.CaptureRequest.*;
import android.content.Context;
import android.graphics.ImageFormat;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
-import android.hardware.camera2.CameraManager;
-import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.CaptureFailure;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
-import android.media.Image;
-import android.media.ImageReader;
-import android.os.Handler;
+import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
import android.os.SystemClock;
-import android.test.AndroidTestCase;
import android.util.Log;
+import android.util.Range;
import android.view.Surface;
import com.android.ex.camera2.blocking.BlockingStateListener;
+
import org.mockito.ArgumentMatcher;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
/**
* <p>Basic test for CameraDevice APIs.</p>
*/
-public class CameraDeviceTest extends AndroidTestCase {
+public class CameraDeviceTest extends Camera2AndroidTestCase {
private static final String TAG = "CameraDeviceTest";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
-
- private CameraManager mCameraManager;
- private BlockingStateListener mCameraListener;
- private CameraTestThread mLooperThread;
- private Handler mCallbackHandler;
- private int mLatestState = STATE_UNINITIALIZED;
-
- /**
- * The error triggered flag starts out as false, and it will flip to true if any errors
- * are ever caught; it won't be reset to false after that happens. This is due to the
- * fact that when multiple tests are run back to back (as they are here), it's hard
- * to associate the asynchronous error with the test that caused it (so we won't even try).
- */
- private boolean mErrorTriggered = false;
- private ImageReader mReader;
- private CameraTestThread mDummyThread;
- private Surface mSurface;
-
- private static final int CAMERA_CONFIGURE_TIMEOUT_MS = 2000;
- private static final int CAPTURE_WAIT_TIMEOUT_MS = 2000;
private static final int ERROR_LISTENER_WAIT_TIMEOUT_MS = 1000;
private static final int REPEATING_CAPTURE_EXPECTED_RESULT_COUNT = 5;
- // VGA size capture is required by CDD.
- private static final int DEFAULT_CAPTURE_WIDTH = 640;
- private static final int DEFAULT_CAPTURE_HEIGHT = 480;
private static final int MAX_NUM_IMAGES = 5;
+ private static final int MIN_FPS_REQUIRED_FOR_STREAMING = 20;
+
+ private BlockingStateListener mCameraMockListener;
+ private int mLatestState = STATE_UNINITIALIZED;
private static int[] mTemplates = new int[] {
CameraDevice.TEMPLATE_PREVIEW,
@@ -87,18 +68,11 @@
public void setContext(Context context) {
super.setContext(context);
/**
- * Workaround for mockito and JB-MR2 incompatibility
- *
- * Avoid java.lang.IllegalArgumentException: dexcache == null
- * https://code.google.com/p/dexmaker/issues/detail?id=2
- */
- System.setProperty("dexmaker.dexcache", mContext.getCacheDir().toString());
- /**
- * Create errorlistener in context scope, to catch asynchronous device error.
+ * Create error listener in context scope, to catch asynchronous device error.
* Use spy object here since we want to use the SimpleDeviceListener callback
* implementation (spy doesn't stub the functions unless we ask it to do so).
*/
- mCameraListener = spy(new BlockingStateListener());
+ mCameraMockListener = spy(new BlockingStateListener());
}
@Override
@@ -110,113 +84,367 @@
* fail the rest of the tests. This is especially needed when error
* callback is fired too late.
*/
- verify(mCameraListener, never())
+ verify(mCameraMockListener, never())
.onError(
any(CameraDevice.class),
anyInt());
- verify(mCameraListener, never())
+ verify(mCameraMockListener, never())
.onDisconnected(
any(CameraDevice.class));
- mCameraManager = (CameraManager)mContext.getSystemService(Context.CAMERA_SERVICE);
- assertNotNull("Can't connect to camera manager", mCameraManager);
- createDefaultSurface();
- mLooperThread = new CameraTestThread();
- mCallbackHandler = mLooperThread.start();
+ mCameraListener = mCameraMockListener;
+ createDefaultImageReader(DEFAULT_CAPTURE_SIZE, ImageFormat.YUV_420_888, MAX_NUM_IMAGES,
+ new ImageDropperListener());
}
@Override
protected void tearDown() throws Exception {
- mDummyThread.close();
- mReader.close();
super.tearDown();
}
- public void testCameraDeviceCreateCaptureBuilder() throws Exception {
- String[] ids = mCameraManager.getCameraIdList();
- for (int i = 0; i < ids.length; i++) {
- CameraDevice camera = null;
- try {
- camera = CameraTestUtils.openCamera(mCameraManager, ids[i], mCallbackHandler);
- assertNotNull(
- String.format("Failed to open camera device ID: %s", ids[i]), camera);
+ /**
+ * <p>
+ * Test camera capture request preview capture template.
+ * </p>
+ *
+ * <p>
+ * The request template returned by the camera device must include a
+ * necessary set of metadata keys, and their values must be set correctly.
+ * It mainly requires below settings:
+ * </p>
+ * <ul>
+ * <li>All 3A settings are auto.</li>
+ * <li>All sensor settings are not null.</li>
+ * <li>All ISP processing settings should be non-manual, and the camera
+ * device should make sure the stable frame rate is guaranteed for the given
+ * settings.</li>
+ * </ul>
+ */
+ public void testCameraDevicePreviewTemplate() throws Exception {
+ for (int i = 0; i < mCameraIds.length; i++) {
+ captureTemplateTestByCamera(mCameraIds[i], CameraDevice.TEMPLATE_PREVIEW);
+ }
+ // TODO: test the frame rate sustainability in preview use case test.
+ }
+
+ /**
+ * <p>
+ * Test camera capture request still capture template.
+ * </p>
+ *
+ * <p>
+ * The request template returned by the camera device must include a
+ * necessary set of metadata keys, and their values must be set correctly.
+ * It mainly requires below settings:
+ * </p>
+ * <ul>
+ * <li>All 3A settings are auto.</li>
+ * <li>All sensor settings are not null.</li>
+ * <li>All ISP processing settings should be non-manual, and the camera
+ * device should make sure the high quality takes priority to the stable
+ * frame rate for the given settings.</li>
+ * </ul>
+ */
+ public void testCameraDeviceStillTemplate() throws Exception {
+ for (int i = 0; i < mCameraIds.length; i++) {
+ captureTemplateTestByCamera(mCameraIds[i], CameraDevice.TEMPLATE_STILL_CAPTURE);
+ }
+ }
+
+ /**
+ * <p>
+ * Test camera capture video recording template.
+ * </p>
+ *
+ * <p>
+ * The request template returned by the camera device must include a
+ * necessary set of metadata keys, and their values must be set correctly.
+ * It has the similar requirement as preview, with one difference:
+ * </p>
+ * <ul>
+ * <li>Frame rate should be stable, for example, wide fps range like [7, 30]
+ * is a bad setting.</li>
+ */
+ public void testCameraDeviceRecordingTemplate() throws Exception {
+ for (int i = 0; i < mCameraIds.length; i++) {
+ captureTemplateTestByCamera(mCameraIds[i], CameraDevice.TEMPLATE_RECORD);
+ }
+
+ // TODO: test the frame rate sustainability in recording use case test.
+ }
+
+ /**
+ *<p>Test camera capture video snapshot template.</p>
+ *
+ * <p>The request template returned by the camera device must include a necessary set of
+ * metadata keys, and their values must be set correctly. It has the similar requirement
+ * as recording, with an additional requirement: the settings should maximize image quality
+ * without compromising stable frame rate.</p>
+ */
+ public void testCameraDeviceVideoSnapShotTemplate() throws Exception {
+ for (int i = 0; i < mCameraIds.length; i++) {
+ captureTemplateTestByCamera(mCameraIds[i], CameraDevice.TEMPLATE_VIDEO_SNAPSHOT);
+ }
+
+ // TODO: test the frame rate sustainability in video snapshot use case test.
+ }
+
+ /**
+ *<p>Test camera capture request zero shutter lag template.</p>
+ *
+ * <p>The request template returned by the camera device must include a necessary set of
+ * metadata keys, and their values must be set correctly. It has the similar requirement
+ * as preview, with an additional requirement: </p>
+ */
+ public void testCameraDeviceZSLTemplate() throws Exception {
+ for (int i = 0; i < mCameraIds.length; i++) {
+ captureTemplateTestByCamera(mCameraIds[i], CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG);
+ }
+ }
+
+ /**
+ * <p>
+ * Test camera capture request manual template.
+ * </p>
+ *
+ * <p>
+ * The request template returned by the camera device must include a
+ * necessary set of metadata keys, and their values must be set correctly. It
+ * mainly requires below settings:
+ * </p>
+ * <ul>
+ * <li>All 3A settings are manual.</li>
+ * <li>ISP processing parameters are set to preview quality.</li>
+ * <li>The manual capture parameters (exposure, sensitivity, and so on) are
+ * set to reasonable defaults.</li>
+ * </ul>
+ */
+ public void testCameraDeviceManualTemplate() throws Exception {
+ for (int i = 0; i < mCameraIds.length; i++) {
+ captureTemplateTestByCamera(mCameraIds[i], CameraDevice.TEMPLATE_MANUAL);
+ }
+ }
+
+ public void testCameraDeviceCreateCaptureBuilder() throws Exception {
+ for (int i = 0; i < mCameraIds.length; i++) {
+ try {
+ openDevice(mCameraIds[i], mCameraMockListener);
/**
* Test: that each template type is supported, and that its required fields are
* present.
*/
for (int j = 0; j < mTemplates.length; j++) {
- CaptureRequest.Builder capReq = camera.createCaptureRequest(mTemplates[j]);
+ CaptureRequest.Builder capReq = mCamera.createCaptureRequest(mTemplates[j]);
assertNotNull("Failed to create capture request", capReq);
assertNotNull("Missing field: SENSOR_EXPOSURE_TIME",
capReq.get(CaptureRequest.SENSOR_EXPOSURE_TIME));
assertNotNull("Missing field: SENSOR_SENSITIVITY",
capReq.get(CaptureRequest.SENSOR_SENSITIVITY));
-
- // TODO: Add more tests to check more fields.
}
}
finally {
- if (camera != null) {
- camera.close();
- }
+ closeDevice(mCameraIds[i], mCameraMockListener);
}
}
}
public void testCameraDeviceSetErrorListener() throws Exception {
- String[] ids = mCameraManager.getCameraIdList();
- for (int i = 0; i < ids.length; i++) {
- CameraDevice camera = null;
+ for (int i = 0; i < mCameraIds.length; i++) {
try {
- camera = CameraTestUtils.openCamera(mCameraManager, ids[i],
- mCameraListener, mCallbackHandler);
- assertNotNull(
- String.format("Failed to open camera device %s", ids[i]), camera);
-
+ openDevice(mCameraIds[i], mCameraMockListener);
/**
* Test: that the error listener can be set without problems.
* Also, wait some time to check if device doesn't run into error.
*/
SystemClock.sleep(ERROR_LISTENER_WAIT_TIMEOUT_MS);
- verify(mCameraListener, never())
+ verify(mCameraMockListener, never())
.onError(
any(CameraDevice.class),
anyInt());
}
finally {
- if (camera != null) {
- camera.close();
- }
+ closeDevice(mCameraIds[i], mCameraMockListener);
}
}
}
public void testCameraDeviceCapture() throws Exception {
- runCaptureTest(false, false);
+ runCaptureTest(/*burst*/false, /*repeating*/false, /*flush*/false);
}
public void testCameraDeviceCaptureBurst() throws Exception {
- runCaptureTest(true, false);
+ runCaptureTest(/*burst*/true, /*repeating*/false, /*flush*/false);
}
public void testCameraDeviceRepeatingRequest() throws Exception {
- runCaptureTest(false, true);
+ runCaptureTest(/*burst*/false, /*repeating*/true, /*flush*/false);
}
public void testCameraDeviceRepeatingBurst() throws Exception {
- runCaptureTest(true, true);
+ runCaptureTest(/*burst*/true, /*repeating*/true, /*flush*/false);
}
- private class IsCameraMetadataNotEmpty<T extends CameraMetadata>
- extends ArgumentMatcher<T> {
+ /**
+ * Test {@link CameraDevice#flush} API.
+ *
+ * <p>
+ * Flush is the fastest way to idle the camera device for reconfiguration
+ * with {@link #configureOutputs}, at the cost of discarding in-progress
+ * work. Once the flush is complete, the idle callback will be called.
+ * </p>
+ */
+ public void testCameraDeviceFlush() throws Exception {
+ runCaptureTest(/*burst*/false, /*repeating*/true, /*flush*/true);
+ runCaptureTest(/*burst*/true, /*repeating*/true, /*flush*/true);
+ /**
+ * TODO: this is only basic test of flush. we probably should also test below cases:
+ *
+ * 1. Performance. Make sure flush is faster than stopRepeating, we can test each one
+ * a couple of times, then compare the average. Also, for flush() alone, we should make
+ * sure it doesn't take too long time (e.g. <100ms for full devices, <500ms for limited
+ * devices), after the flush, we should be able to get all results back very quickly.
+ * This can be done in performance test.
+ *
+ * 2. Make sure all in-flight request comes back after flush, e.g. submit a couple of
+ * long exposure single captures, then flush, then check if we can get the pending
+ * request back quickly.
+ *
+ * 3. Also need check onCaptureSequenceCompleted for repeating burst after flush().
+ */
+ }
+
+ /**
+ * Test invalid capture (e.g. null or empty capture request).
+ */
+ public void testInvalidCapture() throws Exception {
+ for (int i = 0; i < mCameraIds.length; i++) {
+ try {
+ openDevice(mCameraIds[i], mCameraMockListener);
+ waitForState(STATE_UNCONFIGURED, CAMERA_OPEN_TIMEOUT_MS);
+
+ prepareCapture();
+
+ invalidRequestCaptureTestByCamera();
+ }
+ finally {
+ closeDevice(mCameraIds[i], mCameraMockListener);
+ }
+ }
+ }
+
+ private void invalidRequestCaptureTestByCamera() throws Exception {
+ List<CaptureRequest> emptyRequests = new ArrayList<CaptureRequest>();
+ CaptureRequest.Builder requestBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ CaptureRequest unConfiguredRequest = requestBuilder.build();
+ List<CaptureRequest> unConfiguredRequests = new ArrayList<CaptureRequest>();
+ unConfiguredRequests.add(unConfiguredRequest);
+
+ try {
+ // Test: CameraDevice capture should throw IAE for null request.
+ mCamera.capture(/*request*/null, /*listener*/null, mHandler);
+ mCollector.addMessage(
+ "CameraDevice capture should throw IllegalArgumentException for null request");
+ } catch (IllegalArgumentException e) {
+ // Pass.
+ }
+
+ try {
+ // Test: CameraDevice capture should throw IAE for request
+ // without surface configured.
+ mCamera.capture(unConfiguredRequest, /*listener*/null, mHandler);
+ mCollector.addMessage("CameraDevice capture should throw " +
+ "IllegalArgumentException for request without surface configured");
+ } catch (IllegalArgumentException e) {
+ // Pass.
+ }
+
+ try {
+ // Test: CameraDevice setRepeatingRequest should throw IAE for null request.
+ mCamera.setRepeatingRequest(/*request*/null, /*listener*/null, mHandler);
+ mCollector.addMessage("CameraDevice setRepeatingRequest should throw" +
+ "IllegalArgumentException for null request");
+ } catch (IllegalArgumentException e) {
+ // Pass.
+ }
+
+ try {
+ // Test: CameraDevice setRepeatingRequest should throw IAE for for request
+ // without surface configured.
+ mCamera.setRepeatingRequest(unConfiguredRequest, /*listener*/null, mHandler);
+ mCollector.addMessage("Capture zero burst should throw IllegalArgumentException" +
+ "for request without surface configured");
+ } catch (IllegalArgumentException e) {
+ // Pass.
+ }
+
+ try {
+ // Test: CameraDevice captureBurst should throw IAE for null request list.
+ mCamera.captureBurst(/*requests*/null, /*listener*/null, mHandler);
+ mCollector.addMessage("CameraDevice captureBurst should throw" +
+ "IllegalArgumentException for null request list");
+ } catch (IllegalArgumentException e) {
+ // Pass.
+ }
+
+ try {
+ // Test: CameraDevice captureBurst should throw IAE for empty request list.
+ mCamera.captureBurst(emptyRequests, /*listener*/null, mHandler);
+ mCollector.addMessage("CameraDevice captureBurst should throw" +
+ " IllegalArgumentException for empty request list");
+ } catch (IllegalArgumentException e) {
+ // Pass.
+ }
+
+ try {
+ // Test: CameraDevice captureBurst should throw IAE for request
+ // without surface configured.
+ mCamera.captureBurst(unConfiguredRequests, /*listener*/null, mHandler);
+ fail("CameraDevice captureBurst should throw IllegalArgumentException" +
+ "for null request list");
+ } catch (IllegalArgumentException e) {
+ // Pass.
+ }
+
+ try {
+ // Test: CameraDevice setRepeatingBurst should throw IAE for null request list.
+ mCamera.setRepeatingBurst(/*requests*/null, /*listener*/null, mHandler);
+ mCollector.addMessage("CameraDevice setRepeatingBurst should throw" +
+ "IllegalArgumentException for null request list");
+ } catch (IllegalArgumentException e) {
+ // Pass.
+ }
+
+ try {
+ // Test: CameraDevice setRepeatingBurst should throw IAE for empty request list.
+ mCamera.setRepeatingBurst(emptyRequests, /*listener*/null, mHandler);
+ mCollector.addMessage("CameraDevice setRepeatingBurst should throw" +
+ "IllegalArgumentException for empty request list");
+ } catch (IllegalArgumentException e) {
+ // Pass.
+ }
+
+ try {
+ // Test: CameraDevice setRepeatingBurst should throw IAE for request
+ // without surface configured.
+ mCamera.setRepeatingBurst(unConfiguredRequests, /*listener*/null, mHandler);
+ mCollector.addMessage("CameraDevice setRepeatingBurst should throw" +
+ "IllegalArgumentException for request without surface configured");
+ } catch (IllegalArgumentException e) {
+ // Pass.
+ }
+ }
+
+ private class IsCaptureResultNotEmpty
+ extends ArgumentMatcher<TotalCaptureResult> {
@Override
public boolean matches(Object obj) {
/**
* Do the simple verification here. Only verify the timestamp for now.
* TODO: verify more required capture result metadata fields.
*/
- CameraMetadata result = (CameraMetadata) obj;
+ TotalCaptureResult result = (TotalCaptureResult) obj;
Long timeStamp = result.get(CaptureResult.SENSOR_TIMESTAMP);
if (timeStamp != null && timeStamp.longValue() > 0L) {
return true;
@@ -225,31 +453,33 @@
}
}
- private void runCaptureTest(boolean burst, boolean repeating) throws Exception {
- String[] ids = mCameraManager.getCameraIdList();
- for (int i = 0; i < ids.length; i++) {
- CameraDevice camera = null;
+ /**
+ * Run capture test with different test configurations.
+ *
+ * @param burst If the test uses {@link CameraDevice#captureBurst} or
+ * {@link CameraDevice#setRepeatingBurst} to capture the burst.
+ * @param repeating If the test uses {@link CameraDevice#setRepeatingBurst} or
+ * {@link CameraDevice#setRepeatingRequest} for repeating capture.
+ * @param flush If the test uses {@link CameraDevice#flush} to stop the repeating capture.
+ * It has no effect if repeating is false.
+ */
+ private void runCaptureTest(boolean burst, boolean repeating, boolean flush) throws Exception {
+ for (int i = 0; i < mCameraIds.length; i++) {
try {
- camera = CameraTestUtils.openCamera(mCameraManager, ids[i],
- mCameraListener, mCallbackHandler);
- assertNotNull(
- String.format("Failed to open camera device %s", ids[i]), camera);
+ openDevice(mCameraIds[i], mCameraMockListener);
waitForState(STATE_UNCONFIGURED, CAMERA_OPEN_TIMEOUT_MS);
- prepareCapture(camera);
+ prepareCapture();
if (!burst) {
// Test: that a single capture of each template type succeeds.
for (int j = 0; j < mTemplates.length; j++) {
- captureSingleShot(camera, ids[i], mTemplates[j], repeating);
+ captureSingleShot(mCameraIds[i], mTemplates[j], repeating, flush);
}
}
else {
- // Test: burst of zero shots
- captureBurstShot(camera, ids[i], mTemplates, 0, repeating);
-
// Test: burst of one shot
- captureBurstShot(camera, ids[i], mTemplates, 1, repeating);
+ captureBurstShot(mCameraIds[i], mTemplates, 1, repeating, flush);
int[] templates = new int[] {
CameraDevice.TEMPLATE_STILL_CAPTURE,
@@ -260,36 +490,34 @@
};
// Test: burst of 5 shots of the same template type
- captureBurstShot(camera, ids[i], templates, templates.length, repeating);
+ captureBurstShot(mCameraIds[i], templates, templates.length, repeating, flush);
// Test: burst of 5 shots of different template types
- captureBurstShot(camera, ids[i], mTemplates, mTemplates.length, repeating);
+ captureBurstShot(
+ mCameraIds[i], mTemplates, mTemplates.length, repeating, flush);
}
- verify(mCameraListener, never())
+ verify(mCameraMockListener, never())
.onError(
any(CameraDevice.class),
anyInt());
}
finally {
- if (camera != null) {
- camera.close();
- }
+ closeDevice(mCameraIds[i], mCameraMockListener);
}
}
}
private void captureSingleShot(
- CameraDevice camera,
String id,
int template,
- boolean repeating) throws Exception {
+ boolean repeating, boolean flush) throws Exception {
assertEquals("Bad initial state for preparing to capture",
mLatestState, STATE_IDLE);
- CaptureRequest.Builder requestBuilder = camera.createCaptureRequest(template);
+ CaptureRequest.Builder requestBuilder = mCamera.createCaptureRequest(template);
assertNotNull("Failed to create capture request", requestBuilder);
- requestBuilder.addTarget(mSurface);
+ requestBuilder.addTarget(mReaderSurface);
CameraDevice.CaptureListener mockCaptureListener =
mock(CameraDevice.CaptureListener.class);
@@ -297,30 +525,29 @@
Log.v(TAG, String.format("Capturing shot for device %s, template %d",
id, template));
}
- if (!repeating) {
- camera.capture(requestBuilder.build(), mockCaptureListener, mCallbackHandler);
- }
- else {
- camera.setRepeatingRequest(requestBuilder.build(), mockCaptureListener,
- mCallbackHandler);
- }
+
+ startCapture(requestBuilder.build(), repeating, mockCaptureListener, mHandler);
waitForState(STATE_ACTIVE, CAMERA_CONFIGURE_TIMEOUT_MS);
int expectedCaptureResultCount = repeating ? REPEATING_CAPTURE_EXPECTED_RESULT_COUNT : 1;
- verifyCaptureResults(camera, mockCaptureListener, expectedCaptureResultCount);
+ verifyCaptureResults(mockCaptureListener, expectedCaptureResultCount);
if (repeating) {
- camera.stopRepeating();
+ if (flush) {
+ mCamera.flush();
+ } else {
+ mCamera.stopRepeating();
+ }
}
waitForState(STATE_IDLE, CAMERA_CONFIGURE_TIMEOUT_MS);
}
private void captureBurstShot(
- CameraDevice camera,
String id,
int[] templates,
int len,
- boolean repeating) throws Exception {
+ boolean repeating,
+ boolean flush) throws Exception {
assertEquals("Bad initial state for preparing to capture",
mLatestState, STATE_IDLE);
@@ -328,9 +555,9 @@
assertTrue("Invalid args to capture function", len <= templates.length);
List<CaptureRequest> requests = new ArrayList<CaptureRequest>();
for (int i = 0; i < len; i++) {
- CaptureRequest.Builder requestBuilder = camera.createCaptureRequest(templates[i]);
+ CaptureRequest.Builder requestBuilder = mCamera.createCaptureRequest(templates[i]);
assertNotNull("Failed to create capture request", requestBuilder);
- requestBuilder.addTarget(mSurface);
+ requestBuilder.addTarget(mReaderSurface);
requests.add(requestBuilder.build());
}
CameraDevice.CaptureListener mockCaptureListener =
@@ -341,10 +568,10 @@
}
if (!repeating) {
- camera.captureBurst(requests, mockCaptureListener, mCallbackHandler);
+ mCamera.captureBurst(requests, mockCaptureListener, mHandler);
}
else {
- camera.setRepeatingBurst(requests, mockCaptureListener, mCallbackHandler);
+ mCamera.setRepeatingBurst(requests, mockCaptureListener, mHandler);
}
waitForState(STATE_ACTIVE, CAMERA_CONFIGURE_TIMEOUT_MS);
@@ -353,88 +580,319 @@
expectedResultCount *= REPEATING_CAPTURE_EXPECTED_RESULT_COUNT;
}
- verifyCaptureResults(camera, mockCaptureListener, expectedResultCount);
+ verifyCaptureResults(mockCaptureListener, expectedResultCount);
if (repeating) {
- camera.stopRepeating();
+ if (flush) {
+ mCamera.flush();
+ } else {
+ mCamera.stopRepeating();
+ }
}
waitForState(STATE_IDLE, CAMERA_CONFIGURE_TIMEOUT_MS);
}
// Precondition: Device must be in known IDLE/UNCONFIGURED state (has been waited for)
- private void prepareCapture(CameraDevice camera) throws Exception {
+ private void prepareCapture() throws Exception {
assertTrue("Bad initial state for preparing to capture",
mLatestState == STATE_IDLE || mLatestState == STATE_UNCONFIGURED);
List<Surface> outputSurfaces = new ArrayList<Surface>(1);
- outputSurfaces.add(mSurface);
- camera.configureOutputs(outputSurfaces);
+ outputSurfaces.add(mReaderSurface);
+ mCamera.configureOutputs(outputSurfaces);
waitForState(STATE_BUSY, CAMERA_BUSY_TIMEOUT_MS);
waitForState(STATE_IDLE, CAMERA_IDLE_TIMEOUT_MS);
- }
-
- /**
- * Dummy listener that release the image immediately once it is available.
- * It can be used for the case where we don't care the image data at all.
- * TODO: move it to the CameraTestUtil class.
- */
- private class ImageDropperListener implements ImageReader.OnImageAvailableListener {
- @Override
- public void onImageAvailable(ImageReader reader) {
- Image image = null;
- try {
- image = reader.acquireNextImage();
- } finally {
- if (image != null) {
- image.close();
- }
- }
- }
- }
-
- private void createDefaultSurface() throws Exception {
- mReader =
- ImageReader.newInstance(DEFAULT_CAPTURE_WIDTH,
- DEFAULT_CAPTURE_HEIGHT,
- ImageFormat.YUV_420_888,
- MAX_NUM_IMAGES);
- mSurface = mReader.getSurface();
- // Create dummy image listener since we don't care the image data in this test.
- ImageReader.OnImageAvailableListener listener = new ImageDropperListener();
- mDummyThread = new CameraTestThread();
- mReader.setOnImageAvailableListener(listener, mDummyThread.start());
- }
+}
private void waitForState(int state, long timeout) {
- mCameraListener.waitForState(state, timeout);
+ mCameraMockListener.waitForState(state, timeout);
mLatestState = state;
}
private void verifyCaptureResults(
- CameraDevice camera,
CameraDevice.CaptureListener mockListener,
int expectResultCount) {
// Should receive expected number of capture results.
verify(mockListener,
timeout(CAPTURE_WAIT_TIMEOUT_MS).atLeast(expectResultCount))
.onCaptureCompleted(
- eq(camera),
+ eq(mCamera),
isA(CaptureRequest.class),
- argThat(new IsCameraMetadataNotEmpty<CaptureResult>()));
+ argThat(new IsCaptureResultNotEmpty()));
// Should not receive any capture failed callbacks.
verify(mockListener, never())
.onCaptureFailed(
- eq(camera),
- argThat(new IsCameraMetadataNotEmpty<CaptureRequest>()),
+ eq(mCamera),
+ isA(CaptureRequest.class),
isA(CaptureFailure.class));
// Should receive expected number of capture shutter calls
verify(mockListener,
atLeast(expectResultCount))
.onCaptureStarted(
- eq(camera),
+ eq(mCamera),
isA(CaptureRequest.class),
anyLong());
}
+ private void checkFpsRange(CaptureRequest.Builder request, int template,
+ CameraCharacteristics props) {
+ CaptureRequest.Key<Range<Integer>> fpsRangeKey = CONTROL_AE_TARGET_FPS_RANGE;
+ Range<Integer> fpsRange;
+ if ((fpsRange = mCollector.expectKeyValueNotNull(request, fpsRangeKey)) == null) {
+ return;
+ }
+
+ int minFps = fpsRange.getLower();
+ int maxFps = fpsRange.getUpper();
+ Range<Integer>[] availableFpsRange = props
+ .get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);
+ boolean foundRange = false;
+ for (int i = 0; i < availableFpsRange.length; i += 1) {
+ if (minFps == availableFpsRange[i].getLower()
+ && maxFps == availableFpsRange[i].getUpper()) {
+ foundRange = true;
+ break;
+ }
+ }
+ if (!foundRange) {
+ mCollector.addMessage(String.format("Unable to find the fps range (%d, %d)",
+ minFps, maxFps));
+ return;
+ }
+
+ if (template != CameraDevice.TEMPLATE_MANUAL &&
+ template != CameraDevice.TEMPLATE_STILL_CAPTURE) {
+ if (maxFps < MIN_FPS_REQUIRED_FOR_STREAMING) {
+ mCollector.addMessage("Max fps should be at least "
+ + MIN_FPS_REQUIRED_FOR_STREAMING);
+ return;
+ }
+
+ // Need give fixed frame rate for video recording template.
+ if (template == CameraDevice.TEMPLATE_RECORD) {
+ if (maxFps != minFps) {
+ mCollector.addMessage("Video recording frame rate should be fixed");
+ }
+ }
+ }
+ }
+
+ private void checkAfMode(CaptureRequest.Builder request, int template,
+ CameraCharacteristics props) {
+ boolean hasFocuser =
+ props.get(CameraCharacteristics.LENS_INFO_MINIMUM_FOCUS_DISTANCE) > 0f;
+
+ if (!hasFocuser) {
+ return;
+ }
+
+ int targetAfMode = CaptureRequest.CONTROL_AF_MODE_AUTO;
+ int[] availableAfMode = props.get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES);
+ if (template == CameraDevice.TEMPLATE_PREVIEW ||
+ template == CameraDevice.TEMPLATE_STILL_CAPTURE ||
+ template == CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG) {
+ // Default to CONTINUOUS_PICTURE if it is available, otherwise AUTO.
+ for (int i = 0; i < availableAfMode.length; i++) {
+ if (availableAfMode[i] == CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE) {
+ targetAfMode = CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE;
+ break;
+ }
+ }
+ } else if (template == CameraDevice.TEMPLATE_RECORD ||
+ template == CameraDevice.TEMPLATE_VIDEO_SNAPSHOT) {
+ // Default to CONTINUOUS_VIDEO if it is available, otherwise AUTO.
+ for (int i = 0; i < availableAfMode.length; i++) {
+ if (availableAfMode[i] == CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_VIDEO) {
+ targetAfMode = CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_VIDEO;
+ break;
+ }
+ }
+ } else if (template == CameraDevice.TEMPLATE_MANUAL) {
+ targetAfMode = CaptureRequest.CONTROL_AF_MODE_OFF;
+ }
+
+ mCollector.expectKeyValueEquals(request, CONTROL_AF_MODE, targetAfMode);
+ mCollector.expectKeyValueNotNull(request, LENS_FOCUS_DISTANCE);
+ }
+
+ /**
+ * <p>Check if the request settings are suitable for a given request template.</p>
+ *
+ * <p>This function doesn't fail the test immediately, it updates the
+ * test pass/fail status and appends the failure message to the error collector each key.</p>
+ *
+ * @param request The request to be checked.
+ * @param template The capture template targeted by this request.
+ * @param props The CameraCharacteristics this request is checked against with.
+ */
+ private void checkRequestForTemplate(CaptureRequest.Builder request, int template,
+ CameraCharacteristics props) {
+ // 3A settings--control.mode.
+ if (template != CameraDevice.TEMPLATE_MANUAL) {
+ mCollector.expectKeyValueEquals(request, CONTROL_MODE,
+ CaptureRequest.CONTROL_MODE_AUTO);
+ }
+
+ // 3A settings--AE/AWB/AF.
+ int maxRegionsAe = props.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AE);
+ int maxRegionsAwb = props.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AWB);
+ int maxRegionsAf = props.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AF);
+
+ checkAfMode(request, template, props);
+ checkFpsRange(request, template, props);
+
+ if (template == CameraDevice.TEMPLATE_MANUAL) {
+ mCollector.expectKeyValueEquals(request, CONTROL_MODE, CaptureRequest.CONTROL_MODE_OFF);
+ mCollector.expectKeyValueEquals(request, CONTROL_AE_MODE,
+ CaptureRequest.CONTROL_AE_MODE_OFF);
+ mCollector.expectKeyValueEquals(request, CONTROL_AWB_MODE,
+ CaptureRequest.CONTROL_AWB_MODE_OFF);
+ } else {
+ mCollector.expectKeyValueEquals(request, CONTROL_AE_MODE,
+ CaptureRequest.CONTROL_AE_MODE_ON);
+ mCollector.expectKeyValueNotEquals(request, CONTROL_AE_ANTIBANDING_MODE,
+ CaptureRequest.CONTROL_AE_ANTIBANDING_MODE_OFF);
+ mCollector.expectKeyValueEquals(request, CONTROL_AE_EXPOSURE_COMPENSATION, 0);
+ mCollector.expectKeyValueEquals(request, CONTROL_AE_LOCK, false);
+ mCollector.expectKeyValueEquals(request, CONTROL_AE_PRECAPTURE_TRIGGER,
+ CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_IDLE);
+
+ mCollector.expectKeyValueEquals(request, CONTROL_AF_TRIGGER,
+ CaptureRequest.CONTROL_AF_TRIGGER_IDLE);
+
+ mCollector.expectKeyValueEquals(request, CONTROL_AWB_MODE,
+ CaptureRequest.CONTROL_AWB_MODE_AUTO);
+ mCollector.expectKeyValueEquals(request, CONTROL_AWB_LOCK, false);
+
+ // Check 3A regions.
+ if (VERBOSE) {
+ Log.v(TAG, String.format("maxRegions is: {AE: %s, AWB: %s, AF: %s}",
+ maxRegionsAe, maxRegionsAwb, maxRegionsAf));
+ }
+ if (maxRegionsAe > 0) {
+ mCollector.expectKeyValueNotNull(request, CONTROL_AE_REGIONS);
+ }
+ if (maxRegionsAwb > 0) {
+ mCollector.expectKeyValueNotNull(request, CONTROL_AWB_REGIONS);
+ }
+ if (maxRegionsAf > 0) {
+ mCollector.expectKeyValueNotNull(request, CONTROL_AF_REGIONS);
+ }
+ }
+
+ // Sensor settings.
+ float[] availableApertures =
+ props.get(CameraCharacteristics.LENS_INFO_AVAILABLE_APERTURES);
+ if (availableApertures.length > 1) {
+ mCollector.expectKeyValueNotNull(request, LENS_APERTURE);
+ }
+
+ float[] availableFilters =
+ props.get(CameraCharacteristics.LENS_INFO_AVAILABLE_FILTER_DENSITIES);
+ if (availableFilters.length > 1) {
+ mCollector.expectKeyValueNotNull(request, LENS_FILTER_DENSITY);
+ }
+
+ float[] availableFocalLen =
+ props.get(CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS);
+ if (availableFocalLen.length > 1) {
+ mCollector.expectKeyValueNotNull(request, LENS_FOCAL_LENGTH);
+ }
+
+ int[] availableOIS =
+ props.get(CameraCharacteristics.LENS_INFO_AVAILABLE_OPTICAL_STABILIZATION);
+ if (availableOIS.length > 1) {
+ mCollector.expectKeyValueNotNull(request, LENS_OPTICAL_STABILIZATION_MODE);
+ }
+
+ mCollector.expectKeyValueEquals(request, BLACK_LEVEL_LOCK, false);
+ mCollector.expectKeyValueNotNull(request, SENSOR_FRAME_DURATION);
+ mCollector.expectKeyValueNotNull(request, SENSOR_EXPOSURE_TIME);
+ mCollector.expectKeyValueNotNull(request, SENSOR_SENSITIVITY);
+
+ // ISP-processing settings.
+ mCollector.expectKeyValueEquals(
+ request, STATISTICS_FACE_DETECT_MODE,
+ CaptureRequest.STATISTICS_FACE_DETECT_MODE_OFF);
+ mCollector.expectKeyValueEquals(request, FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);
+ mCollector.expectKeyValueEquals(
+ request, STATISTICS_LENS_SHADING_MAP_MODE,
+ CaptureRequest.STATISTICS_LENS_SHADING_MAP_MODE_OFF);
+
+ if (template == CameraDevice.TEMPLATE_STILL_CAPTURE) {
+ // Not enforce high quality here, as some devices may not effectively have high quality
+ // mode.
+ mCollector.expectKeyValueNotEquals(
+ request, COLOR_CORRECTION_MODE,
+ CaptureRequest.COLOR_CORRECTION_MODE_TRANSFORM_MATRIX);
+
+ List<Integer> availableEdgeModes =
+ Arrays.asList(toObject(mStaticInfo.getAvailableEdgeModesChecked()));
+ if (availableEdgeModes.contains(CaptureRequest.EDGE_MODE_HIGH_QUALITY)) {
+ mCollector.expectKeyValueEquals(request, EDGE_MODE,
+ CaptureRequest.EDGE_MODE_HIGH_QUALITY);
+ } else if (availableEdgeModes.contains(CaptureRequest.EDGE_MODE_FAST)) {
+ mCollector.expectKeyValueEquals(request, EDGE_MODE, CaptureRequest.EDGE_MODE_FAST);
+ } else {
+ mCollector.expectKeyValueEquals(request, EDGE_MODE, CaptureRequest.EDGE_MODE_OFF);
+ }
+
+ List<Integer> availableNoiseReductionModes =
+ Arrays.asList(toObject(mStaticInfo.getAvailableNoiseReductionModesChecked()));
+ if (availableNoiseReductionModes
+ .contains(CaptureRequest.NOISE_REDUCTION_MODE_HIGH_QUALITY)) {
+ mCollector.expectKeyValueEquals(
+ request, NOISE_REDUCTION_MODE,
+ CaptureRequest.NOISE_REDUCTION_MODE_HIGH_QUALITY);
+ } else if (availableNoiseReductionModes
+ .contains(CaptureRequest.NOISE_REDUCTION_MODE_FAST)) {
+ mCollector.expectKeyValueEquals(
+ request, NOISE_REDUCTION_MODE, CaptureRequest.NOISE_REDUCTION_MODE_FAST);
+ } else {
+ mCollector.expectKeyValueEquals(
+ request, NOISE_REDUCTION_MODE, CaptureRequest.NOISE_REDUCTION_MODE_OFF);
+ }
+
+ List<Integer> availableToneMapModes =
+ Arrays.asList(toObject(mStaticInfo.getAvailableToneMapModesChecked()));
+ if (availableToneMapModes.contains(CaptureRequest.TONEMAP_MODE_HIGH_QUALITY)) {
+ mCollector.expectKeyValueEquals(request, TONEMAP_MODE,
+ CaptureRequest.TONEMAP_MODE_HIGH_QUALITY);
+ } else {
+ mCollector.expectKeyValueEquals(request, TONEMAP_MODE,
+ CaptureRequest.TONEMAP_MODE_FAST);
+ }
+ } else {
+ mCollector.expectKeyValueNotNull(request, EDGE_MODE);
+ mCollector.expectKeyValueNotNull(request, NOISE_REDUCTION_MODE);
+ mCollector.expectKeyValueNotEquals(request, TONEMAP_MODE,
+ CaptureRequest.TONEMAP_MODE_CONTRAST_CURVE);
+ }
+
+ mCollector.expectKeyValueEquals(request, CONTROL_CAPTURE_INTENT, template);
+
+ // TODO: use the list of keys from CameraCharacteristics to avoid expecting
+ // keys which are not available by this CameraDevice.
+ }
+
+ private void captureTemplateTestByCamera(String cameraId, int template) throws Exception {
+ try {
+ openDevice(cameraId, mCameraMockListener);
+
+ assertTrue("Camera template " + template + " is out of range!",
+ template >= CameraDevice.TEMPLATE_PREVIEW
+ && template <= CameraDevice.TEMPLATE_MANUAL);
+
+ mCollector.setCameraId(cameraId);
+ CaptureRequest.Builder request = mCamera.createCaptureRequest(template);
+ assertNotNull("Failed to create capture request for template " + template, request);
+
+ CameraCharacteristics props = mStaticInfo.getCharacteristics();
+ checkRequestForTemplate(request, template, props);
+ }
+ finally {
+ closeDevice(cameraId, mCameraMockListener);
+ }
+ }
}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraManagerTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraManagerTest.java
index 7091cac..9719278 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraManagerTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraManagerTest.java
@@ -16,17 +16,32 @@
package android.hardware.camera2.cts;
+import static org.mockito.Mockito.*;
+import static org.mockito.AdditionalMatchers.not;
+import static org.mockito.AdditionalMatchers.and;
+
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraDevice.StateListener;
import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.cts.CameraTestUtils.MockStateListener;
+import android.hardware.camera2.cts.helpers.CameraErrorCollector;
import android.os.Handler;
+import android.os.HandlerThread;
import android.test.AndroidTestCase;
import android.util.Log;
+import com.android.ex.camera2.blocking.BlockingStateListener;
+
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
/**
* <p>Basic test for CameraManager class.</p>
@@ -34,14 +49,15 @@
public class CameraManagerTest extends AndroidTestCase {
private static final String TAG = "CameraManagerTest";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
-
private static final int NUM_CAMERA_REOPENS = 10;
private PackageManager mPackageManager;
private CameraManager mCameraManager;
private NoopCameraListener mListener;
- private CameraTestThread mLooperThread;
+ private HandlerThread mHandlerThread;
private Handler mHandler;
+ private BlockingStateListener mCameraListener;
+ private CameraErrorCollector mCollector;
@Override
public void setContext(Context context) {
@@ -57,16 +73,45 @@
protected void setUp() throws Exception {
super.setUp();
- mLooperThread = new CameraTestThread();
- mHandler = mLooperThread.start();
+ mCameraListener = spy(new BlockingStateListener());
+
+ mHandlerThread = new HandlerThread(TAG);
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+ mCollector = new CameraErrorCollector();
}
@Override
protected void tearDown() throws Exception {
- mLooperThread.close();
+ mHandlerThread.quitSafely();
mHandler = null;
- super.tearDown();
+ try {
+ mCollector.verify();
+ } catch (Throwable e) {
+ // When new Exception(e) is used, exception info will be printed twice.
+ throw new Exception(e.getMessage());
+ } finally {
+ super.tearDown();
+ }
+ }
+
+ /**
+ * Verifies that the reason is in the range of public-only codes.
+ */
+ private static int checkCameraAccessExceptionReason(CameraAccessException e) {
+ int reason = e.getReason();
+
+ switch (reason) {
+ case CameraAccessException.CAMERA_DISABLED:
+ case CameraAccessException.CAMERA_DISCONNECTED:
+ case CameraAccessException.CAMERA_ERROR:
+ return reason;
+ }
+
+ fail("Invalid CameraAccessException code: " + reason);
+
+ return -1; // unreachable
}
public void testCameraManagerGetDeviceIdList() throws Exception {
@@ -135,12 +180,12 @@
for (int i = 0; i < ids.length; i++) {
invalidId.append(ids[i]);
}
+
try {
- CameraCharacteristics props = mCameraManager.getCameraCharacteristics(
+ mCameraManager.getCameraCharacteristics(
invalidId.toString());
fail(String.format("Accepted invalid camera ID: %s", invalidId.toString()));
- }
- catch (IllegalArgumentException e) {
+ } catch (IllegalArgumentException e) {
// This is the exception that should be thrown in this case.
}
}
@@ -150,84 +195,263 @@
String[] ids = mCameraManager.getCameraIdList();
for (int i = 0; i < ids.length; i++) {
for (int j = 0; j < NUM_CAMERA_REOPENS; j++) {
- CameraDevice camera = CameraTestUtils.openCamera(mCameraManager, ids[i], mHandler);
- assertNotNull(
- String.format("Failed to open camera device ID: %s", ids[i]), camera);
- camera.close();
+ CameraDevice camera = null;
+ try {
+ MockStateListener mockListener = MockStateListener.mock();
+ mCameraListener = new BlockingStateListener(mockListener);
+
+ mCameraManager.openCamera(ids[i], mCameraListener, mHandler);
+
+ // Block until unConfigured
+ mCameraListener.waitForState(BlockingStateListener.STATE_UNCONFIGURED,
+ CameraTestUtils.CAMERA_IDLE_TIMEOUT_MS);
+
+ // Ensure state transitions are in right order:
+ // -- 1) Opened
+ // -- 2) Unconfigured
+ // Ensure no other state transitions have occurred:
+ camera = verifyCameraStateOpenedThenUnconfigured(ids[i], mockListener);
+ } finally {
+ if (camera != null) {
+ camera.close();
+ }
+ }
}
}
}
/**
- * Test: that all camera devices can be open at the same time, or the appropriate
- * exception is thrown if this can't be done.
+ * Test: one or more camera devices can be open at the same time, or the right error state
+ * is set if this can't be done.
*/
public void testCameraManagerOpenAllCameras() throws Exception {
String[] ids = mCameraManager.getCameraIdList();
- CameraDevice[] cameras = new CameraDevice[ids.length];
+ assertNotNull("Camera ids shouldn't be null", ids);
+
+ // Skip test if the device doesn't have multiple cameras.
+ if (ids.length <= 1) {
+ return;
+ }
+
+ List<CameraDevice> cameraList = new ArrayList<CameraDevice>();
+ List<MockStateListener> listenerList = new ArrayList<MockStateListener>();
try {
for (int i = 0; i < ids.length; i++) {
- try {
- cameras[i] = CameraTestUtils.openCamera(mCameraManager, ids[i], mHandler);
+ // Ignore state changes from other cameras
+ MockStateListener mockListener = MockStateListener.mock();
+ mCameraListener = new BlockingStateListener(mockListener);
- /**
- * If the camera can't be opened, should throw an exception, rather than
- * returning null.
- */
- assertNotNull(
- String.format("Failed to open camera device ID: %s", ids[i]),
- cameras[i]);
+ /**
+ * Track whether or not we got a synchronous error from openCamera.
+ *
+ * A synchronous error must also be accompanied by an asynchronous
+ * StateListener#onError callback.
+ */
+ boolean expectingError = false;
+
+ String cameraId = ids[i];
+ try {
+ mCameraManager.openCamera(cameraId, mCameraListener,
+ mHandler);
+ } catch (CameraAccessException e) {
+ if (checkCameraAccessExceptionReason(e) == CameraAccessException.CAMERA_ERROR) {
+ expectingError = true;
+ } else {
+ // TODO: We should handle a Disabled camera by passing here and elsewhere
+ fail("Camera must not be disconnected or disabled for this test" + ids[i]);
+ }
}
- catch (CameraAccessException e) {
- /**
- * This is the expected behavior if the camera can't be opened due to
- * limitations on how many devices can be open simultaneously.
- */
- assertEquals(
- String.format("Invalid exception reason: %s", e.getReason()),
- CameraAccessException.MAX_CAMERAS_IN_USE, e.getReason());
+
+ List<Integer> expectedStates = new ArrayList<Integer>();
+ expectedStates.add(BlockingStateListener.STATE_UNCONFIGURED);
+ expectedStates.add(BlockingStateListener.STATE_ERROR);
+ int state = mCameraListener.waitForAnyOfStates(
+ expectedStates, CameraTestUtils.CAMERA_IDLE_TIMEOUT_MS);
+
+ // It's possible that we got an asynchronous error transition only. This is ok.
+ if (expectingError) {
+ assertEquals("Throwing a CAMERA_ERROR exception must be accompanied with a " +
+ "StateListener#onError callback",
+ BlockingStateListener.STATE_ERROR, state);
}
+
+ /**
+ * Two situations are considered passing:
+ * 1) The camera opened successfully.
+ * => No error must be set.
+ * 2) The camera did not open because there were too many other cameras opened.
+ * => Only MAX_CAMERAS_IN_USE error must be set.
+ *
+ * Any other situation is considered a failure.
+ *
+ * For simplicity we treat disconnecting asynchronously as a failure, so
+ * camera devices should not be physically unplugged during this test.
+ */
+
+ CameraDevice camera;
+ if (state == BlockingStateListener.STATE_ERROR) {
+ // Camera did not open because too many other cameras were opened
+ // => onError called exactly once with a non-null camera
+ assertTrue("At least one camera must be opened successfully",
+ cameraList.size() > 0);
+
+ ArgumentCaptor<CameraDevice> argument =
+ ArgumentCaptor.forClass(CameraDevice.class);
+
+ verify(mockListener)
+ .onError(
+ argument.capture(),
+ eq(CameraDevice.StateListener.ERROR_MAX_CAMERAS_IN_USE));
+ verifyNoMoreInteractions(mockListener);
+
+ camera = argument.getValue();
+ assertNotNull("Expected a non-null camera for the error transition for ID: "
+ + ids[i], camera);
+ } else if (state == BlockingStateListener.STATE_UNCONFIGURED) {
+ // Camera opened successfully.
+ // => onOpened+onUnconfigured called exactly once with same argument
+ camera = verifyCameraStateOpenedThenUnconfigured(cameraId,
+ mockListener);
+ } else {
+ fail("Unexpected state " + state);
+ camera = null; // unreachable. but need this for java compiler
+ }
+
+ // Keep track of cameras so we can close it later
+ cameraList.add(camera);
+ listenerList.add(mockListener);
+ }
+ } finally {
+ for (CameraDevice camera : cameraList) {
+ camera.close();
}
}
- finally {
- for (int i = 0; i < ids.length; i++) {
- if (cameras[i] != null) {
- cameras[i].close();
- }
- }
+
+ /*
+ * Ensure that no state transitions have bled through from one camera to another
+ * after closing the cameras.
+ */
+ int i = 0;
+ for (MockStateListener listener : listenerList) {
+ CameraDevice camera = cameraList.get(i);
+
+ verify(listener).onClosed(eq(camera));
+ verifyNoMoreInteractions(listener);
+ // Only a #close can happen on the camera since we were done with it.
+ // Also nothing else should've happened between the close and the open.
}
}
- // Test: that opening the same device multiple times throws the right exception.
+ /**
+ * Verifies the camera in this listener was opened and then unconfigured exactly once.
+ *
+ * <p>This assumes that no other action to the camera has been done (e.g.
+ * it hasn't been configured, or closed, or disconnected). Verification is
+ * performed immediately without any timeouts.</p>
+ *
+ * <p>This checks that the state has previously changed first for opened and then unconfigured.
+ * Any other state transitions will fail. A test failure is thrown if verification fails.</p>
+ *
+ * @param cameraId Camera identifier
+ * @param listener Listener which was passed to {@link CameraManager#openCamera}
+ *
+ * @return The camera device (non-{@code null}).
+ */
+ private static CameraDevice verifyCameraStateOpenedThenUnconfigured(String cameraId,
+ MockStateListener listener) {
+ ArgumentCaptor<CameraDevice> argument =
+ ArgumentCaptor.forClass(CameraDevice.class);
+ InOrder inOrder = inOrder(listener);
+
+ /**
+ * State transitions (in that order):
+ * 1) onOpened
+ * 2) onUnconfigured
+ *
+ * No other transitions must occur for successful #openCamera
+ */
+ inOrder.verify(listener)
+ .onOpened(argument.capture());
+
+ CameraDevice camera = argument.getValue();
+ assertNotNull(
+ String.format("Failed to unconfigure camera device ID: %s", cameraId),
+ camera);
+
+ inOrder.verify(listener)
+ .onUnconfigured(argument.capture());
+
+ assertEquals(String.format("Opened camera did not match unconfigured camera " +
+ "for camera device ID: %s", cameraId),
+ camera,
+ argument.getValue());
+ // Do not use inOrder here since that would skip anything called before onOpened
+ verifyNoMoreInteractions(listener);
+
+ return camera;
+ }
+
+ /**
+ * Test: that opening the same device multiple times and make sure the right
+ * error state is set.
+ */
public void testCameraManagerOpenCameraTwice() throws Exception {
String[] ids = mCameraManager.getCameraIdList();
- CameraDevice[] cameras = new CameraDevice[2];
- if (ids.length > 0) {
+
+ // Test across every camera device.
+ for (int i = 0; i < ids.length; ++i) {
+ CameraDevice successCamera = null;
+ mCollector.setCameraId(ids[i]);
+
try {
- cameras[0] = CameraTestUtils.openCamera(mCameraManager, ids[0], mHandler);
- assertNotNull(
- String.format("Failed to open camera device ID: %s", ids[0]),
- cameras[0]);
+ MockStateListener mockSuccessListener = MockStateListener.mock();
+ MockStateListener mockFailListener = MockStateListener.mock();
+
+ BlockingStateListener successListener =
+ new BlockingStateListener(mockSuccessListener);
+ BlockingStateListener failListener =
+ new BlockingStateListener(mockFailListener);
+
+ mCameraManager.openCamera(ids[i], successListener, mHandler);
+
try {
- cameras[1] = CameraTestUtils.openCamera(mCameraManager, ids[0], mHandler);
- fail(String.format("Opened the same camera device twice ID: %s",
- ids[0]));
+ mCameraManager.openCamera(ids[i], mCameraListener,
+ mHandler);
+ } catch (CameraAccessException e) {
+ // Optional (but common). Camera might fail asynchronously only.
+ // Don't assert here, otherwise, all subsequent tests will fail because the
+ // opened camera is never closed.
+ mCollector.expectEquals(
+ "If second camera open fails immediately, must be due to"
+ + "camera being busy for ID: " + ids[i],
+ CameraAccessException.CAMERA_ERROR, e.getReason());
}
- catch (CameraAccessException e) {
- /**
- * This is the expected behavior if the camera device is attempted to
- * be opened more than once.
- */
- assertEquals(
- String.format("Invalid exception reason: %s", e.getReason()),
- CameraAccessException.CAMERA_IN_USE, e.getReason());
- }
- }
- finally {
- for (int i = 0; i < 2; i++) {
- if (cameras[i] != null) {
- cameras[i].close();
- }
+
+ successListener.waitForState(BlockingStateListener.STATE_OPENED,
+ CameraTestUtils.CAMERA_IDLE_TIMEOUT_MS);
+ // Have to get the successCamera here, otherwise, it won't be
+ // closed if STATE_ERROR timeout exception occurs.
+ ArgumentCaptor<CameraDevice> argument =
+ ArgumentCaptor.forClass(CameraDevice.class);
+ verify(mockSuccessListener, atLeastOnce()).onOpened(argument.capture());
+ successCamera = argument.getValue();
+ successListener.waitForState(BlockingStateListener.STATE_UNCONFIGURED,
+ CameraTestUtils.CAMERA_IDLE_TIMEOUT_MS);
+
+ failListener.waitForState(BlockingStateListener.STATE_ERROR,
+ CameraTestUtils.CAMERA_IDLE_TIMEOUT_MS);
+
+ successCamera = verifyCameraStateOpenedThenUnconfigured(
+ ids[i], mockSuccessListener);
+
+ verify(mockFailListener)
+ .onError(
+ and(notNull(CameraDevice.class), not(successCamera)),
+ StateListener.ERROR_CAMERA_IN_USE);
+ verifyNoMoreInteractions(mockFailListener);
+ } finally {
+ if (successCamera != null) {
+ successCamera.close();
}
}
}
@@ -252,13 +476,12 @@
* a listener that isn't registered should have no effect.
*/
public void testCameraManagerListener() throws Exception {
- CameraTestThread callbackThread = new CameraTestThread();
- Handler callbackHandler = callbackThread.start();
+ mCameraManager.removeAvailabilityListener(mListener);
+ mCameraManager.addAvailabilityListener(mListener, mHandler);
+ mCameraManager.addAvailabilityListener(mListener, mHandler);
+ mCameraManager.removeAvailabilityListener(mListener);
+ mCameraManager.removeAvailabilityListener(mListener);
- mCameraManager.removeAvailabilityListener(mListener);
- mCameraManager.addAvailabilityListener(mListener, callbackHandler);
- mCameraManager.addAvailabilityListener(mListener, callbackHandler);
- mCameraManager.removeAvailabilityListener(mListener);
- mCameraManager.removeAvailabilityListener(mListener);
+ // TODO: test the listener callbacks
}
}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestThread.java b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestThread.java
deleted file mode 100644
index 9516ead..0000000
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestThread.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.hardware.camera2.cts;
-
-import android.os.ConditionVariable;
-import android.os.Handler;
-import android.os.Looper;
-import android.util.Log;
-
-import java.util.concurrent.TimeoutException;
-
-/**
- * Camera test thread wrapper for handling camera callbacks
- */
-public class CameraTestThread implements AutoCloseable {
- private static final String TAG = "CameraTestThread";
- private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
- // Timeout for initializing looper and opening camera in Milliseconds.
- private static final long WAIT_FOR_COMMAND_TO_COMPLETE = 5000;
- private Looper mLooper = null;
- private Handler mHandler = null;
-
- /**
- * Create and start a looper thread, return the Handler
- */
- public synchronized Handler start() throws Exception {
- final ConditionVariable startDone = new ConditionVariable();
- if (mLooper != null || mHandler !=null) {
- Log.w(TAG, "Looper thread already started");
- return mHandler;
- }
-
- new Thread() {
- @Override
- public void run() {
- if (VERBOSE) Log.v(TAG, "start loopRun");
- Looper.prepare();
- // Save the looper so that we can terminate this thread
- // after we are done with it.
- mLooper = Looper.myLooper();
- mHandler = new Handler();
- startDone.open();
- Looper.loop();
- if (VERBOSE) Log.v(TAG, "createLooperThread: finished");
- }
- }.start();
-
- if (VERBOSE) Log.v(TAG, "start waiting for looper");
- if (!startDone.block(WAIT_FOR_COMMAND_TO_COMPLETE)) {
- throw new TimeoutException("createLooperThread: start timeout");
- }
- return mHandler;
- }
-
- /**
- * Terminate the looper thread
- */
- public synchronized void close() throws Exception {
- if (mLooper == null || mHandler == null) {
- Log.w(TAG, "Looper thread doesn't start yet");
- return;
- }
-
- if (VERBOSE) Log.v(TAG, "Terminate looper thread");
- mLooper.quit();
- mLooper.getThread().join();
- mLooper = null;
- mHandler = null;
- }
-
- @Override
- protected void finalize() throws Throwable {
- try {
- close();
- } finally {
- super.finalize();
- }
- }
-}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java
index 7f10cb8..52a486f 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java
@@ -16,43 +16,78 @@
package android.hardware.camera2.cts;
+import static com.android.ex.camera2.blocking.BlockingStateListener.*;
+
+import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
+import android.graphics.PointF;
+import android.graphics.Rect;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
-import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.CameraCharacteristics;
-import android.hardware.camera2.Size;
+import android.hardware.camera2.CaptureFailure;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.TotalCaptureResult;
+import android.util.Size;
+import android.hardware.camera2.params.MeteringRectangle;
+import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.media.Image.Plane;
import android.os.Handler;
import android.util.Log;
+import android.view.Surface;
import com.android.ex.camera2.blocking.BlockingCameraManager;
import com.android.ex.camera2.blocking.BlockingCameraManager.BlockingOpenException;
+import com.android.ex.camera2.blocking.BlockingStateListener;
+import com.android.ex.camera2.exceptions.TimeoutRuntimeException;
import junit.framework.Assert;
+import org.mockito.Mockito;
+
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Array;
import java.nio.ByteBuffer;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
/**
* A package private utility class for wrapping up the camera2 cts test common utility functions
*/
-class CameraTestUtils extends Assert {
+public class CameraTestUtils extends Assert {
private static final String TAG = "CameraTestUtils";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
-
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ // Only test the preview and video size that is no larger than 1080p.
+ public static final Size PREVIEW_SIZE_BOUND = new Size(1920, 1080);
// Default timeouts for reaching various states
- public static final int CAMERA_OPEN_TIMEOUT_MS = 500;
+ public static final int CAMERA_OPEN_TIMEOUT_MS = 2000;
+ public static final int CAMERA_CLOSE_TIMEOUT_MS = 2000;
public static final int CAMERA_IDLE_TIMEOUT_MS = 2000;
- public static final int CAMERA_ACTIVE_TIMEOUT_MS = 500;
- public static final int CAMERA_BUSY_TIMEOUT_MS = 500;
+ public static final int CAMERA_ACTIVE_TIMEOUT_MS = 1000;
+ public static final int CAMERA_BUSY_TIMEOUT_MS = 1000;
+ public static final int CAMERA_UNCONFIGURED_TIMEOUT_MS = 1000;
+ public static final int CAMERA_CONFIGURE_TIMEOUT_MS = 2000;
+ public static final int CAPTURE_RESULT_TIMEOUT_MS = 3000;
+ public static final int CAPTURE_IMAGE_TIMEOUT_MS = 3000;
+ /**
+ * Dummy listener that release the image immediately once it is available.
+ *
+ * <p>
+ * It can be used for the case where we don't care the image data at all.
+ * </p>
+ */
public static class ImageDropperListener implements ImageReader.OnImageAvailableListener {
@Override
public void onImageAvailable(ImageReader reader) {
@@ -67,6 +102,108 @@
}
}
+ public static class SimpleImageReaderListener
+ implements ImageReader.OnImageAvailableListener {
+ private final LinkedBlockingQueue<Image> mQueue =
+ new LinkedBlockingQueue<Image>();
+
+ @Override
+ public void onImageAvailable(ImageReader reader) {
+ try {
+ mQueue.put(reader.acquireNextImage());
+ } catch (InterruptedException e) {
+ throw new UnsupportedOperationException(
+ "Can't handle InterruptedException in onImageAvailable");
+ }
+ }
+
+ /**
+ * Get an image from the image reader.
+ *
+ * @param timeout Timeout value for the wait.
+ * @return The image from the image reader.
+ */
+ public Image getImage(long timeout) throws InterruptedException {
+ Image image = mQueue.poll(timeout, TimeUnit.MILLISECONDS);
+ assertNotNull("Wait for an image timed out in " + timeout + "ms", image);
+ return image;
+ }
+ }
+
+ public static class SimpleCaptureListener extends CameraDevice.CaptureListener {
+ private final LinkedBlockingQueue<CaptureResult> mQueue =
+ new LinkedBlockingQueue<CaptureResult>();
+
+ @Override
+ public void onCaptureStarted(CameraDevice camera, CaptureRequest request, long timestamp)
+ {
+ }
+
+ @Override
+ public void onCaptureCompleted(CameraDevice camera, CaptureRequest request,
+ TotalCaptureResult result) {
+ try {
+ mQueue.put(result);
+ } catch (InterruptedException e) {
+ throw new UnsupportedOperationException(
+ "Can't handle InterruptedException in onCaptureCompleted");
+ }
+ }
+
+ @Override
+ public void onCaptureFailed(CameraDevice camera, CaptureRequest request,
+ CaptureFailure failure) {
+ }
+
+ @Override
+ public void onCaptureSequenceCompleted(CameraDevice camera, int sequenceId,
+ long frameNumber) {
+ }
+
+ public CaptureResult getCaptureResult(long timeout) {
+ try {
+ CaptureResult result = mQueue.poll(timeout, TimeUnit.MILLISECONDS);
+ assertNotNull("Wait for a capture result timed out in " + timeout + "ms", result);
+ return result;
+ } catch (InterruptedException e) {
+ throw new UnsupportedOperationException("Unhandled interrupted exception", e);
+ }
+ }
+
+ /**
+ * Get the {@link #CaptureResult capture result} for a given
+ * {@link #CaptureRequest capture request}.
+ *
+ * @param myRequest The {@link #CaptureRequest capture request} whose
+ * corresponding {@link #CaptureResult capture result} was
+ * being waited for
+ * @param numResultsWait Number of frames to wait for the capture result
+ * before timeout.
+ * @throws TimeoutRuntimeException If more than numResultsWait results are
+ * seen before the result matching myRequest arrives, or each
+ * individual wait for result times out after
+ * {@value #CAPTURE_RESULT_TIMEOUT_MS}ms.
+ */
+ public CaptureResult getCaptureResultForRequest(CaptureRequest myRequest,
+ int numResultsWait) {
+ if (numResultsWait < 0) {
+ throw new IllegalArgumentException("numResultsWait must be no less than 0");
+ }
+
+ CaptureResult result;
+ int i = 0;
+ do {
+ result = getCaptureResult(CAPTURE_RESULT_TIMEOUT_MS);
+ if (result.getRequest().equals(myRequest)) {
+ return result;
+ }
+ } while (i++ < numResultsWait);
+
+ throw new TimeoutRuntimeException("Unable to get the expected capture result after "
+ + "waiting for " + numResultsWait + " results");
+ }
+ }
+
/**
* Block until the camera is opened.
*
@@ -74,9 +211,15 @@
* an AssertionError if it fails to open the camera device.</p>
*
* @return CameraDevice opened camera device
- * @throws BlockingOpenException
*
- * @throws AssertionError if the camera fails to open (or times out)
+ * @throws IllegalArgumentException
+ * If the handler is null, or if the handler's looper is current.
+ * @throws CameraAccessException
+ * If open fails immediately.
+ * @throws BlockingOpenException
+ * If open fails after blocking for some amount of time.
+ * @throws TimeoutRuntimeException
+ * If opening times out. Typically unrecoverable.
*/
public static CameraDevice openCamera(CameraManager manager, String cameraId,
CameraDevice.StateListener listener, Handler handler) throws CameraAccessException,
@@ -101,9 +244,14 @@
* <p>Don't use this to test #onDisconnected/#onError since this will throw
* an AssertionError if it fails to open the camera device.</p>
*
- * @return CameraDevice opened camera device
- *
- * @throws AssertionError if the camera fails to open (or times out)
+ * @throws IllegalArgumentException
+ * If the handler is null, or if the handler's looper is current.
+ * @throws CameraAccessException
+ * If open fails immediately.
+ * @throws BlockingOpenException
+ * If open fails after blocking for some amount of time.
+ * @throws TimeoutRuntimeException
+ * If opening times out. Typically unrecoverable.
*/
public static CameraDevice openCamera(CameraManager manager, String cameraId, Handler handler)
throws CameraAccessException,
@@ -111,6 +259,24 @@
return openCamera(manager, cameraId, /*listener*/null, handler);
}
+ /**
+ * Configure camera output surfaces.
+ *
+ * @param camera The CameraDevice to be configured.
+ * @param outputSurfaces The surface list that used for camera output.
+ * @param listener The callback CameraDevice will notify when capture results are available.
+ */
+ public static void configureCameraOutputs(CameraDevice camera, List<Surface> outputSurfaces,
+ BlockingStateListener listener) throws CameraAccessException {
+ camera.configureOutputs(outputSurfaces);
+ listener.waitForState(STATE_BUSY, CAMERA_BUSY_TIMEOUT_MS);
+ if (outputSurfaces == null || outputSurfaces.size() == 0) {
+ listener.waitForState(STATE_UNCONFIGURED, CAMERA_UNCONFIGURED_TIMEOUT_MS);
+ } else {
+ listener.waitForState(STATE_IDLE, CAMERA_IDLE_TIMEOUT_MS);
+ }
+ }
+
public static <T> void assertArrayNotEmpty(T arr, String message) {
assertTrue(message, arr != null && Array.getLength(arr) > 0);
}
@@ -121,9 +287,7 @@
public static void checkYuvFormat(int format) {
if ((format != ImageFormat.YUV_420_888) &&
(format != ImageFormat.NV21) &&
- (format != ImageFormat.YV12) &&
- (format != ImageFormat.Y8) &&
- (format != ImageFormat.Y16)) {
+ (format != ImageFormat.YV12)) {
fail("Wrong formats: " + format);
}
}
@@ -183,7 +347,6 @@
buffer = planes[i].getBuffer();
assertNotNull("Fail to get bytebuffer from plane", buffer);
rowStride = planes[i].getRowStride();
- assertTrue("rowStride should be no less than width", rowStride >= width);
pixelStride = planes[i].getPixelStride();
assertTrue("pixel stride " + pixelStride + " is invalid", pixelStride > 0);
if (VERBOSE) {
@@ -216,6 +379,7 @@
}
}
if (VERBOSE) Log.v(TAG, "Finished reading data from plane " + i);
+ buffer.rewind();
}
return data;
}
@@ -223,7 +387,7 @@
/**
* <p>Check android image format validity for an image, only support below formats:</p>
*
- * <p>YUV_420_888/NV21/YV12/Y8/Y16, can add more for future</p>
+ * <p>YUV_420_888/NV21/YV12, can add more for future</p>
*/
public static void checkAndroidImageFormat(Image image) {
int format = image.getFormat();
@@ -234,11 +398,8 @@
case ImageFormat.YV12:
assertEquals("YUV420 format Images should have 3 planes", 3, planes.length);
break;
- case ImageFormat.Y8:
- case ImageFormat.Y16:
- assertEquals("Y8/Y16 Image should have 1 plane", 1, planes.length);
- break;
case ImageFormat.JPEG:
+ case ImageFormat.RAW_SENSOR:
assertEquals("Jpeg Image should have one plane", 1, planes.length);
break;
default:
@@ -263,31 +424,480 @@
}
}
+ /**
+ * Get the available output sizes for the user-defined {@code format}.
+ *
+ * <p>Note that implementation-defined/hidden formats are not supported.</p>
+ */
public static Size[] getSupportedSizeForFormat(int format, String cameraId,
- CameraManager cameraManager) throws Exception {
- CameraMetadata.Key<Size[]> key = null;
+ CameraManager cameraManager) throws CameraAccessException {
CameraCharacteristics properties = cameraManager.getCameraCharacteristics(cameraId);
assertNotNull("Can't get camera characteristics!", properties);
if (VERBOSE) {
Log.v(TAG, "get camera characteristics for camera: " + cameraId);
}
- switch (format) {
- case ImageFormat.JPEG:
- key = CameraCharacteristics.SCALER_AVAILABLE_JPEG_SIZES;
- break;
- case ImageFormat.YUV_420_888:
- case ImageFormat.YV12:
- case ImageFormat.NV21:
- case ImageFormat.Y8:
- case ImageFormat.Y16:
- key = CameraCharacteristics.SCALER_AVAILABLE_PROCESSED_SIZES;
- break;
- default:
- throw new UnsupportedOperationException(
- String.format("Invalid format specified 0x%x", format));
- }
- Size[] availableSizes = properties.get(key);
+ StreamConfigurationMap configMap =
+ properties.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+ Size[] availableSizes = configMap.getOutputSizes(format);
+ assertArrayNotEmpty(availableSizes, "availableSizes should not be empty");
if (VERBOSE) Log.v(TAG, "Supported sizes are: " + Arrays.deepToString(availableSizes));
return availableSizes;
}
-}
\ No newline at end of file
+
+ /**
+ * Size comparator that compares the number of pixels it covers.
+ *
+ * <p>If two the areas of two sizes are same, compare the widths.</p>
+ */
+ public static class SizeComparator implements Comparator<Size> {
+ @Override
+ public int compare(Size lhs, Size rhs) {
+ long left = lhs.getWidth() * lhs.getHeight();
+ long right = rhs.getWidth() * rhs.getHeight();
+ if (left == right) {
+ left = lhs.getWidth();
+ right = rhs.getWidth();
+ }
+ return (left < right) ? -1 : (left > right ? 1 : 0);
+ }
+ }
+
+ /**
+ * Get sorted size list in descending order. Remove the sizes larger than
+ * the bound. If the bound is null, don't do the size bound filtering.
+ */
+ static public List<Size> getSupportedPreviewSizes(String cameraId,
+ CameraManager cameraManager, Size bound) throws CameraAccessException {
+ return getSortedSizesForFormat(cameraId, cameraManager, ImageFormat.YUV_420_888, bound);
+ }
+
+ /**
+ * Get a sorted list of sizes from a given size list.
+ *
+ * <p>
+ * The size is compare by area it covers, if the areas are same, then
+ * compare the widths.
+ * </p>
+ *
+ * @param sizeList The input size list to be sorted
+ * @param ascending True if the order is ascending, otherwise descending order
+ * @return The ordered list of sizes
+ */
+ static public List<Size> getAscendingOrderSizes(final List<Size> sizeList, boolean ascending) {
+ if (sizeList == null) {
+ throw new IllegalArgumentException("sizeList shouldn't be null");
+ }
+
+ Comparator<Size> comparator = new SizeComparator();
+ List<Size> sortedSizes = new ArrayList<Size>();
+ sortedSizes.addAll(sizeList);
+ Collections.sort(sortedSizes, comparator);
+ if (!ascending) {
+ Collections.reverse(sortedSizes);
+ }
+
+ return sortedSizes;
+ }
+
+ /**
+ * Get sorted (descending order) size list for given format. Remove the sizes larger than
+ * the bound. If the bound is null, don't do the size bound filtering.
+ */
+ static private List<Size> getSortedSizesForFormat(String cameraId,
+ CameraManager cameraManager, int format, Size bound) throws CameraAccessException {
+ Comparator<Size> comparator = new SizeComparator();
+ Size[] sizes = getSupportedSizeForFormat(format, cameraId, cameraManager);
+ List<Size> sortedSizes = null;
+ if (bound != null) {
+ sortedSizes = new ArrayList<Size>(/*capacity*/1);
+ for (Size sz : sizes) {
+ if (comparator.compare(sz, bound) <= 0) {
+ sortedSizes.add(sz);
+ }
+ }
+ } else {
+ sortedSizes = Arrays.asList(sizes);
+ }
+ assertTrue("Supported size list should have at least one element",
+ sortedSizes.size() > 0);
+
+ Collections.sort(sortedSizes, comparator);
+ // Make it in descending order.
+ Collections.reverse(sortedSizes);
+ return sortedSizes;
+ }
+
+ /**
+ * Get supported video size list for a given camera device.
+ *
+ * <p>
+ * Filter out the sizes that are larger than the bound. If the bound is
+ * null, don't do the size bound filtering.
+ * </p>
+ */
+ static public List<Size> getSupportedVideoSizes(String cameraId,
+ CameraManager cameraManager, Size bound) throws CameraAccessException {
+ return getSortedSizesForFormat(cameraId, cameraManager, ImageFormat.YUV_420_888, bound);
+ }
+
+ /**
+ * Get supported video size list (descending order) for a given camera device.
+ *
+ * <p>
+ * Filter out the sizes that are larger than the bound. If the bound is
+ * null, don't do the size bound filtering.
+ * </p>
+ */
+ static public List<Size> getSupportedStillSizes(String cameraId,
+ CameraManager cameraManager, Size bound) throws CameraAccessException {
+ return getSortedSizesForFormat(cameraId, cameraManager, ImageFormat.JPEG, bound);
+ }
+
+ static public Size getMinPreviewSize(String cameraId, CameraManager cameraManager)
+ throws CameraAccessException {
+ List<Size> sizes = getSupportedPreviewSizes(cameraId, cameraManager, null);
+ return sizes.get(sizes.size() - 1);
+ }
+
+ /**
+ * Get max supported preview size for a camera device.
+ */
+ static public Size getMaxPreviewSize(String cameraId, CameraManager cameraManager)
+ throws CameraAccessException {
+ return getMaxPreviewSize(cameraId, cameraManager, /*bound*/null);
+ }
+
+ /**
+ * Get max preview size for a camera device in the supported sizes that are no larger
+ * than the bound.
+ */
+ static public Size getMaxPreviewSize(String cameraId, CameraManager cameraManager, Size bound)
+ throws CameraAccessException {
+ List<Size> sizes = getSupportedPreviewSizes(cameraId, cameraManager, bound);
+ return sizes.get(0);
+ }
+
+ /**
+ * Get the largest size by area.
+ *
+ * @param sizes an array of sizes, must have at least 1 element
+ *
+ * @return Largest Size
+ *
+ * @throws IllegalArgumentException if sizes was null or had 0 elements
+ */
+ public static Size getMaxSize(Size[] sizes) {
+ if (sizes == null || sizes.length == 0) {
+ throw new IllegalArgumentException("sizes was empty");
+ }
+
+ Size sz = sizes[0];
+ for (Size size : sizes) {
+ if (size.getWidth() * size.getHeight() > sz.getWidth() * sz.getHeight()) {
+ sz = size;
+ }
+ }
+
+ return sz;
+ }
+
+ /**
+ * Get object array from byte array.
+ *
+ * @param array Input byte array to be converted
+ * @return Byte object array converted from input byte array
+ */
+ public static Byte[] toObject(byte[] array) {
+ return convertPrimitiveArrayToObjectArray(array, Byte.class);
+ }
+
+ /**
+ * Get object array from int array.
+ *
+ * @param array Input int array to be converted
+ * @return Integer object array converted from input int array
+ */
+ public static Integer[] toObject(int[] array) {
+ return convertPrimitiveArrayToObjectArray(array, Integer.class);
+ }
+
+ /**
+ * Get object array from float array.
+ *
+ * @param array Input float array to be converted
+ * @return Float object array converted from input float array
+ */
+ public static Float[] toObject(float[] array) {
+ return convertPrimitiveArrayToObjectArray(array, Float.class);
+ }
+
+ /**
+ * Get object array from double array.
+ *
+ * @param array Input double array to be converted
+ * @return Double object array converted from input double array
+ */
+ public static Double[] toObject(double[] array) {
+ return convertPrimitiveArrayToObjectArray(array, Double.class);
+ }
+
+ /**
+ * Convert a primitive input array into its object array version (e.g. from int[] to Integer[]).
+ *
+ * @param array Input array object
+ * @param wrapperClass The boxed class it converts to
+ * @return Boxed version of primitive array
+ */
+ private static <T> T[] convertPrimitiveArrayToObjectArray(final Object array,
+ final Class<T> wrapperClass) {
+ // getLength does the null check and isArray check already.
+ int arrayLength = Array.getLength(array);
+ if (arrayLength == 0) {
+ throw new IllegalArgumentException("Input array shouldn't be empty");
+ }
+
+ @SuppressWarnings("unchecked")
+ final T[] result = (T[]) Array.newInstance(wrapperClass, arrayLength);
+ for (int i = 0; i < arrayLength; i++) {
+ Array.set(result, i, Array.get(array, i));
+ }
+ return result;
+ }
+
+ /**
+ * Validate image based on format and size.
+ * <p>
+ * Only RAW_SENSOR, YUV420_888 and JPEG formats are supported. Calling this
+ * method with other formats will cause a UnsupportedOperationException.
+ * </p>
+ *
+ * @param image The image to be validated.
+ * @param width The image width.
+ * @param height The image height.
+ * @param format The image format.
+ * @param filePath The debug dump file path, null if don't want to dump to
+ * file.
+ * @throws UnsupportedOperationException if calling with format other than
+ * RAW_SENSOR, YUV420_888 or JPEG.
+ */
+ public static void validateImage(Image image, int width, int height, int format,
+ String filePath) {
+ checkImage(image, width, height, format);
+
+ /**
+ * TODO: validate timestamp:
+ * 1. capture result timestamp against the image timestamp (need
+ * consider frame drops)
+ * 2. timestamps should be monotonically increasing for different requests
+ */
+ if(VERBOSE) Log.v(TAG, "validating Image");
+ byte[] data = getDataFromImage(image);
+ assertTrue("Invalid image data", data != null && data.length > 0);
+
+ switch (format) {
+ case ImageFormat.JPEG:
+ validateJpegData(data, width, height, filePath);
+ break;
+ case ImageFormat.YUV_420_888:
+ validateYuvData(data, width, height, format, image.getTimestamp(), filePath);
+ break;
+ case ImageFormat.RAW_SENSOR:
+ validateRaw16Data(data, width, height, format, image.getTimestamp(), filePath);
+ break;
+ default:
+ throw new UnsupportedOperationException("Unsupported format for validation: "
+ + format);
+ }
+ }
+
+ /**
+ * Provide a mock for {@link CameraDevice.StateListener}.
+ *
+ * <p>Only useful because mockito can't mock {@link CameraDevice.StateListener} which is an
+ * abstract class.</p>
+ *
+ * <p>
+ * Use this instead of other classes when needing to verify interactions, since
+ * trying to spy on {@link BlockingStateListener} (or others) will cause unnecessary extra
+ * interactions which will cause false test failures.
+ * </p>
+ *
+ */
+ public static class MockStateListener extends CameraDevice.StateListener {
+
+ @Override
+ public void onOpened(CameraDevice camera) {
+ }
+
+ @Override
+ public void onDisconnected(CameraDevice camera) {
+ }
+
+ @Override
+ public void onError(CameraDevice camera, int error) {
+ }
+
+ private MockStateListener() {}
+
+ /**
+ * Create a Mockito-ready mocked StateListener.
+ */
+ public static MockStateListener mock() {
+ return Mockito.spy(new MockStateListener());
+ }
+ }
+
+ private static void validateJpegData(byte[] jpegData, int width, int height, String filePath) {
+ BitmapFactory.Options bmpOptions = new BitmapFactory.Options();
+ // DecodeBound mode: only parse the frame header to get width/height.
+ // it doesn't decode the pixel.
+ bmpOptions.inJustDecodeBounds = true;
+ BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length, bmpOptions);
+ assertEquals(width, bmpOptions.outWidth);
+ assertEquals(height, bmpOptions.outHeight);
+
+ // Pixel decoding mode: decode whole image. check if the image data
+ // is decodable here.
+ assertNotNull("Decoding jpeg failed",
+ BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length));
+ if (DEBUG && filePath != null) {
+ String fileName =
+ filePath + "/" + width + "x" + height + ".jpeg";
+ dumpFile(fileName, jpegData);
+ }
+ }
+
+ private static void validateYuvData(byte[] yuvData, int width, int height, int format,
+ long ts, String filePath) {
+ checkYuvFormat(format);
+ 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);
+
+ // TODO: Can add data validation for test pattern.
+
+ if (DEBUG && filePath != null) {
+ String fileName =
+ filePath + "/" + width + "x" + height + "_" + ts / 1e6 + ".yuv";
+ dumpFile(fileName, yuvData);
+ }
+ }
+
+ private static void validateRaw16Data(byte[] rawData, int width, int height, int format,
+ long ts, String filePath) {
+ if (VERBOSE) Log.v(TAG, "Validating raw data");
+ int expectedSize = width * height * ImageFormat.getBitsPerPixel(format) / 8;
+ assertEquals("Yuv data doesn't match", expectedSize, rawData.length);
+
+ // TODO: Can add data validation for test pattern.
+
+ if (DEBUG && filePath != null) {
+ String fileName =
+ filePath + "/" + width + "x" + height + "_" + ts / 1e6 + ".raw16";
+ dumpFile(fileName, rawData);
+ }
+
+ return;
+ }
+
+ public static <T> T getValueNotNull(CaptureResult result, CaptureResult.Key<T> key) {
+ if (result == null) {
+ throw new IllegalArgumentException("Result must not be null");
+ }
+
+ T value = result.get(key);
+ assertNotNull("Value of Key " + key.getName() + "shouldn't be null", value);
+ return value;
+ }
+
+ /**
+ * Get a crop region for a given zoom factor and center position.
+ * <p>
+ * The center position is normalized position in range of [0, 1.0], where
+ * (0, 0) represents top left corner, (1.0. 1.0) represents bottom right
+ * corner. The center position could limit the effective minimal zoom
+ * factor, for example, if the center position is (0.75, 0.75), the
+ * effective minimal zoom position becomes 2.0. If the requested zoom factor
+ * is smaller than 2.0, a crop region with 2.0 zoom factor will be returned.
+ * </p>
+ * <p>
+ * The aspect ratio of the crop region is maintained the same as the aspect
+ * ratio of active array.
+ * </p>
+ *
+ * @param zoomFactor The zoom factor to generate the crop region, it must be
+ * >= 1.0
+ * @param center The normalized zoom center point that is in the range of [0, 1].
+ * @param maxZoom The max zoom factor supported by this device.
+ * @param activeArray The active array size of this device.
+ * @return crop region for the given normalized center and zoom factor.
+ */
+ public static Rect getCropRegionForZoom(float zoomFactor, final PointF center,
+ final float maxZoom, final Rect activeArray) {
+ if (zoomFactor < 1.0) {
+ throw new IllegalArgumentException("zoom factor " + zoomFactor + " should be >= 1.0");
+ }
+ if (center.x > 1.0 || center.x < 0) {
+ throw new IllegalArgumentException("center.x " + center.x
+ + " should be in range of [0, 1.0]");
+ }
+ if (center.y > 1.0 || center.y < 0) {
+ throw new IllegalArgumentException("center.y " + center.y
+ + " should be in range of [0, 1.0]");
+ }
+ if (maxZoom < 1.0) {
+ throw new IllegalArgumentException("max zoom factor " + maxZoom + " should be >= 1.0");
+ }
+ if (activeArray == null) {
+ throw new IllegalArgumentException("activeArray must not be null");
+ }
+
+ float minCenterLength = Math.min(Math.min(center.x, 1.0f - center.x),
+ Math.min(center.y, 1.0f - center.y));
+ float minEffectiveZoom = 0.5f / minCenterLength;
+ if (minEffectiveZoom > maxZoom) {
+ throw new IllegalArgumentException("Requested center " + center.toString() +
+ " has minimal zoomable factor " + minEffectiveZoom + ", which exceeds max"
+ + " zoom factor " + maxZoom);
+ }
+
+ if (zoomFactor < minEffectiveZoom) {
+ Log.w(TAG, "Requested zoomFactor " + zoomFactor + " > minimal zoomable factor "
+ + minEffectiveZoom + ". It will be overwritten by " + minEffectiveZoom);
+ zoomFactor = minEffectiveZoom;
+ }
+
+ int cropCenterX = (int)(activeArray.width() * center.x);
+ int cropCenterY = (int)(activeArray.height() * center.y);
+ int cropWidth = (int) (activeArray.width() / zoomFactor);
+ int cropHeight = (int) (activeArray.height() / zoomFactor);
+
+ return new Rect(
+ /*left*/cropCenterX - cropWidth / 2,
+ /*top*/cropCenterY - cropHeight / 2,
+ /*right*/ cropCenterX + cropWidth / 2 - 1,
+ /*bottom*/cropCenterY + cropHeight / 2 - 1);
+ }
+
+ /**
+ * Calculate output 3A region from the intersection of input 3A region and cropped region.
+ *
+ * @param requestRegions The input 3A regions
+ * @param cropRect The cropped region
+ * @return expected 3A regions output in capture result
+ */
+ public static MeteringRectangle[] getExpectedOutputRegion(
+ MeteringRectangle[] requestRegions, Rect cropRect){
+ MeteringRectangle[] resultRegions = new MeteringRectangle[requestRegions.length];
+ for (int i = 0; i < requestRegions.length; i++) {
+ Rect requestRect = requestRegions[i].getRect();
+ Rect resultRect = new Rect();
+ assertTrue("Input 3A region must intersect cropped region",
+ resultRect.setIntersect(requestRect, cropRect));
+ resultRegions[i] = new MeteringRectangle(
+ resultRect,
+ requestRegions[i].getMeteringWeight());
+ }
+ return resultRegions;
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureRequestTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureRequestTest.java
new file mode 100644
index 0000000..aa548ed
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureRequestTest.java
@@ -0,0 +1,1988 @@
+/*
+ * Copyright 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 android.hardware.camera2.cts;
+
+import static android.hardware.camera2.cts.CameraTestUtils.*;
+import static android.hardware.camera2.CameraCharacteristics.*;
+
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.cts.CameraTestUtils.SimpleCaptureListener;
+import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase;
+import android.hardware.camera2.params.ColorSpaceTransform;
+import android.hardware.camera2.params.Face;
+import android.hardware.camera2.params.LensShadingMap;
+import android.hardware.camera2.params.MeteringRectangle;
+import android.hardware.camera2.params.RggbChannelVector;
+import android.hardware.camera2.params.TonemapCurve;
+
+import android.util.Log;
+import android.util.Range;
+import android.util.Rational;
+import android.util.Size;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * <p>
+ * Basic test for camera CaptureRequest key controls.
+ * </p>
+ * <p>
+ * Several test categories are covered: manual sensor control, 3A control,
+ * manual ISP control and other per-frame control and synchronization.
+ * </p>
+ */
+public class CaptureRequestTest extends Camera2SurfaceViewTestCase {
+ private static final String TAG = "CaptureRequestTest";
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+ private static final int NUM_FRAMES_VERIFIED = 15;
+ private static final int NUM_FACE_DETECTION_FRAMES_VERIFIED = 60;
+ /** 30ms exposure time must be supported by full capability devices. */
+ private static final long DEFAULT_EXP_TIME_NS = 30000000L;
+ private static final int DEFAULT_SENSITIVITY = 100;
+ private static final int RGGB_COLOR_CHANNEL_COUNT = 4;
+ private static final int MAX_SHADING_MAP_SIZE = 64 * 64 * RGGB_COLOR_CHANNEL_COUNT;
+ private static final int MIN_SHADING_MAP_SIZE = 1 * 1 * RGGB_COLOR_CHANNEL_COUNT;
+ private static final long IGORE_REQUESTED_EXPOSURE_TIME_CHECK = -1L;
+ private static final long EXPOSURE_TIME_BOUNDARY_50HZ_NS = 10000000L; // 10ms
+ private static final long EXPOSURE_TIME_BOUNDARY_60HZ_NS = 8333333L; // 8.3ms, Approximation.
+ private static final long EXPOSURE_TIME_ERROR_MARGIN_NS = 100000L; // 100us, Approximation.
+ private static final int SENSITIVITY_ERROR_MARGIN = 10; // 10
+ private static final int DEFAULT_NUM_EXPOSURE_TIME_STEPS = 3;
+ private static final int DEFAULT_NUM_SENSITIVITY_STEPS = 16;
+ private static final int DEFAULT_SENSITIVITY_STEP_SIZE = 100;
+ private static final int NUM_RESULTS_WAIT_TIMEOUT = 100;
+ private static final int NUM_TEST_FOCUS_DISTANCES = 10;
+ // 5 percent error margin for calibrated device
+ private static final float FOCUS_DISTANCE_ERROR_PERCENT_CALIBRATED = 0.05f;
+ // 25 percent error margin for uncalibrated device
+ private static final float FOCUS_DISTANCE_ERROR_PERCENT_UNCALIBRATED = 0.25f;
+ // 10 percent error margin for approximate device
+ private static final float FOCUS_DISTANCE_ERROR_PERCENT_APPROXIMATE = 0.10f;
+ private static final int ANTI_FLICKERING_50HZ = 1;
+ private static final int ANTI_FLICKERING_60HZ = 2;
+
+ // Linear tone mapping curve example.
+ private static final float[] TONEMAP_CURVE_LINEAR = {0, 0, 1.0f, 1.0f};
+ // Standard sRGB tone mapping, per IEC 61966-2-1:1999, with 16 control points.
+ private static final float[] TONEMAP_CURVE_SRGB = {
+ 0.0000f, 0.0000f, 0.0667f, 0.2864f, 0.1333f, 0.4007f, 0.2000f, 0.4845f,
+ 0.2667f, 0.5532f, 0.3333f, 0.6125f, 0.4000f, 0.6652f, 0.4667f, 0.7130f,
+ 0.5333f, 0.7569f, 0.6000f, 0.7977f, 0.6667f, 0.8360f, 0.7333f, 0.8721f,
+ 0.8000f, 0.9063f, 0.8667f, 0.9389f, 0.9333f, 0.9701f, 1.0000f, 1.0000f
+ };
+ private final Rational ZERO_R = new Rational(0, 1);
+ private final Rational ONE_R = new Rational(1, 1);
+
+ private final int NUM_ALGORITHMS = 3; // AE, AWB and AF
+ private final int INDEX_ALGORITHM_AE = 0;
+ private final int INDEX_ALGORITHM_AWB = 1;
+ private final int INDEX_ALGORITHM_AF = 2;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ /**
+ * Test black level lock when exposure value change.
+ * <p>
+ * When {@link CaptureRequest#BLACK_LEVEL_LOCK} is true in a request, the
+ * camera device should lock the black level. When the exposure values are changed,
+ * the camera may require reset black level Since changes to certain capture
+ * parameters (such as exposure time) may require resetting of black level
+ * compensation. However, the black level must remain locked after exposure
+ * value changes (when requests have lock ON).
+ * </p>
+ */
+ public void testBlackLevelLock() throws Exception {
+ for (int i = 0; i < mCameraIds.length; i++) {
+ try {
+ openDevice(mCameraIds[i]);
+
+ if (!mStaticInfo.isHardwareLevelFull()) {
+ continue;
+ }
+
+ SimpleCaptureListener listener = new SimpleCaptureListener();
+ CaptureRequest.Builder requestBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+
+ // Start with default manual exposure time, with black level being locked.
+ requestBuilder.set(CaptureRequest.BLACK_LEVEL_LOCK, true);
+ changeExposure(requestBuilder, DEFAULT_EXP_TIME_NS, DEFAULT_SENSITIVITY);
+
+ Size previewSz =
+ getMaxPreviewSize(mCamera.getId(), mCameraManager, PREVIEW_SIZE_BOUND);
+ startPreview(requestBuilder, previewSz, listener);
+
+ // No lock OFF state is allowed as the exposure is not changed.
+ verifyBlackLevelLockResults(listener, NUM_FRAMES_VERIFIED, /*maxLockOffCnt*/0);
+
+ // Double the exposure time and gain, with black level still being locked.
+ changeExposure(requestBuilder, DEFAULT_EXP_TIME_NS * 2, DEFAULT_SENSITIVITY * 2);
+ startPreview(requestBuilder, previewSz, listener);
+
+ // Allow at most one lock OFF state as the exposure is changed once.
+ verifyBlackLevelLockResults(listener, NUM_FRAMES_VERIFIED, /*maxLockOffCnt*/1);
+
+ stopPreview();
+ } finally {
+ closeDevice();
+ }
+ }
+ }
+
+ /**
+ * Basic lens shading map request test.
+ * <p>
+ * When {@link CaptureRequest#SHADING_MODE} is set to OFF, no lens shading correction will
+ * be applied by the camera device, and an identity lens shading map data
+ * will be provided if {@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE} is ON.
+ * </p>
+ * <p>
+ * When {@link CaptureRequest#SHADING_MODE} is set to other modes, lens shading correction
+ * will be applied by the camera device. The lens shading map data can be
+ * requested by setting {@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE} to ON.
+ * </p>
+ */
+ public void testLensShadingMap() throws Exception {
+ for (int i = 0; i < mCameraIds.length; i++) {
+ try {
+ openDevice(mCameraIds[i]);
+
+ if (!mStaticInfo.isHardwareLevelFull()) {
+ continue;
+ }
+
+ SimpleCaptureListener listener = new SimpleCaptureListener();
+ CaptureRequest.Builder requestBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+
+ // Shading map mode OFF, lensShadingMapMode ON, camera device
+ // should output unity maps.
+ requestBuilder.set(CaptureRequest.SHADING_MODE, SHADING_MODE_OFF);
+ requestBuilder.set(CaptureRequest.STATISTICS_LENS_SHADING_MAP_MODE,
+ STATISTICS_LENS_SHADING_MAP_MODE_ON);
+
+ Size previewSz =
+ getMaxPreviewSize(mCamera.getId(), mCameraManager, PREVIEW_SIZE_BOUND);
+
+ listener = new SimpleCaptureListener();
+ startPreview(requestBuilder, previewSz, listener);
+
+ verifyShadingMap(listener, NUM_FRAMES_VERIFIED, SHADING_MODE_OFF);
+
+ // Shading map mode FAST, lensShadingMapMode ON, camera device
+ // should output valid maps.
+ requestBuilder.set(CaptureRequest.SHADING_MODE, SHADING_MODE_FAST);
+
+ listener = new SimpleCaptureListener();
+ startPreview(requestBuilder, previewSz, listener);
+
+ // Allow at most one lock OFF state as the exposure is changed once.
+ verifyShadingMap(listener, NUM_FRAMES_VERIFIED, SHADING_MODE_FAST);
+
+ // Shading map mode HIGH_QUALITY, lensShadingMapMode ON, camera device
+ // should output valid maps.
+ requestBuilder.set(CaptureRequest.SHADING_MODE, SHADING_MODE_HIGH_QUALITY);
+
+ listener = new SimpleCaptureListener();
+ startPreview(requestBuilder, previewSz, listener);
+
+ verifyShadingMap(listener, NUM_FRAMES_VERIFIED, SHADING_MODE_HIGH_QUALITY);
+
+ stopPreview();
+ } finally {
+ closeDevice();
+ }
+ }
+ }
+
+ /**
+ * Test {@link CaptureRequest#CONTROL_AE_ANTIBANDING_MODE} control.
+ * <p>
+ * Test all available anti-banding modes, check if the exposure time adjustment is
+ * correct.
+ * </p>
+ */
+ public void testAntiBandingModes() throws Exception {
+ for (int i = 0; i < mCameraIds.length; i++) {
+ try {
+ openDevice(mCameraIds[i]);
+
+ if (!mStaticInfo.isHardwareLevelFull()) {
+ continue;
+ }
+
+ int[] modes = mStaticInfo.getAeAvailableAntiBandingModesChecked();
+
+ Size previewSz =
+ getMaxPreviewSize(mCamera.getId(), mCameraManager, PREVIEW_SIZE_BOUND);
+
+ for (int mode : modes) {
+ antiBandingTestByMode(previewSz, mode);
+ }
+ } finally {
+ closeDevice();
+ }
+ }
+
+ }
+
+ /**
+ * Test AE mode and lock.
+ *
+ * <p>
+ * For AE lock, when it is locked, exposure parameters shouldn't be changed.
+ * For AE modes, each mode should satisfy the per frame controls defined in
+ * API specifications.
+ * </p>
+ */
+ public void testAeModeAndLock() throws Exception {
+ for (int i = 0; i < mCameraIds.length; i++) {
+ try {
+ openDevice(mCameraIds[i]);
+
+ // Can only test full capability because test relies on per frame control
+ // and synchronization.
+ if (!mStaticInfo.isHardwareLevelFull()) {
+ continue;
+ }
+
+ Size maxPreviewSz = mOrderedPreviewSizes.get(0); // Max preview size.
+
+ // Update preview surface with given size for all sub-tests.
+ updatePreviewSurface(maxPreviewSz);
+
+ // Test aeMode and lock
+ int[] aeModes = mStaticInfo.getAeAvailableModesChecked();
+ for (int mode : aeModes) {
+ aeModeAndLockTestByMode(mode);
+ }
+ } finally {
+ closeDevice();
+ }
+ }
+ }
+
+ /** Test {@link CaptureRequest#FLASH_MODE} control.
+ * <p>
+ * For each {@link CaptureRequest#FLASH_MODE} mode, test the flash control
+ * and {@link CaptureResult#FLASH_STATE} result.
+ * </p>
+ */
+ public void testFlashControl() throws Exception {
+ for (int i = 0; i < mCameraIds.length; i++) {
+ try {
+ openDevice(mCameraIds[i]);
+
+ // Can only test full capability because test relies on per frame control
+ // and synchronization.
+ if (!mStaticInfo.isHardwareLevelFull()) {
+ continue;
+ }
+
+ SimpleCaptureListener listener = new SimpleCaptureListener();
+ CaptureRequest.Builder requestBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+
+ Size maxPreviewSz = mOrderedPreviewSizes.get(0); // Max preview size.
+
+ startPreview(requestBuilder, maxPreviewSz, listener);
+
+ // Flash control can only be used when the AE mode is ON or OFF.
+ flashTestByAeMode(listener, CaptureRequest.CONTROL_AE_MODE_ON);
+ flashTestByAeMode(listener, CaptureRequest.CONTROL_AE_MODE_OFF);
+
+ stopPreview();
+ } finally {
+ closeDevice();
+ }
+ }
+ }
+
+ /**
+ * Test face detection modes and results.
+ */
+ public void testFaceDetection() throws Exception {
+ for (int i = 0; i < mCameraIds.length; i++) {
+ try {
+ openDevice(mCameraIds[i]);
+
+ faceDetectionTestByCamera();
+ } finally {
+ closeDevice();
+ }
+ }
+ }
+
+ /**
+ * Test tone map modes and controls.
+ */
+ public void testToneMapControl() throws Exception {
+ for (int i = 0; i < mCameraIds.length; i++) {
+ try {
+ openDevice(mCameraIds[i]);
+
+ toneMapTestByCamera();
+ } finally {
+ closeDevice();
+ }
+ }
+ }
+
+ /**
+ * Test color correction modes and controls.
+ */
+ public void testColorCorrectionControl() throws Exception {
+ for (String id : mCameraIds) {
+ try {
+ openDevice(id);
+
+ colorCorrectionTestByCamera();
+ } finally {
+ closeDevice();
+ }
+ }
+ }
+
+ public void testEdgeModeControl() throws Exception {
+ for (String id : mCameraIds) {
+ try {
+ openDevice(id);
+ if (!mStaticInfo.isPerFrameControlSupported()) {
+ Log.i(TAG, "Camera " + id + "Doesn't support per frame control");
+ continue;
+ }
+
+ edgeModesTestByCamera();
+ } finally {
+ closeDevice();
+ }
+ }
+ }
+
+ /**
+ * Test focus distance control.
+ */
+ public void testFocusDistanceControl() throws Exception {
+ for (String id : mCameraIds) {
+ try {
+ openDevice(id);
+ if (!mStaticInfo.isPerFrameControlSupported() || !mStaticInfo.hasFocuser()) {
+ Log.i(TAG, "Camera " + id
+ + "Doesn't support per frame control or has no focuser");
+ continue;
+ }
+
+ focusDistanceTestByCamera();
+ } finally {
+ closeDevice();
+ }
+ }
+ }
+
+ public void testNoiseReductionModeControl() throws Exception {
+ for (String id : mCameraIds) {
+ try {
+ openDevice(id);
+ if (!mStaticInfo.isPerFrameControlSupported()) {
+ Log.i(TAG, "Camera " + id + "Doesn't support per frame control");
+ continue;
+ }
+
+ noiseReductionModeTestByCamera();
+ } finally {
+ closeDevice();
+ }
+ }
+ }
+
+ /**
+ * Test AWB lock control. The color correction gain and transform shouldn't be changed
+ * when AWB is locked.
+ */
+ public void testAwbModeAndLock() throws Exception {
+ for (String id : mCameraIds) {
+ try {
+ openDevice(id);
+ if (!mStaticInfo.isPerFrameControlSupported()) {
+ Log.i(TAG, "Camera " + id + "Doesn't support per frame control");
+ continue;
+ }
+
+ awbModeAndLockTestByCamera();
+ } finally {
+ closeDevice();
+ }
+ }
+ }
+
+ /**
+ * Test different AF modes.
+ */
+ public void testAfModes() throws Exception {
+ for (String id : mCameraIds) {
+ try {
+ openDevice(id);
+ if (!mStaticInfo.isPerFrameControlSupported()) {
+ Log.i(TAG, "Camera " + id + "Doesn't support per frame control");
+ continue;
+ }
+
+ afModeTestByCamera();
+ } finally {
+ closeDevice();
+ }
+ }
+ }
+
+ /**
+ * Test video and optical stabilizations.
+ */
+ public void testCameraStabilizations() throws Exception {
+ for (String id : mCameraIds) {
+ try {
+ openDevice(id);
+ if (!mStaticInfo.isPerFrameControlSupported()) {
+ Log.i(TAG, "Camera " + id + "Doesn't support per frame control");
+ continue;
+ }
+
+ stabilizationTestByCamera();
+ } finally {
+ closeDevice();
+ }
+ }
+ }
+
+ /**
+ * Test digitalZoom (center wise and non-center wise), validate the returned crop regions.
+ */
+ public void testDigitalZoom() throws Exception {
+ for (String id : mCameraIds) {
+ try {
+ openDevice(id);
+ if (!mStaticInfo.isPerFrameControlSupported()) {
+ Log.i(TAG, "Camera " + id + "Doesn't support per frame control");
+ continue;
+ }
+
+ digitalZoomTestByCamera();
+ } finally {
+ closeDevice();
+ }
+ }
+ }
+
+ /**
+ * Test scene mode controls.
+ */
+ public void testSceneModes() throws Exception {
+ for (String id : mCameraIds) {
+ try {
+ openDevice(id);
+ if (!mStaticInfo.isPerFrameControlSupported()) {
+ Log.i(TAG, "Camera " + id + "Doesn't support per frame control");
+ continue;
+ }
+
+ sceneModeTestByCamera();
+ } finally {
+ closeDevice();
+ }
+ }
+ }
+
+ /**
+ * Test effect mode controls.
+ */
+ public void testEffectModes() throws Exception {
+ for (String id : mCameraIds) {
+ try {
+ openDevice(id);
+ if (!mStaticInfo.isPerFrameControlSupported()) {
+ Log.i(TAG, "Camera " + id + "Doesn't support per frame control");
+ continue;
+ }
+
+ effectModeTestByCamera();
+ } finally {
+ closeDevice();
+ }
+ }
+ }
+
+ // TODO: add 3A state machine test.
+
+ private void noiseReductionModeTestByCamera() throws Exception {
+ Size maxPrevSize = mOrderedPreviewSizes.get(0);
+ CaptureRequest.Builder requestBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ int[] availableModes = mStaticInfo.getAvailableNoiseReductionModesChecked();
+ SimpleCaptureListener resultListener = new SimpleCaptureListener();
+ startPreview(requestBuilder, maxPrevSize, resultListener);
+
+ for (int mode : availableModes) {
+ requestBuilder.set(CaptureRequest.NOISE_REDUCTION_MODE, mode);
+ resultListener = new SimpleCaptureListener();
+ mCamera.setRepeatingRequest(requestBuilder.build(), resultListener, mHandler);
+
+ verifyCaptureResultForKey(CaptureResult.NOISE_REDUCTION_MODE, (int)mode,
+ resultListener, NUM_FRAMES_VERIFIED);
+
+ // Test that OFF and FAST mode should not slow down the frame rate.
+ if (mode == CaptureRequest.NOISE_REDUCTION_MODE_OFF ||
+ mode == CaptureRequest.NOISE_REDUCTION_MODE_FAST) {
+ verifyFpsNotSlowDown(requestBuilder, NUM_FRAMES_VERIFIED);
+ }
+ }
+
+ stopPreview();
+ }
+
+ private void focusDistanceTestByCamera() throws Exception {
+ Size maxPrevSize = mOrderedPreviewSizes.get(0);
+ float[] testDistances = getFocusDistanceTestValuesInOrder();
+ CaptureRequest.Builder requestBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ requestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_OFF);
+ SimpleCaptureListener resultListener = new SimpleCaptureListener();
+ startPreview(requestBuilder, maxPrevSize, resultListener);
+
+ CaptureRequest request;
+ float[] resultDistances = new float[testDistances.length];
+ for (int i = 0; i < testDistances.length; i++) {
+ requestBuilder.set(CaptureRequest.LENS_FOCUS_DISTANCE, testDistances[i]);
+ request = requestBuilder.build();
+ resultListener = new SimpleCaptureListener();
+ mCamera.setRepeatingRequest(request, resultListener, mHandler);
+ resultDistances[i] = verifyFocusDistanceControl(testDistances[i], request,
+ resultListener);
+ if (VERBOSE) {
+ Log.v(TAG, "Capture request focus distance: " + testDistances[i] + " result: "
+ + resultDistances[i]);
+ }
+ }
+
+ // Verify the monotonicity
+ mCollector.checkArrayMonotonicityAndNotAllEqual(CameraTestUtils.toObject(resultDistances),
+ /*ascendingOrder*/true);
+
+ // Test hyperfocal distance optionally
+ float hyperFocalDistance = mStaticInfo.getHyperfocalDistanceChecked();
+ if (hyperFocalDistance > 0) {
+ requestBuilder.set(CaptureRequest.LENS_FOCUS_DISTANCE, hyperFocalDistance);
+ request = requestBuilder.build();
+ resultListener = new SimpleCaptureListener();
+ mCamera.setRepeatingRequest(request, resultListener, mHandler);
+
+ // Then wait for the lens.state to be stationary.
+ waitForResultValue(resultListener, CaptureResult.LENS_STATE,
+ CaptureResult.LENS_STATE_STATIONARY, NUM_RESULTS_WAIT_TIMEOUT);
+ // Need get reasonably accurate value.
+ CaptureResult result = resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
+ Float focusDistance = getValueNotNull(result, CaptureResult.LENS_FOCUS_DISTANCE);
+ float errorMargin = FOCUS_DISTANCE_ERROR_PERCENT_UNCALIBRATED;
+ int calibrationStatus = mStaticInfo.getFocusDistanceCalibrationChecked();
+ if (calibrationStatus ==
+ CameraMetadata.LENS_INFO_FOCUS_DISTANCE_CALIBRATION_CALIBRATED) {
+ errorMargin = FOCUS_DISTANCE_ERROR_PERCENT_CALIBRATED;
+ } else if (calibrationStatus ==
+ CameraMetadata.LENS_INFO_FOCUS_DISTANCE_CALIBRATION_APPROXIMATE) {
+ errorMargin = FOCUS_DISTANCE_ERROR_PERCENT_APPROXIMATE;
+ }
+ mCollector.expectInRange("Focus distance for hyper focal should be close enough to" +
+ "requested value", focusDistance,
+ hyperFocalDistance * (1.0f - errorMargin),
+ hyperFocalDistance * (1.0f + errorMargin));
+ }
+ }
+
+ /**
+ * Verify focus distance control.
+ *
+ * @param distance The focus distance requested
+ * @param request The capture request to control the manual focus distance
+ * @param resultListener The capture listener to recieve capture result callbacks
+ * @return the result focus distance
+ */
+ private float verifyFocusDistanceControl(float distance, CaptureRequest request,
+ SimpleCaptureListener resultListener) {
+ // Need make sure the result corresponding to the request is back, then check.
+ CaptureResult result =
+ resultListener.getCaptureResultForRequest(request, NUM_RESULTS_WAIT_TIMEOUT);
+ // Then wait for the lens.state to be stationary.
+ waitForResultValue(resultListener, CaptureResult.LENS_STATE,
+ CaptureResult.LENS_STATE_STATIONARY, NUM_RESULTS_WAIT_TIMEOUT);
+ // Then check the focus distance.
+ result = resultListener.getCaptureResultForRequest(request, NUM_RESULTS_WAIT_TIMEOUT);
+ Float resultDistance = getValueNotNull(result, CaptureResult.LENS_FOCUS_DISTANCE);
+ if (mStaticInfo.getFocusDistanceCalibrationChecked() ==
+ CameraCharacteristics.LENS_INFO_FOCUS_DISTANCE_CALIBRATION_CALIBRATED) {
+ // TODO: what's more to test for CALIBRATED devices?
+ }
+
+ float minValue = 0;
+ float maxValue = mStaticInfo.getMinimumFocusDistanceChecked();
+ mCollector.expectInRange("Result focus distance is out of range",
+ resultDistance, minValue, maxValue);
+
+ return resultDistance;
+ }
+
+ /**
+ * Verify edge mode control results.
+ */
+ private void edgeModesTestByCamera() throws Exception {
+ Size maxPrevSize = mOrderedPreviewSizes.get(0);
+ int[] edgeModes = mStaticInfo.getAvailableEdgeModesChecked();
+ CaptureRequest.Builder requestBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ SimpleCaptureListener resultListener = new SimpleCaptureListener();
+ startPreview(requestBuilder, maxPrevSize, resultListener);
+
+ for (int mode : edgeModes) {
+ requestBuilder.set(CaptureRequest.EDGE_MODE, mode);
+ resultListener = new SimpleCaptureListener();
+ mCamera.setRepeatingRequest(requestBuilder.build(), resultListener, mHandler);
+
+ verifyCaptureResultForKey(CaptureResult.EDGE_MODE, (int)mode, resultListener,
+ NUM_FRAMES_VERIFIED);
+
+ // Test that OFF and FAST mode should not slow down the frame rate.
+ if (mode == CaptureRequest.EDGE_MODE_OFF ||
+ mode == CaptureRequest.EDGE_MODE_FAST) {
+ verifyFpsNotSlowDown(requestBuilder, NUM_FRAMES_VERIFIED);
+ }
+ }
+
+ stopPreview();
+ }
+
+ /**
+ * Test color correction controls.
+ *
+ * <p>Test different color correction modes. For TRANSFORM_MATRIX, only test
+ * the unit gain and identity transform.</p>
+ */
+ private void colorCorrectionTestByCamera() throws Exception {
+ CaptureRequest request;
+ CaptureResult result;
+ Size maxPreviewSz = mOrderedPreviewSizes.get(0); // Max preview size.
+ updatePreviewSurface(maxPreviewSz);
+ CaptureRequest.Builder manualRequestBuilder = createRequestForPreview();
+ CaptureRequest.Builder previewRequestBuilder = createRequestForPreview();
+ SimpleCaptureListener listener = new SimpleCaptureListener();
+
+ startPreview(previewRequestBuilder, maxPreviewSz, listener);
+
+ // Default preview result should give valid color correction metadata.
+ result = listener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
+ validateColorCorrectionResult(result);
+
+ // TRANSFORM_MATRIX mode
+ // Only test unit gain and identity transform
+ RggbChannelVector UNIT_GAIN = new RggbChannelVector(1.0f, 1.0f, 1.0f, 1.0f);
+
+ ColorSpaceTransform IDENTITY_TRANSFORM = new ColorSpaceTransform(
+ new Rational[] {
+ ONE_R, ZERO_R, ZERO_R,
+ ZERO_R, ONE_R, ZERO_R,
+ ZERO_R, ZERO_R, ONE_R
+ });
+
+ manualRequestBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_OFF);
+ manualRequestBuilder.set(CaptureRequest.COLOR_CORRECTION_MODE,
+ CaptureRequest.COLOR_CORRECTION_MODE_TRANSFORM_MATRIX);
+ manualRequestBuilder.set(CaptureRequest.COLOR_CORRECTION_GAINS, UNIT_GAIN);
+ manualRequestBuilder.set(CaptureRequest.COLOR_CORRECTION_TRANSFORM, IDENTITY_TRANSFORM);
+ request = manualRequestBuilder.build();
+ mCamera.capture(request, listener, mHandler);
+ result = listener.getCaptureResultForRequest(request, NUM_RESULTS_WAIT_TIMEOUT);
+ RggbChannelVector gains = result.get(CaptureResult.COLOR_CORRECTION_GAINS);
+ ColorSpaceTransform transform = result.get(CaptureResult.COLOR_CORRECTION_TRANSFORM);
+ validateColorCorrectionResult(result);
+ mCollector.expectEquals("Color correction gain result/request mismatch",
+ UNIT_GAIN, gains);
+ mCollector.expectEquals("Color correction gain result/request mismatch",
+ IDENTITY_TRANSFORM, transform);
+
+ // FAST mode
+ manualRequestBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO);
+ manualRequestBuilder.set(CaptureRequest.COLOR_CORRECTION_MODE,
+ CaptureRequest.COLOR_CORRECTION_MODE_FAST);
+ request = manualRequestBuilder.build();
+ mCamera.capture(request, listener, mHandler);
+ result = listener.getCaptureResultForRequest(request, NUM_RESULTS_WAIT_TIMEOUT);
+ validateColorCorrectionResult(result);
+
+ // HIGH_QUALITY mode
+ manualRequestBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO);
+ manualRequestBuilder.set(CaptureRequest.COLOR_CORRECTION_MODE,
+ CaptureRequest.COLOR_CORRECTION_MODE_FAST);
+ request = manualRequestBuilder.build();
+ mCamera.capture(request, listener, mHandler);
+ result = listener.getCaptureResultForRequest(request, NUM_RESULTS_WAIT_TIMEOUT);
+ validateColorCorrectionResult(result);
+ }
+
+ private void validateColorCorrectionResult(CaptureResult result) {
+ final RggbChannelVector ZERO_GAINS = new RggbChannelVector(0, 0, 0, 0);
+ final int TRANSFORM_SIZE = 9;
+ Rational[] zeroTransform = new Rational[TRANSFORM_SIZE];
+ Arrays.fill(zeroTransform, ZERO_R);
+ final ColorSpaceTransform ZERO_TRANSFORM = new ColorSpaceTransform(zeroTransform);
+
+ RggbChannelVector resultGain;
+ if ((resultGain = mCollector.expectKeyValueNotNull(result,
+ CaptureResult.COLOR_CORRECTION_GAINS)) != null) {
+ mCollector.expectKeyValueNotEquals(result,
+ CaptureResult.COLOR_CORRECTION_GAINS, ZERO_GAINS);
+ }
+
+ ColorSpaceTransform resultTransform;
+ if ((resultTransform = mCollector.expectKeyValueNotNull(result,
+ CaptureResult.COLOR_CORRECTION_TRANSFORM)) != null) {
+ mCollector.expectKeyValueNotEquals(result,
+ CaptureResult.COLOR_CORRECTION_TRANSFORM, ZERO_TRANSFORM);
+ }
+ }
+
+ /**
+ * Test flash mode control by AE mode.
+ * <p>
+ * Only allow AE mode ON or OFF, because other AE mode could run into conflict with
+ * flash manual control. This function expects the camera to already have an active
+ * repeating request and be sending results to the listener.
+ * </p>
+ *
+ * @param listener The Capture listener that is used to wait for capture result
+ * @param aeMode The AE mode for flash to test with
+ */
+ private void flashTestByAeMode(SimpleCaptureListener listener, int aeMode) throws Exception {
+ CaptureRequest request;
+ CaptureResult result;
+ final int NUM_FLASH_REQUESTS_TESTED = 10;
+ CaptureRequest.Builder requestBuilder = createRequestForPreview();
+
+ if (aeMode == CaptureRequest.CONTROL_AE_MODE_ON) {
+ requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, aeMode);
+ } else if (aeMode == CaptureRequest.CONTROL_AE_MODE_OFF) {
+ changeExposure(requestBuilder, DEFAULT_EXP_TIME_NS, DEFAULT_SENSITIVITY);
+ } else {
+ throw new IllegalArgumentException("This test only works when AE mode is ON or OFF");
+ }
+
+ // For camera that doesn't have flash unit, flash state should always be UNAVAILABLE.
+ if (mStaticInfo.getFlashInfoChecked() == false) {
+ for (int i = 0; i < NUM_FLASH_REQUESTS_TESTED; i++) {
+ result = listener.getCaptureResult(CAPTURE_RESULT_TIMEOUT_MS);
+ mCollector.expectEquals("No flash unit available, flash state must be UNAVAILABLE"
+ + "for AE mode " + aeMode, CaptureResult.FLASH_STATE_UNAVAILABLE,
+ result.get(CaptureResult.FLASH_STATE));
+ }
+
+ return;
+ }
+
+ // Test flash SINGLE mode control. Wait for flash state to be READY first.
+ waitForResultValue(listener, CaptureResult.FLASH_STATE, CaptureResult.FLASH_STATE_READY,
+ NUM_RESULTS_WAIT_TIMEOUT);
+ requestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_SINGLE);
+ request = requestBuilder.build();
+ mCamera.capture(request, listener, mHandler);
+ result = listener.getCaptureResultForRequest(request,
+ NUM_RESULTS_WAIT_TIMEOUT);
+ // Result mode must be SINGLE, state must be FIRED.
+ mCollector.expectEquals("Flash mode result must be SINGLE",
+ CaptureResult.FLASH_MODE_SINGLE, result.get(CaptureResult.FLASH_MODE));
+ mCollector.expectEquals("Flash state result must be FIRED",
+ CaptureResult.FLASH_STATE_FIRED, result.get(CaptureResult.FLASH_STATE));
+
+ // Test flash TORCH mode control.
+ CaptureRequest[] requests = new CaptureRequest[NUM_FLASH_REQUESTS_TESTED];
+ requestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH);
+ for (int i = 0; i < NUM_FLASH_REQUESTS_TESTED; i++) {
+ requests[i] = requestBuilder.build();
+ mCamera.capture(requests[i], listener, mHandler);
+ }
+ // Verify the results
+ for (int i = 0; i < NUM_FLASH_REQUESTS_TESTED; i++) {
+ result = listener.getCaptureResultForRequest(requests[i],
+ NUM_RESULTS_WAIT_TIMEOUT);
+
+ // Result mode must be TORCH, state must be FIRED
+ mCollector.expectEquals("Flash mode result must be TORCH",
+ CaptureResult.FLASH_MODE_TORCH, result.get(CaptureResult.FLASH_MODE));
+ mCollector.expectEquals("Flash state result must be FIRED",
+ CaptureResult.FLASH_STATE_FIRED, result.get(CaptureResult.FLASH_STATE));
+ }
+
+ // Test flash OFF mode control
+ requestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);
+ request = requestBuilder.build();
+ mCamera.capture(request, listener, mHandler);
+ result = listener.getCaptureResultForRequest(request,
+ NUM_RESULTS_WAIT_TIMEOUT);
+ mCollector.expectEquals("Flash mode result must be OFF", CaptureResult.FLASH_MODE_OFF,
+ result.get(CaptureResult.FLASH_MODE));
+ }
+
+ private void verifyAntiBandingMode(SimpleCaptureListener listener, int numFramesVerified,
+ int mode, boolean isAeManual, long requestExpTime) throws Exception {
+ // Skip the first a couple of frames as antibanding may not be fully up yet.
+ final int NUM_FRAMES_SKIPPED = 5;
+ for (int i = 0; i < NUM_FRAMES_SKIPPED; i++) {
+ listener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
+ }
+
+ for (int i = 0; i < numFramesVerified; i++) {
+ CaptureResult result = listener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
+ Long resultExpTime = result.get(CaptureResult.SENSOR_EXPOSURE_TIME);
+ assertNotNull("Exposure time shouldn't be null", resultExpTime);
+ Integer flicker = result.get(CaptureResult.STATISTICS_SCENE_FLICKER);
+ // Scene flicker result should be always available.
+ assertNotNull("Scene flicker must not be null", flicker);
+ assertTrue("Scene flicker is invalid", flicker >= STATISTICS_SCENE_FLICKER_NONE &&
+ flicker <= STATISTICS_SCENE_FLICKER_60HZ);
+
+ if (isAeManual) {
+ // First, round down not up, second, need close enough.
+ validateExposureTime(requestExpTime, resultExpTime);
+ return;
+ }
+
+ long expectedExpTime = resultExpTime; // Default, no exposure adjustment.
+ if (mode == CONTROL_AE_ANTIBANDING_MODE_50HZ) {
+ // result exposure time must be adjusted by 50Hz illuminant source.
+ expectedExpTime =
+ getAntiFlickeringExposureTime(ANTI_FLICKERING_50HZ, resultExpTime);
+ } else if (mode == CONTROL_AE_ANTIBANDING_MODE_60HZ) {
+ // result exposure time must be adjusted by 60Hz illuminant source.
+ expectedExpTime =
+ getAntiFlickeringExposureTime(ANTI_FLICKERING_60HZ, resultExpTime);
+ } else if (mode == CONTROL_AE_ANTIBANDING_MODE_AUTO){
+ /**
+ * Use STATISTICS_SCENE_FLICKER to tell the illuminant source
+ * and do the exposure adjustment.
+ */
+ expectedExpTime = resultExpTime;
+ if (flicker == STATISTICS_SCENE_FLICKER_60HZ) {
+ expectedExpTime =
+ getAntiFlickeringExposureTime(ANTI_FLICKERING_60HZ, resultExpTime);
+ } else if (flicker == STATISTICS_SCENE_FLICKER_50HZ) {
+ expectedExpTime =
+ getAntiFlickeringExposureTime(ANTI_FLICKERING_50HZ, resultExpTime);
+ }
+ }
+
+ if (Math.abs(resultExpTime - expectedExpTime) > EXPOSURE_TIME_ERROR_MARGIN_NS) {
+ mCollector.addMessage(String.format("Result exposure time %dns diverges too much"
+ + " from expected exposure time %dns for mode %d when AE is auto",
+ resultExpTime, expectedExpTime, mode));
+ }
+ }
+ }
+
+ private void antiBandingTestByMode(Size size, int mode)
+ throws Exception {
+ if(VERBOSE) {
+ Log.v(TAG, "Anti-banding test for mode " + mode + " for camera " + mCamera.getId());
+ }
+ CaptureRequest.Builder requestBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+
+ requestBuilder.set(CaptureRequest.CONTROL_AE_ANTIBANDING_MODE, mode);
+
+ // Test auto AE mode anti-banding behavior
+ SimpleCaptureListener resultListener = new SimpleCaptureListener();
+ startPreview(requestBuilder, size, resultListener);
+ verifyAntiBandingMode(resultListener, NUM_FRAMES_VERIFIED, mode, /*isAeManual*/false,
+ IGORE_REQUESTED_EXPOSURE_TIME_CHECK);
+
+ // Test manual AE mode anti-banding behavior
+ // 65ms, must be supported by full capability devices.
+ final long TEST_MANUAL_EXP_TIME_NS = 65000000L;
+ long manualExpTime = mStaticInfo.getExposureClampToRange(TEST_MANUAL_EXP_TIME_NS);
+ changeExposure(requestBuilder, manualExpTime);
+ resultListener = new SimpleCaptureListener();
+ startPreview(requestBuilder, size, resultListener);
+ verifyAntiBandingMode(resultListener, NUM_FRAMES_VERIFIED, mode, /*isAeManual*/true,
+ manualExpTime);
+
+ stopPreview();
+ }
+
+ /**
+ * Test the all available AE modes and AE lock.
+ * <p>
+ * For manual AE mode, test iterates through different sensitivities and
+ * exposure times, validate the result exposure time correctness. For
+ * CONTROL_AE_MODE_ON_ALWAYS_FLASH mode, the AE lock and flash are tested.
+ * For the rest of the AUTO mode, AE lock is tested.
+ * </p>
+ *
+ * @param mode
+ */
+ private void aeModeAndLockTestByMode(int mode)
+ throws Exception {
+ switch (mode) {
+ case CONTROL_AE_MODE_OFF:
+ // Test manual exposure control.
+ aeManualControlTest();
+ break;
+ case CONTROL_AE_MODE_ON:
+ case CONTROL_AE_MODE_ON_AUTO_FLASH:
+ case CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE:
+ case CONTROL_AE_MODE_ON_ALWAYS_FLASH:
+ // Test AE lock for above AUTO modes.
+ aeAutoModeTestLock(mode);
+ break;
+ default:
+ throw new UnsupportedOperationException("Unhandled AE mode " + mode);
+ }
+ }
+
+ /**
+ * Test AE auto modes.
+ * <p>
+ * Use single request rather than repeating request to test AE lock per frame control.
+ * </p>
+ */
+ private void aeAutoModeTestLock(int mode) throws Exception {
+ CaptureRequest.Builder requestBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ requestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, false);
+ requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, mode);
+ configurePreviewOutput(requestBuilder);
+
+ final int MAX_NUM_CAPTURES_BEFORE_LOCK = 5;
+ for (int i = 1; i <= MAX_NUM_CAPTURES_BEFORE_LOCK; i++) {
+ autoAeMultipleCapturesThenTestLock(requestBuilder, mode, i);
+ }
+ }
+
+ /**
+ * Issue multiple auto AE captures, then lock AE, validate the AE lock vs.
+ * the last capture result before the AE lock.
+ */
+ private void autoAeMultipleCapturesThenTestLock(
+ CaptureRequest.Builder requestBuilder, int aeMode, int numCapturesBeforeLock)
+ throws Exception {
+ if (numCapturesBeforeLock < 1) {
+ throw new IllegalArgumentException("numCapturesBeforeLock must be no less than 1");
+ }
+ if (VERBOSE) {
+ Log.v(TAG, "Camera " + mCamera.getId() + ": Testing auto AE mode and lock for mode "
+ + aeMode + " with " + numCapturesBeforeLock + " captures before lock");
+ }
+
+ SimpleCaptureListener listener = new SimpleCaptureListener();
+ CaptureResult latestResult = null;
+
+ CaptureRequest request = requestBuilder.build();
+ for (int i = 0; i < numCapturesBeforeLock; i++) {
+ // Fire a capture, auto AE, lock off.
+ mCamera.capture(request, listener, mHandler);
+ }
+ // Then fire a capture to lock the AE,
+ requestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, true);
+ mCamera.capture(requestBuilder.build(), listener, mHandler);
+
+ // Get the latest exposure values of the last AE lock off requests.
+ for (int i = 0; i < numCapturesBeforeLock; i++) {
+ latestResult = listener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
+ }
+ int sensitivity = getValueNotNull(latestResult, CaptureResult.SENSOR_SENSITIVITY);
+ long expTime = getValueNotNull(latestResult, CaptureResult.SENSOR_EXPOSURE_TIME);
+
+ // Get the AE lock on result and validate the exposure values.
+ latestResult = listener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
+ int sensitivityLocked = getValueNotNull(latestResult, CaptureResult.SENSOR_SENSITIVITY);
+ long expTimeLocked = getValueNotNull(latestResult, CaptureResult.SENSOR_EXPOSURE_TIME);
+ mCollector.expectEquals("Locked exposure time shouldn't be changed for AE auto mode "
+ + aeMode + "after " + numCapturesBeforeLock + " captures", expTime, expTimeLocked);
+ mCollector.expectEquals("Locked sensitivity shouldn't be changed for AE auto mode " + aeMode
+ + "after " + numCapturesBeforeLock + " captures", sensitivity, sensitivityLocked);
+ }
+
+ /**
+ * Iterate through exposure times and sensitivities for manual AE control.
+ * <p>
+ * Use single request rather than repeating request to test manual exposure
+ * value change per frame control.
+ * </p>
+ */
+ private void aeManualControlTest()
+ throws Exception {
+ CaptureRequest.Builder requestBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+
+ requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CONTROL_AE_MODE_OFF);
+ configurePreviewOutput(requestBuilder);
+ SimpleCaptureListener listener = new SimpleCaptureListener();
+
+ long[] expTimes = getExposureTimeTestValues();
+ int[] sensitivities = getSensitivityTestValues();
+ // Submit single request at a time, then verify the result.
+ for (int i = 0; i < expTimes.length; i++) {
+ for (int j = 0; j < sensitivities.length; j++) {
+ if (VERBOSE) {
+ Log.v(TAG, "Camera " + mCamera.getId() + ": Testing sensitivity "
+ + sensitivities[j] + ", exposure time " + expTimes[i] + "ns");
+ }
+
+ changeExposure(requestBuilder, expTimes[i], sensitivities[j]);
+ mCamera.capture(requestBuilder.build(), listener, mHandler);
+
+ CaptureResult result = listener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
+ long resultExpTime = getValueNotNull(result, CaptureResult.SENSOR_EXPOSURE_TIME);
+ int resultSensitivity = getValueNotNull(result, CaptureResult.SENSOR_SENSITIVITY);
+ validateExposureTime(expTimes[i], resultExpTime);
+ validateSensitivity(sensitivities[j], resultSensitivity);
+ validateFrameDurationForCapture(result);
+ }
+ }
+ // TODO: Add another case to test where we can submit all requests, then wait for
+ // results, which will hide the pipeline latency. this is not only faster, but also
+ // test high speed per frame control and synchronization.
+ }
+
+
+ /**
+ * Verify black level lock control.
+ */
+ private void verifyBlackLevelLockResults(SimpleCaptureListener listener, int numFramesVerified,
+ int maxLockOffCnt) throws Exception {
+ int noLockCnt = 0;
+ for (int i = 0; i < numFramesVerified; i++) {
+ CaptureResult result = listener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
+ Boolean blackLevelLock = result.get(CaptureResult.BLACK_LEVEL_LOCK);
+ assertNotNull("Black level lock result shouldn't be null", blackLevelLock);
+
+ // Count the lock == false result, which could possibly occur at most once.
+ if (blackLevelLock == false) {
+ noLockCnt++;
+ }
+
+ if(VERBOSE) {
+ Log.v(TAG, "Black level lock result: " + blackLevelLock);
+ }
+ }
+ assertTrue("Black level lock OFF occurs " + noLockCnt + " times, expect at most "
+ + maxLockOffCnt + " for camera " + mCamera.getId(), noLockCnt <= maxLockOffCnt);
+ }
+
+ /**
+ * Verify shading map for different shading modes.
+ */
+ private void verifyShadingMap(SimpleCaptureListener listener, int numFramesVerified,
+ int shadingMode) throws Exception {
+
+ for (int i = 0; i < numFramesVerified; i++) {
+ CaptureResult result = listener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
+ mCollector.expectEquals("Shading mode result doesn't match request",
+ shadingMode, result.get(CaptureResult.SHADING_MODE));
+ LensShadingMap mapObj = result.get(CaptureResult.STATISTICS_LENS_SHADING_CORRECTION_MAP);
+ assertNotNull("Map object must not be null", mapObj);
+ int numElementsInMap = mapObj.getGainFactorCount();
+ float[] map = new float[numElementsInMap];
+ mapObj.copyGainFactors(map, /*offset*/0);
+ assertNotNull("Map must not be null", map);
+ assertFalse(String.format(
+ "Map size %d should be less than %d", numElementsInMap, MAX_SHADING_MAP_SIZE),
+ numElementsInMap >= MAX_SHADING_MAP_SIZE);
+ assertFalse(String.format("Map size %d should be no less than %d", numElementsInMap,
+ MIN_SHADING_MAP_SIZE), numElementsInMap < MIN_SHADING_MAP_SIZE);
+
+ if (shadingMode == CaptureRequest.SHADING_MODE_FAST ||
+ shadingMode == CaptureRequest.SHADING_MODE_HIGH_QUALITY) {
+ // shading mode is FAST or HIGH_QUALITY, expect to receive a map with all
+ // elements >= 1.0f
+
+ int badValueCnt = 0;
+ // Detect the bad values of the map data.
+ for (int j = 0; j < numElementsInMap; j++) {
+ if (Float.isNaN(map[j]) || map[j] < 1.0f) {
+ badValueCnt++;
+ }
+ }
+ assertEquals("Number of value in the map is " + badValueCnt + " out of "
+ + numElementsInMap, /*expected*/0, /*actual*/badValueCnt);
+ } else if (shadingMode == CaptureRequest.SHADING_MODE_OFF) {
+ float[] unityMap = new float[numElementsInMap];
+ Arrays.fill(unityMap, 1.0f);
+ // shading mode is OFF, expect to receive a unity map.
+ assertTrue("Result map " + Arrays.toString(map) + " must be an unity map",
+ Arrays.equals(unityMap, map));
+ }
+ }
+ }
+
+ /**
+ * Test face detection for a camera.
+ */
+ private void faceDetectionTestByCamera() throws Exception {
+ // Can only test full capability because test relies on per frame control
+ // and synchronization.
+ if (!mStaticInfo.isHardwareLevelFull()) {
+ return;
+ }
+ int[] faceDetectModes = mStaticInfo.getAvailableFaceDetectModesChecked();
+
+ SimpleCaptureListener listener;
+ CaptureRequest.Builder requestBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+
+ Size maxPreviewSz = mOrderedPreviewSizes.get(0); // Max preview size.
+ for (int mode : faceDetectModes) {
+ requestBuilder.set(CaptureRequest.STATISTICS_FACE_DETECT_MODE, mode);
+ if (VERBOSE) {
+ Log.v(TAG, "Start testing face detection mode " + mode);
+ }
+
+ // Create a new listener for each run to avoid the results from one run spill
+ // into another run.
+ listener = new SimpleCaptureListener();
+ startPreview(requestBuilder, maxPreviewSz, listener);
+ verifyFaceDetectionResults(listener, NUM_FACE_DETECTION_FRAMES_VERIFIED, mode);
+ }
+
+ stopPreview();
+ }
+
+ /**
+ * Verify face detection results for different face detection modes.
+ *
+ * @param listener The listener to get capture result
+ * @param numFramesVerified Number of results to be verified
+ * @param faceDetectionMode Face detection mode to be verified against
+ */
+ private void verifyFaceDetectionResults(SimpleCaptureListener listener, int numFramesVerified,
+ int faceDetectionMode) {
+ for (int i = 0; i < numFramesVerified; i++) {
+ CaptureResult result = listener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
+ mCollector.expectEquals("Result face detection mode should match the request",
+ faceDetectionMode, result.get(CaptureResult.STATISTICS_FACE_DETECT_MODE));
+
+ Face[] faces = result.get(CaptureResult.STATISTICS_FACES);
+ List<Integer> faceIds = new ArrayList<Integer>(faces.length);
+ List<Integer> faceScores = new ArrayList<Integer>(faces.length);
+ if (faceDetectionMode == CaptureResult.STATISTICS_FACE_DETECT_MODE_OFF) {
+ mCollector.expectEquals("Number of detection faces should always 0 for OFF mode",
+ 0, faces.length);
+ } else if (faceDetectionMode == CaptureResult.STATISTICS_FACE_DETECT_MODE_SIMPLE) {
+ for (Face face : faces) {
+ mCollector.expectNotNull("Face rectangle shouldn't be null", face.getBounds());
+ faceScores.add(face.getScore());
+ mCollector.expectTrue("Face id is expected to be -1 for SIMPLE mode",
+ face.getId() == Face.ID_UNSUPPORTED);
+ }
+ } else if (faceDetectionMode == CaptureResult.STATISTICS_FACE_DETECT_MODE_FULL) {
+ if (VERBOSE) {
+ Log.v(TAG, "Number of faces detected: " + faces.length);
+ }
+
+ for (Face face : faces) {
+ Rect faceBound = null;
+ boolean faceRectAvailable = mCollector.expectTrue("Face rectangle "
+ + "shouldn't be null", face.getBounds() != null);
+ if (!faceRectAvailable) {
+ continue;
+ }
+ faceBound = face.getBounds();
+
+ faceScores.add(face.getScore());
+ faceIds.add(face.getId());
+
+ mCollector.expectTrue("Face id is shouldn't be -1 for FULL mode",
+ face.getId() != Face.ID_UNSUPPORTED);
+ boolean leftEyeAvailable =
+ mCollector.expectTrue("Left eye position shouldn't be null",
+ face.getLeftEyePosition() != null);
+ boolean rightEyeAvailable =
+ mCollector.expectTrue("Right eye position shouldn't be null",
+ face.getRightEyePosition() != null);
+ boolean mouthAvailable =
+ mCollector.expectTrue("Mouth position shouldn't be null",
+ face.getMouthPosition() != null);
+ // Eyes/mouth position should be inside of the face rect.
+ if (leftEyeAvailable) {
+ Point leftEye = face.getLeftEyePosition();
+ mCollector.expectTrue("Left eye " + leftEye.toString() + "should be"
+ + "inside of face rect " + faceBound.toString(),
+ faceBound.contains(leftEye.x, leftEye.y));
+ }
+ if (rightEyeAvailable) {
+ Point rightEye = face.getRightEyePosition();
+ mCollector.expectTrue("Right eye " + rightEye.toString() + "should be"
+ + "inside of face rect " + faceBound.toString(),
+ faceBound.contains(rightEye.x, rightEye.y));
+ }
+ if (mouthAvailable) {
+ Point mouth = face.getMouthPosition();
+ mCollector.expectTrue("Mouth " + mouth.toString() + " should be inside of"
+ + " face rect " + faceBound.toString(),
+ faceBound.contains(mouth.x, mouth.y));
+ }
+ }
+ }
+ mCollector.expectValuesInRange("Face scores are invalid", faceIds,
+ Face.SCORE_MIN, Face.SCORE_MAX);
+ mCollector.expectValuesUnique("Face ids are invalid", faceIds);
+ }
+ }
+
+ /**
+ * Test tone map mode and result by camera
+ */
+ private void toneMapTestByCamera() throws Exception {
+ // Can only test full capability because test relies on per frame control
+ // and synchronization.
+ if (!mStaticInfo.isHardwareLevelFull()) {
+ return;
+ }
+
+ SimpleCaptureListener listener;
+ CaptureRequest.Builder requestBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+
+ Size maxPreviewSz = mOrderedPreviewSizes.get(0); // Max preview size.
+
+ int[] toneMapModes = mStaticInfo.getAvailableToneMapModesChecked();
+ for (int mode : toneMapModes) {
+ requestBuilder.set(CaptureRequest.TONEMAP_MODE, mode);
+ if (VERBOSE) {
+ Log.v(TAG, "Testing tonemap mode " + mode);
+ }
+
+ if (mode == CaptureRequest.TONEMAP_MODE_CONTRAST_CURVE) {
+ TonemapCurve tcLinear = new TonemapCurve(
+ TONEMAP_CURVE_LINEAR, TONEMAP_CURVE_LINEAR, TONEMAP_CURVE_LINEAR);
+ requestBuilder.set(CaptureRequest.TONEMAP_CURVE, tcLinear);
+ // Create a new listener for each run to avoid the results from one run spill
+ // into another run.
+ listener = new SimpleCaptureListener();
+ startPreview(requestBuilder, maxPreviewSz, listener);
+ verifyToneMapModeResults(listener, NUM_FRAMES_VERIFIED, mode,
+ TONEMAP_CURVE_LINEAR);
+
+ TonemapCurve tcSrgb = new TonemapCurve(
+ TONEMAP_CURVE_SRGB, TONEMAP_CURVE_SRGB, TONEMAP_CURVE_SRGB);
+ requestBuilder.set(CaptureRequest.TONEMAP_CURVE, tcSrgb);
+ // Create a new listener for each run to avoid the results from one run spill
+ // into another run.
+ listener = new SimpleCaptureListener();
+ startPreview(requestBuilder, maxPreviewSz, listener);
+ verifyToneMapModeResults(listener, NUM_FRAMES_VERIFIED, mode,
+ TONEMAP_CURVE_SRGB);
+ } else {
+ // Create a new listener for each run to avoid the results from one run spill
+ // into another run.
+ listener = new SimpleCaptureListener();
+ startPreview(requestBuilder, maxPreviewSz, listener);
+ verifyToneMapModeResults(listener, NUM_FRAMES_VERIFIED, mode,
+ /*inputToneCurve*/null);
+ }
+ }
+
+ stopPreview();
+ }
+
+ /**
+ * Verify tonemap results.
+ * <p>
+ * Assumes R,G,B channels use the same tone curve
+ * </p>
+ *
+ * @param listener The capture listener used to get the capture results
+ * @param numFramesVerified Number of results to be verified
+ * @param tonemapMode Tonemap mode to verify
+ * @param inputToneCurve Tonemap curve used by all 3 channels, ignored when
+ * map mode is not CONTRAST_CURVE.
+ */
+ private void verifyToneMapModeResults(SimpleCaptureListener listener, int numFramesVerified,
+ int tonemapMode, float[] inputToneCurve) {
+ final int MIN_TONEMAP_CURVE_POINTS = 2;
+ final Float ZERO = new Float(0);
+ final Float ONE = new Float(1.0f);
+
+ int maxCurvePoints = mStaticInfo.getMaxTonemapCurvePointChecked();
+ for (int i = 0; i < numFramesVerified; i++) {
+ CaptureResult result = listener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
+ mCollector.expectEquals("Capture result tonemap mode should match request", tonemapMode,
+ result.get(CaptureResult.TONEMAP_MODE));
+ TonemapCurve tc = getValueNotNull(result, CaptureResult.TONEMAP_CURVE);
+ int pointCount = tc.getPointCount(TonemapCurve.CHANNEL_RED);
+ float[] mapRed = new float[pointCount * TonemapCurve.POINT_SIZE];
+ pointCount = tc.getPointCount(TonemapCurve.CHANNEL_GREEN);
+ float[] mapGreen = new float[pointCount * TonemapCurve.POINT_SIZE];
+ pointCount = tc.getPointCount(TonemapCurve.CHANNEL_BLUE);
+ float[] mapBlue = new float[pointCount * TonemapCurve.POINT_SIZE];
+ tc.copyColorCurve(TonemapCurve.CHANNEL_RED, mapRed, 0);
+ tc.copyColorCurve(TonemapCurve.CHANNEL_GREEN, mapGreen, 0);
+ tc.copyColorCurve(TonemapCurve.CHANNEL_BLUE, mapBlue, 0);
+ if (tonemapMode == CaptureResult.TONEMAP_MODE_CONTRAST_CURVE) {
+ /**
+ * TODO: need figure out a good way to measure the difference
+ * between request and result, as they may have different array
+ * size.
+ */
+ }
+
+ // Tonemap curve result availability and basic sanity check for all modes.
+ mCollector.expectValuesInRange("Tonemap curve red values are out of range",
+ CameraTestUtils.toObject(mapRed), /*min*/ZERO, /*max*/ONE);
+ mCollector.expectInRange("Tonemap curve red length is out of range",
+ mapRed.length, MIN_TONEMAP_CURVE_POINTS, maxCurvePoints * 2);
+ mCollector.expectValuesInRange("Tonemap curve green values are out of range",
+ CameraTestUtils.toObject(mapGreen), /*min*/ZERO, /*max*/ONE);
+ mCollector.expectInRange("Tonemap curve green length is out of range",
+ mapGreen.length, MIN_TONEMAP_CURVE_POINTS, maxCurvePoints * 2);
+ mCollector.expectValuesInRange("Tonemap curve blue values are out of range",
+ CameraTestUtils.toObject(mapBlue), /*min*/ZERO, /*max*/ONE);
+ mCollector.expectInRange("Tonemap curve blue length is out of range",
+ mapBlue.length, MIN_TONEMAP_CURVE_POINTS, maxCurvePoints * 2);
+ }
+ }
+
+ /**
+ * Test awb mode control.
+ * <p>
+ * Test each supported AWB mode, verify the AWB mode in capture result
+ * matches request. When AWB is locked, the color correction gains and
+ * transform should remain unchanged.
+ * </p>
+ */
+ private void awbModeAndLockTestByCamera() throws Exception {
+ int[] awbModes = mStaticInfo.getAwbAvailableModesChecked();
+ Size maxPreviewSize = mOrderedPreviewSizes.get(0);
+ CaptureRequest.Builder requestBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ startPreview(requestBuilder, maxPreviewSize, /*listener*/null);
+
+ for (int mode : awbModes) {
+ SimpleCaptureListener listener;
+ requestBuilder.set(CaptureRequest.CONTROL_AWB_MODE, mode);
+ listener = new SimpleCaptureListener();
+ mCamera.setRepeatingRequest(requestBuilder.build(), listener, mHandler);
+
+ // Verify AWB mode in capture result.
+ verifyCaptureResultForKey(CaptureResult.CONTROL_AWB_MODE, mode, listener,
+ NUM_FRAMES_VERIFIED);
+
+ // Verify color correction transform and gains stay unchanged after a lock.
+ requestBuilder.set(CaptureRequest.CONTROL_AWB_LOCK, true);
+ listener = new SimpleCaptureListener();
+ mCamera.setRepeatingRequest(requestBuilder.build(), listener, mHandler);
+ waitForResultValue(listener, CaptureResult.CONTROL_AWB_STATE,
+ CaptureResult.CONTROL_AWB_STATE_LOCKED, NUM_RESULTS_WAIT_TIMEOUT);
+ verifyAwbCaptureResultUnchanged(listener, NUM_FRAMES_VERIFIED);
+ }
+ }
+
+ private void verifyAwbCaptureResultUnchanged(SimpleCaptureListener listener,
+ int numFramesVerified) {
+ CaptureResult result = listener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
+ RggbChannelVector lockedGains =
+ getValueNotNull(result, CaptureResult.COLOR_CORRECTION_GAINS);
+ ColorSpaceTransform lockedTransform =
+ getValueNotNull(result, CaptureResult.COLOR_CORRECTION_TRANSFORM);
+
+ for (int i = 0; i < numFramesVerified; i++) {
+ result = listener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
+ validateColorCorrectionResult(result);
+
+ RggbChannelVector gains = getValueNotNull(result, CaptureResult.COLOR_CORRECTION_GAINS);
+ ColorSpaceTransform transform =
+ getValueNotNull(result, CaptureResult.COLOR_CORRECTION_TRANSFORM);
+ mCollector.expectEquals("Color correction gains should remain unchanged after awb lock",
+ lockedGains, gains);
+ mCollector.expectEquals("Color correction transform should remain unchanged after"
+ + " awb lock", lockedTransform, transform);
+ }
+ }
+
+ /**
+ * Test AF mode control.
+ * <p>
+ * Test all supported AF modes, verify the AF mode in capture result matches
+ * request. When AF mode is one of the CONTROL_AF_MODE_CONTINUOUS_* mode,
+ * verify if the AF can converge to PASSIVE_FOCUSED or PASSIVE_UNFOCUSED
+ * state within certain amount of frames.
+ * </p>
+ */
+ private void afModeTestByCamera() throws Exception {
+ int[] afModes = mStaticInfo.getAfAvailableModesChecked();
+ Size maxPreviewSize = mOrderedPreviewSizes.get(0);
+ CaptureRequest.Builder requestBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ startPreview(requestBuilder, maxPreviewSize, /*listener*/null);
+
+ for (int mode : afModes) {
+ SimpleCaptureListener listener;
+ requestBuilder.set(CaptureRequest.CONTROL_AF_MODE, mode);
+ listener = new SimpleCaptureListener();
+ mCamera.setRepeatingRequest(requestBuilder.build(), listener, mHandler);
+
+ // Verify AF mode in capture result.
+ verifyCaptureResultForKey(CaptureResult.CONTROL_AF_MODE, mode, listener,
+ NUM_FRAMES_VERIFIED);
+
+ // Verify AF can finish a scan for CONTROL_AF_MODE_CONTINUOUS_* modes
+ if (mode == CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE ||
+ mode == CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_VIDEO) {
+ List<Integer> afStateList = new ArrayList<Integer>();
+ afStateList.add(CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED);
+ afStateList.add(CaptureResult.CONTROL_AF_STATE_PASSIVE_UNFOCUSED);
+ waitForAnyResultValue(listener, CaptureResult.CONTROL_AF_STATE, afStateList,
+ NUM_RESULTS_WAIT_TIMEOUT);
+ }
+ }
+ }
+
+ /**
+ * Test video and optical stabilizations if they are supported by a given camera.
+ */
+ private void stabilizationTestByCamera() throws Exception {
+ // video stabilization test.
+ int[] videoStabModes = mStaticInfo.getAvailableVideoStabilizationModesChecked();
+ int[] opticalStabModes = mStaticInfo.getAvailableOpticalStabilizationChecked();
+ Size maxPreviewSize = mOrderedPreviewSizes.get(0);
+ CaptureRequest.Builder requestBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ SimpleCaptureListener listener = new SimpleCaptureListener();
+ startPreview(requestBuilder, maxPreviewSize, listener);
+
+ for (int mode : videoStabModes) {
+ listener = new SimpleCaptureListener();
+ requestBuilder.set(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE, mode);
+ mCamera.setRepeatingRequest(requestBuilder.build(), listener, mHandler);
+ // TODO: enable below code when b/14059883 is fixed.
+ /*
+ verifyCaptureResultForKey(CaptureResult.CONTROL_VIDEO_STABILIZATION_MODE, (int)mode,
+ listener, NUM_FRAMES_VERIFIED);
+ */
+ }
+
+ for (int mode : opticalStabModes) {
+ listener = new SimpleCaptureListener();
+ requestBuilder.set(CaptureRequest.LENS_OPTICAL_STABILIZATION_MODE, mode);
+ mCamera.setRepeatingRequest(requestBuilder.build(), listener, mHandler);
+ verifyCaptureResultForKey(CaptureResult.LENS_OPTICAL_STABILIZATION_MODE, mode,
+ listener, NUM_FRAMES_VERIFIED);
+ }
+
+ stopPreview();
+ }
+
+ private void digitalZoomTestByCamera() throws Exception {
+ final int ZOOM_STEPS = 30;
+ final PointF[] TEST_ZOOM_CENTERS = new PointF[] {
+ new PointF(0.5f, 0.5f), // Center point
+ new PointF(0.25f, 0.25f), // top left corner zoom, minimal zoom: 2x
+ new PointF(0.75f, 0.25f), // top right corner zoom, minimal zoom: 2x
+ new PointF(0.25f, 0.75f), // bottom left corner zoom, minimal zoom: 2x
+ new PointF(0.75f, 0.75f), // bottom right corner zoom, minimal zoom: 2x
+ };
+ final float maxZoom = mStaticInfo.getAvailableMaxDigitalZoomChecked();
+ final Rect activeArraySize = mStaticInfo.getActiveArraySizeChecked();
+ Rect[] cropRegions = new Rect[ZOOM_STEPS];
+ MeteringRectangle[][] expectRegions = new MeteringRectangle[ZOOM_STEPS][];
+ Size maxPreviewSize = mOrderedPreviewSizes.get(0);
+ CaptureRequest.Builder requestBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ SimpleCaptureListener listener = new SimpleCaptureListener();
+ startPreview(requestBuilder, maxPreviewSize, listener);
+ CaptureRequest[] requests = new CaptureRequest[ZOOM_STEPS];
+
+ // Set algorithm regions to full active region
+ // TODO: test more different 3A regions
+ final MeteringRectangle[] defaultMeteringRect = new MeteringRectangle[] {
+ new MeteringRectangle (
+ 0, 0, activeArraySize.width(), activeArraySize.height(), 1)};
+
+ for (int algo = 0; algo < NUM_ALGORITHMS; algo++) {
+ update3aRegion(requestBuilder, algo, defaultMeteringRect);
+ }
+
+ for (PointF center : TEST_ZOOM_CENTERS) {
+ for (int i = 0; i < ZOOM_STEPS; i++) {
+ float zoomFactor = (float) (1.0f + (maxZoom - 1.0) * i / ZOOM_STEPS);
+ cropRegions[i] = getCropRegionForZoom(zoomFactor, center, maxZoom, activeArraySize);
+ if (VERBOSE) {
+ Log.v(TAG, "Testing Zoom for factor " + zoomFactor + " and center " +
+ center.toString() + " The cropRegion is " + cropRegions[i].toString());
+ }
+ requestBuilder.set(CaptureRequest.SCALER_CROP_REGION, cropRegions[i]);
+ requests[i] = requestBuilder.build();
+ expectRegions[i] = getExpectedOutputRegion(
+ /*requestRegion*/defaultMeteringRect,
+ /*cropRect*/ cropRegions[i]);
+ mCamera.capture(requests[i], listener, mHandler);
+ }
+
+ // Validate capture result
+ CaptureResult result;
+ for (int i = 0; i < ZOOM_STEPS; i++) {
+ result = listener.getCaptureResultForRequest(
+ requests[i], NUM_RESULTS_WAIT_TIMEOUT);
+ Rect cropRegion = getValueNotNull(result, CaptureResult.SCALER_CROP_REGION);
+ // Verify Output 3A region is intersection of input 3A region and crop region
+ for (int algo = 0; algo < NUM_ALGORITHMS; algo++) {
+ validate3aRegion(result, algo, expectRegions[i]);
+ }
+ mCollector.expectEquals(" Request and result crop region should match",
+ cropRegions[i], cropRegion);
+ }
+ }
+
+ stopPreview();
+ }
+
+ private void sceneModeTestByCamera() throws Exception {
+ int[] sceneModes = mStaticInfo.getAvailableSceneModesChecked();
+ Size maxPreviewSize = mOrderedPreviewSizes.get(0);
+ CaptureRequest.Builder requestBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ SimpleCaptureListener listener = new SimpleCaptureListener();
+ requestBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_USE_SCENE_MODE);
+ startPreview(requestBuilder, maxPreviewSize, listener);
+
+ for(int mode : sceneModes) {
+ requestBuilder.set(CaptureRequest.CONTROL_SCENE_MODE, mode);
+ listener = new SimpleCaptureListener();
+ mCamera.setRepeatingRequest(requestBuilder.build(), listener, mHandler);
+ // Enable below check when b/14059883 is fixed.
+ /*
+ verifyCaptureResultForKey(CaptureResult.CONTROL_SCENE_MODE,
+ CaptureRequest.CONTROL_SCENE_MODE, listener, NUM_FRAMES_VERIFIED);
+ */
+ // This also serves as purpose of showing preview for NUM_FRAMES_VERIFIED
+ verifyCaptureResultForKey(CaptureResult.CONTROL_MODE,
+ CaptureRequest.CONTROL_MODE_USE_SCENE_MODE, listener, NUM_FRAMES_VERIFIED);
+ }
+ }
+
+ private void effectModeTestByCamera() throws Exception {
+ int[] effectModes = mStaticInfo.getAvailableEffectModesChecked();
+ Size maxPreviewSize = mOrderedPreviewSizes.get(0);
+ CaptureRequest.Builder requestBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ requestBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO);
+ SimpleCaptureListener listener = new SimpleCaptureListener();
+ startPreview(requestBuilder, maxPreviewSize, listener);
+
+ for(int mode : effectModes) {
+ requestBuilder.set(CaptureRequest.CONTROL_EFFECT_MODE, mode);
+ listener = new SimpleCaptureListener();
+ mCamera.setRepeatingRequest(requestBuilder.build(), listener, mHandler);
+ // Enable below check when b/14059883 is fixed.
+ /*
+ verifyCaptureResultForKey(CaptureResult.CONTROL_SCENE_MODE,
+ CaptureRequest.CONTROL_SCENE_MODE, listener, NUM_FRAMES_VERIFIED);
+ */
+ // This also serves as purpose of showing preview for NUM_FRAMES_VERIFIED
+ verifyCaptureResultForKey(CaptureResult.CONTROL_MODE,
+ CaptureRequest.CONTROL_MODE_AUTO, listener, NUM_FRAMES_VERIFIED);
+ }
+ }
+
+ //----------------------------------------------------------------
+ //---------Below are common functions for all tests.--------------
+ //----------------------------------------------------------------
+
+ /**
+ * Enable exposure manual control and change exposure and sensitivity and
+ * clamp the value into the supported range.
+ */
+ private void changeExposure(CaptureRequest.Builder requestBuilder,
+ long expTime, int sensitivity) {
+ // Check if the max analog sensitivity is available and no larger than max sensitivity.
+ // The max analog sensitivity is not actually used here. This is only an extra sanity check.
+ mStaticInfo.getMaxAnalogSensitivityChecked();
+
+ expTime = mStaticInfo.getExposureClampToRange(expTime);
+ sensitivity = mStaticInfo.getSensitivityClampToRange(sensitivity);
+
+ requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CONTROL_AE_MODE_OFF);
+ requestBuilder.set(CaptureRequest.SENSOR_EXPOSURE_TIME, expTime);
+ requestBuilder.set(CaptureRequest.SENSOR_SENSITIVITY, sensitivity);
+ }
+ /**
+ * Enable exposure manual control and change exposure time and
+ * clamp the value into the supported range.
+ *
+ * <p>The sensitivity is set to default value.</p>
+ */
+ private void changeExposure(CaptureRequest.Builder requestBuilder, long expTime) {
+ changeExposure(requestBuilder, expTime, DEFAULT_SENSITIVITY);
+ }
+
+ /**
+ * Get the exposure time array that contains multiple exposure time steps in
+ * the exposure time range.
+ */
+ private long[] getExposureTimeTestValues() {
+ long[] testValues = new long[DEFAULT_NUM_EXPOSURE_TIME_STEPS + 1];
+ long maxExpTime = mStaticInfo.getExposureMaximumOrDefault(DEFAULT_EXP_TIME_NS);
+ long minxExpTime = mStaticInfo.getExposureMinimumOrDefault(DEFAULT_EXP_TIME_NS);
+
+ long range = maxExpTime - minxExpTime;
+ double stepSize = range / (double)DEFAULT_NUM_EXPOSURE_TIME_STEPS;
+ for (int i = 0; i < testValues.length; i++) {
+ testValues[i] = minxExpTime + (long)(stepSize * i);
+ testValues[i] = mStaticInfo.getExposureClampToRange(testValues[i]);
+ }
+
+ return testValues;
+ }
+
+ /**
+ * Generate test focus distances in range of [0, minFocusDistance] in increasing order.
+ */
+ private float[] getFocusDistanceTestValuesInOrder() {
+ float[] testValues = new float[NUM_TEST_FOCUS_DISTANCES + 1];
+ float minValue = 0;
+ float maxValue = mStaticInfo.getMinimumFocusDistanceChecked();
+
+ float range = maxValue - minValue;
+ float stepSize = range / NUM_TEST_FOCUS_DISTANCES;
+ for (int i = 0; i < testValues.length; i++) {
+ testValues[i] = minValue + stepSize * i;
+ }
+
+ return testValues;
+ }
+
+ /**
+ * Get the sensitivity array that contains multiple sensitivity steps in the
+ * sensitivity range.
+ * <p>
+ * Sensitivity number of test values is determined by
+ * {@value #DEFAULT_SENSITIVITY_STEP_SIZE} and sensitivity range, and
+ * bounded by {@value #DEFAULT_NUM_SENSITIVITY_STEPS}.
+ * </p>
+ */
+ private int[] getSensitivityTestValues() {
+ int maxSensitivity = mStaticInfo.getSensitivityMaximumOrDefault(
+ DEFAULT_SENSITIVITY);
+ int minSensitivity = mStaticInfo.getSensitivityMinimumOrDefault(
+ DEFAULT_SENSITIVITY);
+
+ int range = maxSensitivity - minSensitivity;
+ int stepSize = DEFAULT_SENSITIVITY_STEP_SIZE;
+ int numSteps = range / stepSize;
+ // Bound the test steps to avoid supper long test.
+ if (numSteps > DEFAULT_NUM_SENSITIVITY_STEPS) {
+ numSteps = DEFAULT_NUM_SENSITIVITY_STEPS;
+ stepSize = range / numSteps;
+ }
+ int[] testValues = new int[numSteps + 1];
+ for (int i = 0; i < testValues.length; i++) {
+ testValues[i] = minSensitivity + stepSize * i;
+ testValues[i] = mStaticInfo.getSensitivityClampToRange(testValues[i]);
+ }
+
+ return testValues;
+ }
+
+ /**
+ * Validate the AE manual control exposure time.
+ *
+ * <p>Exposure should be close enough, and only round down if they are not equal.</p>
+ *
+ * @param request Request exposure time
+ * @param result Result exposure time
+ */
+ private void validateExposureTime(long request, long result) {
+ long expTimeDelta = request - result;
+ // First, round down not up, second, need close enough.
+ mCollector.expectTrue("Exposture time is invalid for AE manaul control test, request: "
+ + request + " result: " + result,
+ expTimeDelta < EXPOSURE_TIME_ERROR_MARGIN_NS && expTimeDelta >= 0);
+ }
+
+ /**
+ * Validate AE manual control sensitivity.
+ *
+ * @param request Request sensitivity
+ * @param result Result sensitivity
+ */
+ private void validateSensitivity(int request, int result) {
+ int sensitivityDelta = request - result;
+ // First, round down not up, second, need close enough.
+ mCollector.expectTrue("Sensitivity is invalid for AE manaul control test, request: "
+ + request + " result: " + result,
+ sensitivityDelta < SENSITIVITY_ERROR_MARGIN && sensitivityDelta >= 0);
+ }
+
+ /**
+ * Validate frame duration for a given capture.
+ *
+ * <p>Frame duration should be longer than exposure time.</p>
+ *
+ * @param result The capture result for a given capture
+ */
+ private void validateFrameDurationForCapture(CaptureResult result) {
+ long expTime = getValueNotNull(result, CaptureResult.SENSOR_EXPOSURE_TIME);
+ long frameDuration = getValueNotNull(result, CaptureResult.SENSOR_FRAME_DURATION);
+ if (VERBOSE) {
+ Log.v(TAG, "frame duration: " + frameDuration + " Exposure time: " + expTime);
+ }
+
+ mCollector.expectTrue(String.format("Frame duration (%d) should be longer than exposure"
+ + " time (%d) for a given capture", frameDuration, expTime),
+ frameDuration >= expTime);
+
+ validatePipelineDepth(result);
+ }
+
+ private <T> T getValueNotNull(CaptureResult result, CaptureResult.Key<T> key) {
+ T value = result.get(key);
+ assertNotNull("Value of Key " + key.getName() + " shouldn't be null", value);
+ return value;
+ }
+
+ /**
+ * Basic verification for the control mode capture result.
+ *
+ * @param key The capture result key to be verified against
+ * @param requestMode The request mode for this result
+ * @param listener The capture listener to get capture results
+ * @param numFramesVerified The number of capture results to be verified
+ */
+ private <T> void verifyCaptureResultForKey(CaptureResult.Key<T> key, T requestMode,
+ SimpleCaptureListener listener, int numFramesVerified) {
+ for (int i = 0; i < numFramesVerified; i++) {
+ CaptureResult result = listener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
+ validatePipelineDepth(result);
+ T resultMode = getValueNotNull(result, key);
+ if (VERBOSE) {
+ Log.v(TAG, "Expect value: " + requestMode.toString() + " result value: "
+ + resultMode.toString());
+ }
+ mCollector.expectEquals("Key " + key.getName() + " result should match request",
+ requestMode, resultMode);
+ }
+ }
+
+ /**
+ * Verify if the fps is slow down for given input request with certain
+ * controls inside.
+ * <p>
+ * This method selects a max preview size for each fps range, and then
+ * configure the preview stream. Preview is started with the max preview
+ * size, and then verify if the result frame duration is in the frame
+ * duration range.
+ * </p>
+ *
+ * @param requestBuilder The request builder that contains post-processing
+ * controls that could impact the output frame rate, such as
+ * {@link CaptureRequest.NOISE_REDUCTION_MODE}. The value of
+ * these controls must be set to some values such that the frame
+ * rate is not slow down.
+ * @param numFramesVerified The number of frames to be verified
+ */
+ private void verifyFpsNotSlowDown(CaptureRequest.Builder requestBuilder,
+ int numFramesVerified) throws Exception {
+ Range<Integer>[] fpsRanges = mStaticInfo.getAeAvailableTargetFpsRangesChecked();
+ boolean antiBandingOffIsSupported = mStaticInfo.isAntiBandingOffModeSupported();
+ Range<Integer> fpsRange;
+ SimpleCaptureListener resultListener;
+
+ for (int i = 0; i < fpsRanges.length; i += 1) {
+ fpsRange = fpsRanges[i];
+ Size previewSz = getMaxPreviewSizeForFpsRange(fpsRange);
+ // If unable to find a preview size, then log the failure, and skip this run.
+ if (!mCollector.expectTrue(String.format(
+ "Unable to find a preview size supporting given fps range %s",
+ fpsRange), previewSz != null)) {
+ continue;
+ }
+
+ if (VERBOSE) {
+ Log.v(TAG, String.format("Test fps range %s for preview size %s",
+ fpsRange, previewSz.toString()));
+ }
+ requestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange);
+ // Turn off auto antibanding to avoid exposure time and frame duration interference
+ // from antibanding algorithm.
+ if (antiBandingOffIsSupported) {
+ requestBuilder.set(CaptureRequest.CONTROL_AE_ANTIBANDING_MODE,
+ CaptureRequest.CONTROL_AE_ANTIBANDING_MODE_OFF);
+ } else {
+ // The device doesn't implement the OFF mode, test continues. It need make sure
+ // that the antibanding algorithm doesn't slow down the fps.
+ Log.i(TAG, "OFF antibanding mode is not supported, the camera device output must" +
+ " not slow down the frame rate regardless of its current antibanding" +
+ " mode");
+ }
+
+ resultListener = new SimpleCaptureListener();
+ startPreview(requestBuilder, previewSz, resultListener);
+ long[] frameDurationRange =
+ new long[]{(long) (1e9 / fpsRange.getUpper()), (long) (1e9 / fpsRange.getLower())};
+ for (int j = 0; j < numFramesVerified; j++) {
+ CaptureResult result =
+ resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
+ validatePipelineDepth(result);
+ long frameDuration = getValueNotNull(result, CaptureResult.SENSOR_FRAME_DURATION);
+ mCollector.expectInRange(
+ "Frame duration must be in the range of " + Arrays.toString(frameDurationRange),
+ frameDuration,
+ (long) (frameDurationRange[0] * (1 - FRAME_DURATION_ERROR_MARGIN)),
+ (long) (frameDurationRange[1] * (1 + FRAME_DURATION_ERROR_MARGIN)));
+ }
+ }
+
+ mCamera.stopRepeating();
+ }
+
+ /**
+ * Validate the pipeline depth result.
+ *
+ * @param result The capture result to get pipeline depth data
+ */
+ private void validatePipelineDepth(CaptureResult result) {
+ final byte MIN_PIPELINE_DEPTH = 1;
+ byte maxPipelineDepth = mStaticInfo.getPipelineMaxDepthChecked();
+ Byte pipelineDepth = getValueNotNull(result, CaptureResult.REQUEST_PIPELINE_DEPTH);
+ mCollector.expectInRange(String.format("Pipeline depth must be in the range of [%d, %d]",
+ MIN_PIPELINE_DEPTH, maxPipelineDepth), pipelineDepth, MIN_PIPELINE_DEPTH,
+ maxPipelineDepth);
+ }
+
+ /**
+ * Calculate the anti-flickering corrected exposure time.
+ * <p>
+ * If the input exposure time is very short (shorter than flickering
+ * boundary), which indicate the scene is bright and very likely at outdoor
+ * environment, skip the correction, as it doesn't make much sense by doing so.
+ * </p>
+ * <p>
+ * For long exposure time (larger than the flickering boundary), find the
+ * exposure time that is closest to the flickering boundary.
+ * </p>
+ *
+ * @param flickeringMode The flickering mode
+ * @param exposureTime The input exposureTime to be corrected
+ * @return anti-flickering corrected exposure time
+ */
+ private long getAntiFlickeringExposureTime(int flickeringMode, long exposureTime) {
+ if (flickeringMode != ANTI_FLICKERING_50HZ && flickeringMode != ANTI_FLICKERING_60HZ) {
+ throw new IllegalArgumentException("Input anti-flickering mode must be 50 or 60Hz");
+ }
+ long flickeringBoundary = EXPOSURE_TIME_BOUNDARY_50HZ_NS;
+ if (flickeringMode == ANTI_FLICKERING_60HZ) {
+ flickeringBoundary = EXPOSURE_TIME_BOUNDARY_60HZ_NS;
+ }
+
+ if (exposureTime <= flickeringBoundary) {
+ return exposureTime;
+ }
+
+ // Find the closest anti-flickering corrected exposure time
+ long correctedExpTime = exposureTime + (flickeringBoundary / 2);
+ correctedExpTime = correctedExpTime - (correctedExpTime % flickeringBoundary);
+ return correctedExpTime;
+ }
+
+ /**
+ * Update one 3A region in capture request builder if that region is supported. Do nothing
+ * if the specified 3A region is not supported by camera device.
+ * @param requestBuilder The request to be updated
+ * @param algoIdx The index to the algorithm. (AE: 0, AWB: 1, AF: 2)
+ * @param regions The 3A regions to be set
+ */
+ private void update3aRegion(
+ CaptureRequest.Builder requestBuilder, int algoIdx, MeteringRectangle[] regions)
+ {
+ int[] maxRegions = mStaticInfo.get3aMaxRegionsChecked();
+
+ if (regions == null || regions.length == 0) {
+ throw new IllegalArgumentException("Invalid input 3A region!");
+ }
+
+ if (maxRegions[algoIdx] >= regions.length)
+ {
+ switch (algoIdx) {
+ case INDEX_ALGORITHM_AE:
+ requestBuilder.set(CaptureRequest.CONTROL_AE_REGIONS, regions);
+ break;
+ case INDEX_ALGORITHM_AWB:
+ requestBuilder.set(CaptureRequest.CONTROL_AWB_REGIONS, regions);
+ break;
+ case INDEX_ALGORITHM_AF:
+ requestBuilder.set(CaptureRequest.CONTROL_AF_REGIONS, regions);
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown 3A Algorithm!");
+ }
+ }
+ }
+
+ /**
+ * Validate one 3A region in capture result equals to expected region if that region is
+ * supported. Do nothing if the specified 3A region is not supported by camera device.
+ * @param result The capture result to be validated
+ * @param algoIdx The index to the algorithm. (AE: 0, AWB: 1, AF: 2)
+ * @param expectRegion The 3A regions expected in capture result
+ */
+ private void validate3aRegion(
+ CaptureResult result, int algoIdx, MeteringRectangle[] expectRegion)
+ {
+ int[] maxRegions = mStaticInfo.get3aMaxRegionsChecked();
+ MeteringRectangle[] actualRegion;
+
+ if (maxRegions[algoIdx] > 0)
+ {
+ switch (algoIdx) {
+ case INDEX_ALGORITHM_AE:
+ actualRegion = getValueNotNull(result, CaptureResult.CONTROL_AE_REGIONS);
+ break;
+ case INDEX_ALGORITHM_AWB:
+ actualRegion = getValueNotNull(result, CaptureResult.CONTROL_AWB_REGIONS);
+ break;
+ case INDEX_ALGORITHM_AF:
+ actualRegion = getValueNotNull(result, CaptureResult.CONTROL_AF_REGIONS);
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown 3A Algorithm!");
+ }
+ mCollector.expectEquals(
+ "Expected 3A region: " + Arrays.toString(expectRegion) +
+ " does not match actual one: " + Arrays.toString(actualRegion),
+ expectRegion, actualRegion);
+ }
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureResultTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureResultTest.java
new file mode 100644
index 0000000..dd7439f
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureResultTest.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.camera2.cts;
+
+import android.content.Context;
+import android.graphics.ImageFormat;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.util.Size;
+import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
+
+import static android.hardware.camera2.cts.CameraTestUtils.*;
+
+import android.util.Log;
+import android.view.Surface;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class CaptureResultTest extends Camera2AndroidTestCase {
+ private static final String TAG = "CaptureResultTest";
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+ private static final int MAX_NUM_IMAGES = 5;
+ private static final int NUM_FRAMES_VERIFIED = 30;
+ private static final long WAIT_FOR_RESULT_TIMEOUT_MS = 3000;
+
+ // List that includes all public keys from CaptureResult
+ List<CaptureResult.Key<?>> mAllKeys;
+
+ // List tracking the failed test keys.
+
+ @Override
+ public void setContext(Context context) {
+ mAllKeys = getAllCaptureResultKeys();
+ super.setContext(context);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ /**
+ * <p>
+ * Basic non-null check test for multiple capture results.
+ * </p>
+ * <p>
+ * When capturing many frames, some camera devices may return some results that have null keys
+ * randomly, which is an API violation and could cause application crash randomly. This test
+ * runs a typical flexible yuv capture many times, and checks if there is any null entries in
+ * a capture result.
+ * </p>
+ */
+ public void testCameraCaptureResultAllKeys() throws Exception {
+ /**
+ * Hardcode a key waiver list for the keys that are allowed to be null.
+ * FIXME: We need get ride of this list, see bug 11116270.
+ */
+ List<CaptureResult.Key<?>> waiverkeys = new ArrayList<CaptureResult.Key<?>>();
+ waiverkeys.add(CaptureResult.JPEG_GPS_COORDINATES);
+ waiverkeys.add(CaptureResult.JPEG_GPS_PROCESSING_METHOD);
+ waiverkeys.add(CaptureResult.JPEG_GPS_TIMESTAMP);
+ waiverkeys.add(CaptureResult.JPEG_ORIENTATION);
+ waiverkeys.add(CaptureResult.JPEG_QUALITY);
+ waiverkeys.add(CaptureResult.JPEG_THUMBNAIL_QUALITY);
+ waiverkeys.add(CaptureResult.JPEG_THUMBNAIL_SIZE);
+
+ for (String id : mCameraIds) {
+ try {
+ openDevice(id);
+ if (!mStaticInfo.isHardwareLevelFull()) {
+ Log.i(TAG, "Camera " + id + " is not a full mode device, skip the test");
+ continue;
+ }
+ // TODO: check for LIMITED keys
+
+ // Create image reader and surface.
+ Size size = mOrderedPreviewSizes.get(0);
+ createDefaultImageReader(size, ImageFormat.YUV_420_888, MAX_NUM_IMAGES,
+ new ImageDropperListener());
+
+ // Configure output streams.
+ List<Surface> outputSurfaces = new ArrayList<Surface>(1);
+ outputSurfaces.add(mReaderSurface);
+ configureCameraOutputs(mCamera, outputSurfaces, mCameraListener);;
+
+ CaptureRequest.Builder requestBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ assertNotNull("Failed to create capture request", requestBuilder);
+ requestBuilder.addTarget(mReaderSurface);
+
+ // Enable face detection if supported
+ int[] faceModes = mStaticInfo.getAvailableFaceDetectModesChecked();
+ for (int i = 0; i < faceModes.length; i++) {
+ if (faceModes[i] == CameraMetadata.STATISTICS_FACE_DETECT_MODE_FULL) {
+ if (VERBOSE) {
+ Log.v(TAG, "testCameraCaptureResultAllKeys - " +
+ "setting facedetection mode to full");
+ }
+ requestBuilder.set(CaptureRequest.STATISTICS_FACE_DETECT_MODE,
+ (int)faceModes[i]);
+ }
+ }
+
+ // Enable lensShading mode, it should be supported by full mode device.
+ requestBuilder.set(CaptureRequest.STATISTICS_LENS_SHADING_MAP_MODE,
+ CameraMetadata.STATISTICS_LENS_SHADING_MAP_MODE_ON);
+
+ // Start capture
+ SimpleCaptureListener captureListener = new SimpleCaptureListener();
+ startCapture(requestBuilder.build(), /*repeating*/true, captureListener, mHandler);
+
+ // Verify results
+ validateCaptureResult(captureListener, waiverkeys, requestBuilder,
+ NUM_FRAMES_VERIFIED);
+
+ stopCapture(/*fast*/false);
+ } finally {
+ closeDevice(id);
+ closeDefaultImageReader();
+ }
+ }
+ }
+
+ private void validateCaptureResult(SimpleCaptureListener captureListener,
+ List<CaptureResult.Key<?>> skippedKeys, CaptureRequest.Builder requestBuilder,
+ int numFramesVerified) throws Exception {
+ CaptureResult result = null;
+ for (int i = 0; i < numFramesVerified; i++) {
+ String failMsg = "Failed capture result " + i + " test ";
+ result = captureListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
+
+ for (CaptureResult.Key<?> key : mAllKeys) {
+ if (!skippedKeys.contains(key)) {
+ /**
+ * Check the critical tags here.
+ * TODO: Can use the same key for request and result when request/result
+ * becomes symmetric (b/14059883). Then below check can be wrapped into
+ * a generic function.
+ */
+ String msg = failMsg + "for key " + key.getName();
+ if (key.equals(CaptureResult.CONTROL_AE_MODE)) {
+ mCollector.expectEquals(msg,
+ requestBuilder.get(CaptureRequest.CONTROL_AE_MODE),
+ result.get(CaptureResult.CONTROL_AE_MODE));
+ } else if (key.equals(CaptureResult.CONTROL_AF_MODE)) {
+ mCollector.expectEquals(msg,
+ requestBuilder.get(CaptureRequest.CONTROL_AF_MODE),
+ result.get(CaptureResult.CONTROL_AF_MODE));
+ } else if (key.equals(CaptureResult.CONTROL_AWB_MODE)) {
+ mCollector.expectEquals(msg,
+ requestBuilder.get(CaptureRequest.CONTROL_AWB_MODE),
+ result.get(CaptureResult.CONTROL_AWB_MODE));
+ } else if (key.equals(CaptureResult.CONTROL_MODE)) {
+ mCollector.expectEquals(msg,
+ requestBuilder.get(CaptureRequest.CONTROL_MODE),
+ result.get(CaptureResult.CONTROL_MODE));
+ } else if (key.equals(CaptureResult.STATISTICS_FACE_DETECT_MODE)) {
+ mCollector.expectEquals(msg,
+ requestBuilder.get(CaptureRequest.STATISTICS_FACE_DETECT_MODE),
+ result.get(CaptureResult.STATISTICS_FACE_DETECT_MODE));
+ } else if (key.equals(CaptureResult.NOISE_REDUCTION_MODE)) {
+ mCollector.expectEquals(msg,
+ requestBuilder.get(CaptureRequest.NOISE_REDUCTION_MODE),
+ result.get(CaptureResult.NOISE_REDUCTION_MODE));
+ } else if (key.equals(CaptureResult.NOISE_REDUCTION_MODE)) {
+ mCollector.expectEquals(msg,
+ requestBuilder.get(CaptureRequest.NOISE_REDUCTION_MODE),
+ result.get(CaptureResult.NOISE_REDUCTION_MODE));
+ } else if (key.equals(CaptureResult.REQUEST_PIPELINE_DEPTH)) {
+
+ } else {
+ // Only do non-null check for the rest of keys.
+ mCollector.expectKeyValueNotNull(failMsg, result, key);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * TODO: Use CameraCharacteristics.getAvailableCaptureResultKeys() once we can filter out
+ * @hide keys.
+ *
+ */
+
+ /*@O~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~
+ * The key entries below this point are generated from metadata
+ * definitions in /system/media/camera/docs. Do not modify by hand or
+ * modify the comment blocks at the start or end.
+ *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~*/
+
+ private static List<CaptureResult.Key<?>> getAllCaptureResultKeys() {
+ ArrayList<CaptureResult.Key<?>> resultKeys = new ArrayList<CaptureResult.Key<?>>();
+ resultKeys.add(CaptureResult.COLOR_CORRECTION_MODE);
+ resultKeys.add(CaptureResult.COLOR_CORRECTION_TRANSFORM);
+ resultKeys.add(CaptureResult.COLOR_CORRECTION_GAINS);
+ resultKeys.add(CaptureResult.CONTROL_AE_ANTIBANDING_MODE);
+ resultKeys.add(CaptureResult.CONTROL_AE_EXPOSURE_COMPENSATION);
+ resultKeys.add(CaptureResult.CONTROL_AE_LOCK);
+ resultKeys.add(CaptureResult.CONTROL_AE_MODE);
+ resultKeys.add(CaptureResult.CONTROL_AE_REGIONS);
+ resultKeys.add(CaptureResult.CONTROL_AE_TARGET_FPS_RANGE);
+ resultKeys.add(CaptureResult.CONTROL_AE_PRECAPTURE_TRIGGER);
+ resultKeys.add(CaptureResult.CONTROL_AF_MODE);
+ resultKeys.add(CaptureResult.CONTROL_AF_REGIONS);
+ resultKeys.add(CaptureResult.CONTROL_AF_TRIGGER);
+ resultKeys.add(CaptureResult.CONTROL_AWB_LOCK);
+ resultKeys.add(CaptureResult.CONTROL_AWB_MODE);
+ resultKeys.add(CaptureResult.CONTROL_AWB_REGIONS);
+ resultKeys.add(CaptureResult.CONTROL_CAPTURE_INTENT);
+ resultKeys.add(CaptureResult.CONTROL_EFFECT_MODE);
+ resultKeys.add(CaptureResult.CONTROL_MODE);
+ resultKeys.add(CaptureResult.CONTROL_SCENE_MODE);
+ resultKeys.add(CaptureResult.CONTROL_VIDEO_STABILIZATION_MODE);
+ resultKeys.add(CaptureResult.CONTROL_AE_STATE);
+ resultKeys.add(CaptureResult.CONTROL_AF_STATE);
+ resultKeys.add(CaptureResult.CONTROL_AWB_STATE);
+ resultKeys.add(CaptureResult.EDGE_MODE);
+ resultKeys.add(CaptureResult.FLASH_MODE);
+ resultKeys.add(CaptureResult.FLASH_STATE);
+ resultKeys.add(CaptureResult.HOT_PIXEL_MODE);
+ resultKeys.add(CaptureResult.JPEG_GPS_LOCATION);
+ resultKeys.add(CaptureResult.JPEG_ORIENTATION);
+ resultKeys.add(CaptureResult.JPEG_QUALITY);
+ resultKeys.add(CaptureResult.JPEG_THUMBNAIL_QUALITY);
+ resultKeys.add(CaptureResult.JPEG_THUMBNAIL_SIZE);
+ resultKeys.add(CaptureResult.LENS_APERTURE);
+ resultKeys.add(CaptureResult.LENS_FILTER_DENSITY);
+ resultKeys.add(CaptureResult.LENS_FOCAL_LENGTH);
+ resultKeys.add(CaptureResult.LENS_FOCUS_DISTANCE);
+ resultKeys.add(CaptureResult.LENS_OPTICAL_STABILIZATION_MODE);
+ resultKeys.add(CaptureResult.LENS_FOCUS_RANGE);
+ resultKeys.add(CaptureResult.LENS_STATE);
+ resultKeys.add(CaptureResult.NOISE_REDUCTION_MODE);
+ resultKeys.add(CaptureResult.REQUEST_FRAME_COUNT);
+ resultKeys.add(CaptureResult.REQUEST_PIPELINE_DEPTH);
+ resultKeys.add(CaptureResult.SCALER_CROP_REGION);
+ resultKeys.add(CaptureResult.SENSOR_EXPOSURE_TIME);
+ resultKeys.add(CaptureResult.SENSOR_FRAME_DURATION);
+ resultKeys.add(CaptureResult.SENSOR_SENSITIVITY);
+ resultKeys.add(CaptureResult.SENSOR_TIMESTAMP);
+ resultKeys.add(CaptureResult.SENSOR_NEUTRAL_COLOR_POINT);
+ resultKeys.add(CaptureResult.SENSOR_GREEN_SPLIT);
+ resultKeys.add(CaptureResult.SENSOR_TEST_PATTERN_DATA);
+ resultKeys.add(CaptureResult.SENSOR_TEST_PATTERN_MODE);
+ resultKeys.add(CaptureResult.SHADING_MODE);
+ resultKeys.add(CaptureResult.STATISTICS_FACE_DETECT_MODE);
+ resultKeys.add(CaptureResult.STATISTICS_HOT_PIXEL_MAP_MODE);
+ resultKeys.add(CaptureResult.STATISTICS_FACES);
+ resultKeys.add(CaptureResult.STATISTICS_LENS_SHADING_CORRECTION_MAP);
+ resultKeys.add(CaptureResult.STATISTICS_SCENE_FLICKER);
+ resultKeys.add(CaptureResult.STATISTICS_HOT_PIXEL_MAP);
+ resultKeys.add(CaptureResult.STATISTICS_LENS_SHADING_MAP_MODE);
+ resultKeys.add(CaptureResult.TONEMAP_CURVE);
+ resultKeys.add(CaptureResult.TONEMAP_MODE);
+ resultKeys.add(CaptureResult.BLACK_LEVEL_LOCK);
+
+ return resultKeys;
+ }
+
+ /*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~
+ * End generated code
+ *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/DngCreatorTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/DngCreatorTest.java
new file mode 100644
index 0000000..5a9baeb
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/DngCreatorTest.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright 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 android.hardware.camera2.cts;
+
+import android.graphics.ImageFormat;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.DngCreator;
+import android.hardware.camera2.cts.helpers.StaticMetadata;
+import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
+import android.media.Image;
+import android.media.ImageReader;
+import android.util.Log;
+import android.util.Pair;
+import android.util.Size;
+import android.view.Surface;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileOutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import static android.hardware.camera2.cts.CameraTestUtils.configureCameraOutputs;
+
+/**
+ * Tests for the DngCreator API.
+ */
+public class DngCreatorTest extends Camera2AndroidTestCase {
+ private static final String TAG = "DngCreatorTest";
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+ private static final String DEBUG_DNG_FILE = "/raw16.dng";
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ /**
+ * Test basic raw capture and DNG saving functionality for each of the available cameras.
+ *
+ * <p>
+ * For each camera, capture a single RAW16 image at the first capture size reported for
+ * the raw format on that device, and save that image as a DNG file. No further validation
+ * is done.
+ * </p>
+ *
+ * <p>
+ * Note: Enabling adb shell setprop log.tag.DngCreatorTest VERBOSE will also cause the
+ * raw image captured for the first reported camera device to be saved to an output file.
+ * </p>
+ */
+ public void testSingleImageBasic() throws Exception {
+ for (int i = 0; i < mCameraIds.length; i++) {
+ String deviceId = mCameraIds[i];
+ ImageReader captureReader = null;
+ FileOutputStream fileStream = null;
+ ByteArrayOutputStream outputStream = null;
+ try {
+ openDevice(deviceId);
+
+ Size[] targetCaptureSizes =
+ mStaticInfo.getAvailableSizesForFormatChecked(ImageFormat.RAW_SENSOR,
+ StaticMetadata.StreamDirection.Output);
+ if (targetCaptureSizes.length == 0) {
+ if (VERBOSE) {
+ Log.v(TAG, "Skipping testSingleImageBasic - " +
+ "no raw output streams for camera " + deviceId);
+ }
+ continue;
+ }
+
+ Size s = targetCaptureSizes[0];
+
+ // Create capture image reader
+ CameraTestUtils.SimpleImageReaderListener captureListener
+ = new CameraTestUtils.SimpleImageReaderListener();
+ captureReader = createImageReader(s, ImageFormat.RAW_SENSOR, 2,
+ captureListener);
+ Pair<Image, CaptureResult> resultPair = captureSingleRawShot(s, captureReader, captureListener);
+ CameraCharacteristics characteristics = mStaticInfo.getCharacteristics();
+
+ // Test simple writeImage, no header checks
+ DngCreator dngCreator = new DngCreator(characteristics, resultPair.second);
+ outputStream = new ByteArrayOutputStream();
+ dngCreator.writeImage(outputStream, resultPair.first);
+
+ if (VERBOSE && i == 0) {
+ // Write out captured DNG file for the first camera device if setprop is enabled
+ fileStream = new FileOutputStream(DEBUG_FILE_NAME_BASE +
+ DEBUG_DNG_FILE);
+ fileStream.write(outputStream.toByteArray());
+ fileStream.flush();
+ fileStream.close();
+ Log.v(TAG, "Test DNG file for camera " + deviceId + " saved to " +
+ DEBUG_FILE_NAME_BASE + DEBUG_DNG_FILE);
+ }
+ } finally {
+ closeDevice(deviceId);
+ closeImageReader(captureReader);
+
+ if (outputStream != null) {
+ outputStream.close();
+ }
+
+ if (fileStream != null) {
+ fileStream.close();
+ }
+ }
+ }
+ }
+
+ // TODO: Further tests for DNG header validation.
+
+ /**
+ * Capture a single raw image.
+ *
+ * <p>Capture an raw image for a given size.</p>
+ *
+ * @param s The size of the raw image to capture. Must be one of the available sizes for this
+ * device.
+ * @return a pair containing the {@link Image} and {@link CaptureResult} used for this capture.
+ */
+ private Pair<Image, CaptureResult> captureSingleRawShot(Size s, ImageReader captureReader,
+ CameraTestUtils.SimpleImageReaderListener captureListener) throws Exception {
+ if (VERBOSE) {
+ Log.v(TAG, "captureSingleRawShot - Capturing raw image.");
+ }
+
+ Size maxYuvSz = mOrderedPreviewSizes.get(0);
+ Size[] targetCaptureSizes =
+ mStaticInfo.getAvailableSizesForFormatChecked(ImageFormat.RAW_SENSOR,
+ StaticMetadata.StreamDirection.Output);
+
+ // Validate size
+ boolean validSize = false;
+ for (int i = 0; i < targetCaptureSizes.length; ++i) {
+ if (targetCaptureSizes[i].equals(s)) {
+ validSize = true;
+ break;
+ }
+ }
+ assertTrue("Capture size is supported.", validSize);
+ Surface captureSurface = captureReader.getSurface();
+
+ // Capture images.
+ List<Surface> outputSurfaces = new ArrayList<Surface>();
+ outputSurfaces.add(captureSurface);
+ CaptureRequest.Builder request = prepareCaptureRequestForSurfaces(outputSurfaces);
+ request.set(CaptureRequest.STATISTICS_LENS_SHADING_MAP_MODE,
+ CaptureRequest.STATISTICS_LENS_SHADING_MAP_MODE_ON);
+ CameraTestUtils.SimpleCaptureListener resultListener =
+ new CameraTestUtils.SimpleCaptureListener();
+
+ startCapture(request.build(), /*repeating*/false, resultListener, mHandler);
+
+ // Verify capture result and images
+ CaptureResult result = resultListener.getCaptureResult(CAPTURE_WAIT_TIMEOUT_MS);
+
+ Image captureImage = captureListener.getImage(CAPTURE_WAIT_TIMEOUT_MS);
+
+ CameraTestUtils.validateImage(captureImage, s.getWidth(), s.getHeight(),
+ ImageFormat.RAW_SENSOR, null);
+ // Stop capture, delete the streams.
+ stopCapture(/*fast*/false);
+
+ return new Pair<Image, CaptureResult>(captureImage, result);
+ }
+
+ private CaptureRequest.Builder prepareCaptureRequestForSurfaces(List<Surface> surfaces)
+ throws Exception {
+ configureCameraOutputs(mCamera, surfaces, mCameraListener);
+
+ CaptureRequest.Builder captureBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ assertNotNull("Fail to get captureRequest", captureBuilder);
+ for (Surface surface : surfaces) {
+ captureBuilder.addTarget(surface);
+ }
+
+ return captureBuilder;
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/ImageReaderTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/ImageReaderTest.java
index 28cb13e..70fea4b 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/ImageReaderTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/ImageReaderTest.java
@@ -17,30 +17,23 @@
package android.hardware.camera2.cts;
import static android.hardware.camera2.cts.CameraTestUtils.*;
-import static com.android.ex.camera2.blocking.BlockingStateListener.*;
import android.content.Context;
-import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
-import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraDevice;
-import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CaptureRequest;
-import android.hardware.camera2.Size;
+import android.hardware.camera2.CaptureResult;
+import android.util.Size;
+import android.hardware.camera2.cts.helpers.StaticMetadata;
+import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
import android.media.Image;
import android.media.ImageReader;
-import android.os.Environment;
-import android.os.Handler;
-import android.test.AndroidTestCase;
+import android.os.ConditionVariable;
import android.util.Log;
import android.view.Surface;
-import com.android.ex.camera2.blocking.BlockingCameraManager.BlockingOpenException;
-import com.android.ex.camera2.blocking.BlockingStateListener;
-
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
/**
@@ -53,305 +46,362 @@
* <p>Some invalid access test. </p>
* <p>TODO: Add more format tests? </p>
*/
-public class ImageReaderTest extends AndroidTestCase {
+public class ImageReaderTest extends Camera2AndroidTestCase {
private static final String TAG = "ImageReaderTest";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
- private static final boolean DUMP_FILE = false;
- private static final String DEBUG_FILE_NAME_BASE =
- Environment.getExternalStorageDirectory().getPath();
// number of frame (for streaming requests) to be verified.
- // TODO: Need extend it to bigger number
- private static final int NUM_FRAME_VERIFIED = 1;
+ private static final int NUM_FRAME_VERIFIED = 2;
// Max number of images can be accessed simultaneously from ImageReader.
private static final int MAX_NUM_IMAGES = 5;
- private CameraManager mCameraManager;
- private CameraDevice mCamera;
- private BlockingStateListener mCameraListener;
- private String[] mCameraIds;
- private ImageReader mReader = null;
- private Handler mHandler = null;
- private SimpleImageListener mListener = null;
- private CameraTestThread mLooperThread = null;
+ private SimpleImageListener mListener;
@Override
public void setContext(Context context) {
super.setContext(context);
- mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
- assertNotNull("Can't connect to camera manager!", mCameraManager);
}
@Override
protected void setUp() throws Exception {
super.setUp();
- mCameraIds = mCameraManager.getCameraIdList();
- mLooperThread = new CameraTestThread();
- mHandler = mLooperThread.start();
- mCameraListener = new BlockingStateListener();
}
@Override
protected void tearDown() throws Exception {
- if (mCamera != null) {
- mCamera.close();
- mCamera = null;
- }
- if (mReader != null) {
- mReader.close();
- mReader = null;
- }
- mLooperThread.close();
- mHandler = null;
super.tearDown();
}
- public void testImageReaderFromCameraFlexibleYuv() throws Exception {
- for (int i = 0; i < mCameraIds.length; i++) {
- Log.i(TAG, "Testing Camera " + mCameraIds[i]);
- openDevice(mCameraIds[i]);
- bufferFormatTestByCamera(ImageFormat.YUV_420_888, mCameraIds[i]);
- closeDevice(mCameraIds[i]);
+ public void testFlexibleYuv() throws Exception {
+ for (String id : mCameraIds) {
+ try {
+ Log.i(TAG, "Testing Camera " + id);
+ openDevice(id);
+ bufferFormatTestByCamera(ImageFormat.YUV_420_888, /*repeating*/true);
+ } finally {
+ closeDevice(id);
+ }
}
}
- public void testImageReaderFromCameraJpeg() throws Exception {
- for (int i = 0; i < mCameraIds.length; i++) {
- Log.v(TAG, "Testing Camera " + mCameraIds[i]);
- openDevice(mCameraIds[i]);
- bufferFormatTestByCamera(ImageFormat.JPEG, mCameraIds[i]);
- closeDevice(mCameraIds[i]);
+ public void testJpeg() throws Exception {
+ for (String id : mCameraIds) {
+ try {
+ Log.v(TAG, "Testing jpeg capture for Camera " + id);
+ openDevice(id);
+ bufferFormatTestByCamera(ImageFormat.JPEG, /*repeating*/false);
+ } finally {
+ closeDevice(id);
+ }
}
}
- public void testImageReaderFromCameraRaw() {
- // TODO: can test this once raw is supported
+ public void testRaw() throws Exception {
+ for (String id : mCameraIds) {
+ try {
+ Log.v(TAG, "Testing raw capture for camera " + id);
+ openDevice(id);
+
+ bufferFormatTestByCamera(ImageFormat.RAW_SENSOR, /*repeating*/false);
+ } finally {
+ closeDevice(id);
+ }
+ }
}
- public void testImageReaderInvalidAccessTest() {
+ public void testRepeatingJpeg() throws Exception {
+ for (String id : mCameraIds) {
+ try {
+ Log.v(TAG, "Testing repeating jpeg capture for Camera " + id);
+ openDevice(id);
+ bufferFormatTestByCamera(ImageFormat.JPEG, /*repeating*/true);
+ } finally {
+ closeDevice(id);
+ }
+ }
+ }
+
+ public void testRepeatingRaw() throws Exception {
+ for (String id : mCameraIds) {
+ try {
+ Log.v(TAG, "Testing repeating raw capture for camera " + id);
+ openDevice(id);
+
+ bufferFormatTestByCamera(ImageFormat.RAW_SENSOR, /*repeating*/true);
+ } finally {
+ closeDevice(id);
+ }
+ }
+ }
+
+ public void testInvalidAccessTest() {
// TODO: test invalid access case, see if we can receive expected
// exceptions
}
- private void bufferFormatTestByCamera(int format, String cameraId) throws Exception {
- CameraCharacteristics properties = mCameraManager.getCameraCharacteristics(cameraId);
- assertNotNull("Can't get camera properties!", properties);
+ /**
+ * Test two image stream (YUV420_888 and JPEG) capture by using ImageReader.
+ *
+ * <p>Both stream formats are mandatory for Camera2 API</p>
+ */
+ public void testYuvAndJpeg() throws Exception {
+ for (String id : mCameraIds) {
+ try {
+ Log.v(TAG, "YUV and JPEG testing for camera " + id);
+ openDevice(id);
- /**
- * TODO: cleanup the color format mess, we probably need define formats
- * in Image class instead of using ImageFormat for camera. also,
- * probably make sense to change the available format type from Enum[]
- * to int[]. It'll also be nice to put this into a helper function and
- * move to util class.
- */
- int[] availableFormats = properties.get(CameraCharacteristics.SCALER_AVAILABLE_FORMATS);
- assertArrayNotEmpty(availableFormats,
- "availableFormats should not be empty");
- Arrays.sort(availableFormats);
- assertTrue("Can't find the format " + format + " in supported formats " +
- Arrays.toString(availableFormats),
- Arrays.binarySearch(availableFormats, format) >= 0);
+ bufferFormatWithYuvTestByCamera(ImageFormat.JPEG);
+ } finally {
+ closeDevice(id);
+ }
+ }
+ }
- Size[] availableSizes = getSupportedSizeForFormat(format, mCamera.getId(), mCameraManager);
- assertArrayNotEmpty(availableSizes, "availableSizes should not be empty");
+ /**
+ * Test two image stream (YUV420_888 and RAW_SENSOR) capture by using ImageReader.
+ *
+ */
+ public void testImageReaderYuvAndRaw() throws Exception {
+ for (String id : mCameraIds) {
+ try {
+ Log.v(TAG, "YUV and RAW testing for camera " + id);
+ openDevice(id);
+
+ bufferFormatWithYuvTestByCamera(ImageFormat.RAW_SENSOR);
+ } finally {
+ closeDevice(id);
+ }
+ }
+ }
+
+
+ /**
+ * Test capture a given format stream with yuv stream simultaneously.
+ *
+ * <p>Use fixed yuv size, varies targeted format capture size. Single capture is tested.</p>
+ *
+ * @param format The capture format to be tested along with yuv format.
+ */
+ private void bufferFormatWithYuvTestByCamera(int format) throws Exception {
+ if (format != ImageFormat.JPEG && format != ImageFormat.RAW_SENSOR
+ && format != ImageFormat.YUV_420_888) {
+ throw new IllegalArgumentException("Unsupported format: " + format);
+ }
+
+ final int NUM_SINGLE_CAPTURE_TESTED = MAX_NUM_IMAGES - 1;
+ Size maxYuvSz = mOrderedPreviewSizes.get(0);
+ Size[] targetCaptureSizes = mStaticInfo.getAvailableSizesForFormatChecked(format,
+ StaticMetadata.StreamDirection.Output);
+
+ for (Size captureSz : targetCaptureSizes) {
+ if (VERBOSE) {
+ Log.v(TAG, "Testing yuv size " + maxYuvSz.toString() + " and capture size "
+ + captureSz.toString() + " for camera " + mCamera.getId());
+ }
+
+ ImageReader captureReader = null;
+ ImageReader yuvReader = null;
+ try {
+ // Create YUV image reader
+ SimpleImageReaderListener yuvListener = new SimpleImageReaderListener();
+ yuvReader = createImageReader(maxYuvSz, ImageFormat.YUV_420_888, MAX_NUM_IMAGES,
+ yuvListener);
+ Surface yuvSurface = yuvReader.getSurface();
+
+ // Create capture image reader
+ SimpleImageReaderListener captureListener = new SimpleImageReaderListener();
+ captureReader = createImageReader(captureSz, format, MAX_NUM_IMAGES,
+ captureListener);
+ Surface captureSurface = captureReader.getSurface();
+
+ // Capture images.
+ List<Surface> outputSurfaces = new ArrayList<Surface>();
+ outputSurfaces.add(yuvSurface);
+ outputSurfaces.add(captureSurface);
+ CaptureRequest.Builder request = prepareCaptureRequestForSurfaces(outputSurfaces);
+ SimpleCaptureListener resultListener = new SimpleCaptureListener();
+
+ for (int i = 0; i < NUM_SINGLE_CAPTURE_TESTED; i++) {
+ startCapture(request.build(), /*repeating*/false, resultListener, mHandler);
+ }
+
+ // Verify capture result and images
+ for (int i = 0; i < NUM_SINGLE_CAPTURE_TESTED; i++) {
+ resultListener.getCaptureResult(CAPTURE_WAIT_TIMEOUT_MS);
+ if (VERBOSE) {
+ Log.v(TAG, " Got the capture result back for " + i + "th capture");
+ }
+
+ Image yuvImage = yuvListener.getImage(CAPTURE_WAIT_TIMEOUT_MS);
+ if (VERBOSE) {
+ Log.v(TAG, " Got the yuv image back for " + i + "th capture");
+ }
+
+ Image captureImage = captureListener.getImage(CAPTURE_WAIT_TIMEOUT_MS);
+ if (VERBOSE) {
+ Log.v(TAG, " Got the capture image back for " + i + "th capture");
+ }
+
+ //Validate captured images.
+ CameraTestUtils.validateImage(yuvImage, maxYuvSz.getWidth(),
+ maxYuvSz.getHeight(), ImageFormat.YUV_420_888, /*filePath*/null);
+ CameraTestUtils.validateImage(captureImage, captureSz.getWidth(),
+ captureSz.getHeight(), format, /*filePath*/null);
+ }
+
+ // Stop capture, delete the streams.
+ stopCapture(/*fast*/false);
+ } finally {
+ closeImageReader(captureReader);
+ captureReader = null;
+ closeImageReader(yuvReader);
+ yuvReader = null;
+ }
+ }
+ }
+
+ private void bufferFormatTestByCamera(int format, boolean repeating) throws Exception {
+
+ Size[] availableSizes = mStaticInfo.getAvailableSizesForFormatChecked(format,
+ StaticMetadata.StreamDirection.Output);
// for each resolution, test imageReader:
for (Size sz : availableSizes) {
- if (VERBOSE) Log.v(TAG, "Testing size " + sz.toString() + " for camera " + cameraId);
+ try {
+ if (VERBOSE) {
+ Log.v(TAG, "Testing size " + sz.toString() + " format " + format
+ + " for camera " + mCamera.getId());
+ }
- prepareImageReader(sz, format);
+ // Create ImageReader.
+ mListener = new SimpleImageListener();
+ createDefaultImageReader(sz, format, MAX_NUM_IMAGES, mListener);
- CaptureRequest request = prepareCaptureRequest(format);
+ // Start capture.
+ CaptureRequest request = prepareCaptureRequest();
+ SimpleCaptureListener listener = new SimpleCaptureListener();
+ startCapture(request, repeating, listener, mHandler);
- captureAndValidateImage(request, sz, format);
+ int numFrameVerified = repeating ? NUM_FRAME_VERIFIED : 1;
- stopCapture();
+ // Validate images.
+ validateImage(sz, format, numFrameVerified);
+
+ // Validate capture result.
+ validateCaptureResult(format, sz, listener, numFrameVerified);
+
+ // stop capture.
+ stopCapture(/*fast*/false);
+ } finally {
+ closeDefaultImageReader();
+ }
+
}
}
- private class SimpleImageListener implements ImageReader.OnImageAvailableListener {
- private int mPendingImages = 0;
- private final Object mImageSyncObject = new Object();
+ /**
+ * Validate capture results.
+ *
+ * @param format The format of this capture.
+ * @param size The capture size.
+ * @param listener The capture listener to get capture result callbacks.
+ */
+ private void validateCaptureResult(int format, Size size, SimpleCaptureListener listener,
+ int numFrameVerified) {
+ for (int i = 0; i < numFrameVerified; i++) {
+ CaptureResult result = listener.getCaptureResult(CAPTURE_RESULT_TIMEOUT_MS);
+ Long exposureTime = getValueNotNull(result, CaptureResult.SENSOR_EXPOSURE_TIME);
+ Integer sensitivity = getValueNotNull(result, CaptureResult.SENSOR_SENSITIVITY);
+ mCollector.expectInRange(
+ String.format(
+ "Capture for format %d, size %s exposure time is invalid.",
+ format, size.toString()),
+ exposureTime,
+ mStaticInfo.getExposureMinimumOrDefault(),
+ mStaticInfo.getExposureMaximumOrDefault());
+ mCollector.expectInRange(
+ String.format("Capture for format %d, size %s sensitivity is invalid.",
+ format, size.toString()),
+ sensitivity,
+ mStaticInfo.getSensitivityMinimumOrDefault(),
+ mStaticInfo.getSensitivityMaximumOrDefault());
+ // TODO: add more key validations.
+ }
+ }
+
+ private final class SimpleImageListener implements ImageReader.OnImageAvailableListener {
+ private final ConditionVariable imageAvailable = new ConditionVariable();
@Override
public void onImageAvailable(ImageReader reader) {
+ if (mReader != reader) {
+ return;
+ }
+
if (VERBOSE) Log.v(TAG, "new image available");
- synchronized (mImageSyncObject) {
- mPendingImages++;
- mImageSyncObject.notifyAll();
+ imageAvailable.open();
+ }
+
+ public void waitForAnyImageAvailable(long timeout) {
+ if (imageAvailable.block(timeout)) {
+ imageAvailable.close();
+ } else {
+ fail("wait for image available timed out after " + timeout + "ms");
}
}
- public boolean isImagePending() {
- synchronized (mImageSyncObject) {
- return (mPendingImages > 0);
- }
- }
-
- public void waitForImage() {
- final int TIMEOUT_MS = 5000;
- synchronized (mImageSyncObject) {
- while (mPendingImages == 0) {
- try {
- if (VERBOSE)
- Log.d(TAG, "waiting for next image");
- mImageSyncObject.wait(TIMEOUT_MS);
- if (mPendingImages == 0) {
- fail("wait for next image timed out");
- }
- } catch (InterruptedException ie) {
- throw new RuntimeException(ie);
- }
- }
- mPendingImages--;
+ public void closePendingImages() {
+ Image image = mReader.acquireLatestImage();
+ if (image != null) {
+ image.close();
}
}
}
- private void prepareImageReader(Size sz, int format) throws Exception {
- int width = sz.getWidth();
- int height = sz.getHeight();
- mReader = ImageReader.newInstance(width, height, format, MAX_NUM_IMAGES);
- mListener = new SimpleImageListener();
- mReader.setOnImageAvailableListener(mListener, mHandler);
- if (VERBOSE) Log.v(TAG, "Preparing ImageReader size " + sz.toString());
- }
-
- private CaptureRequest prepareCaptureRequest(int format) throws Exception {
- List<Surface> outputSurfaces = new ArrayList<Surface>(1);
+ private CaptureRequest prepareCaptureRequest() throws Exception {
+ List<Surface> outputSurfaces = new ArrayList<Surface>();
Surface surface = mReader.getSurface();
assertNotNull("Fail to get surface from ImageReader", surface);
outputSurfaces.add(surface);
- mCamera.configureOutputs(outputSurfaces);
- mCameraListener.waitForState(STATE_BUSY, CAMERA_BUSY_TIMEOUT_MS);
- mCameraListener.waitForState(STATE_IDLE, CAMERA_IDLE_TIMEOUT_MS);
+ return prepareCaptureRequestForSurfaces(outputSurfaces).build();
+ }
+
+ private CaptureRequest.Builder prepareCaptureRequestForSurfaces(List<Surface> surfaces)
+ throws Exception {
+ configureCameraOutputs(mCamera, surfaces, mCameraListener);
CaptureRequest.Builder captureBuilder =
mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
assertNotNull("Fail to get captureRequest", captureBuilder);
- captureBuilder.addTarget(mReader.getSurface());
+ for (Surface surface : surfaces) {
+ captureBuilder.addTarget(surface);
+ }
- return captureBuilder.build();
+ return captureBuilder;
}
- private void captureAndValidateImage(CaptureRequest request,
- Size sz, int format) throws Exception {
+ private void validateImage(Size sz, int format, int captureCount) throws Exception {
// TODO: Add more format here, and wrap each one as a function.
Image img;
- int captureCount = NUM_FRAME_VERIFIED;
-
- // Only verify single image for still capture
- if (format == ImageFormat.JPEG) {
- captureCount = 1;
- mCamera.capture(request, null, null);
- } else {
- mCamera.setRepeatingRequest(request, null, null);
- }
for (int i = 0; i < captureCount; i++) {
assertNotNull("Image listener is null", mListener);
if (VERBOSE) Log.v(TAG, "Waiting for an Image");
- mListener.waitForImage();
- img = mReader.acquireNextImage();
- if (VERBOSE) Log.v(TAG, "Got next image");
- validateImage(img, sz.getWidth(), sz.getHeight(), format);
+ mListener.waitForAnyImageAvailable(CAPTURE_WAIT_TIMEOUT_MS);
+ /**
+ * Acquire the latest image in case the validation is slower than
+ * the image producing rate.
+ */
+ img = mReader.acquireLatestImage();
+ assertNotNull("Unable to acquire the latest image", img);
+ if (VERBOSE) Log.v(TAG, "Got the latest image");
+ CameraTestUtils.validateImage(img, sz.getWidth(), sz.getHeight(), format,
+ DEBUG_FILE_NAME_BASE);
+ if (VERBOSE) Log.v(TAG, "finish vaildation of image " + i);
img.close();
- // Return the pending images to producer in case the validation is slower
- // than the image producing rate. Otherwise, it could cause the producer
- // starvation.
- while (mListener.isImagePending()) {
- mListener.waitForImage();
- img = mReader.acquireNextImage();
- img.close();
- }
}
- }
- private void stopCapture() throws CameraAccessException {
- if (VERBOSE) Log.v(TAG, "Stopping capture and waiting for idle");
- // Stop repeat, wait for captures to complete, and disconnect from surfaces
- mCamera.configureOutputs(/*outputs*/ null);
- mCameraListener.waitForState(STATE_BUSY, CAMERA_BUSY_TIMEOUT_MS);
- mCameraListener.waitForState(STATE_UNCONFIGURED, CAMERA_IDLE_TIMEOUT_MS);
- // Camera has disconnected, clear out the reader
- mReader.close();
- mReader = null;
- mListener = null;
- }
-
- private void openDevice(String cameraId) {
- if (mCamera != null) {
- throw new IllegalStateException("Already have open camera device");
- }
- try {
- mCamera = CameraTestUtils.openCamera(
- mCameraManager, cameraId, mCameraListener, mHandler);
- } catch (CameraAccessException e) {
- mCamera = null;
- fail("Fail to open camera, " + Log.getStackTraceString(e));
- } catch (BlockingOpenException e) {
- mCamera = null;
- fail("Fail to open camera, " + Log.getStackTraceString(e));
- }
- mCameraListener.waitForState(STATE_UNCONFIGURED, CAMERA_OPEN_TIMEOUT_MS);
- }
-
- private void closeDevice(String cameraId) {
- mCamera.close();
- mCamera = null;
- }
-
- private void validateImage(Image image, int width, int height, int format) {
- checkImage(image, width, height, format);
-
- /**
- * TODO: validate timestamp:
- * 1. capture result timestamp against the image timestamp (need
- * consider frame drops)
- * 2. timestamps should be monotonically increasing for different requests
- */
- if(VERBOSE) Log.v(TAG, "validating Image");
- byte[] data = getDataFromImage(image);
- assertTrue("Invalid image data", data != null && data.length > 0);
-
- if (format == ImageFormat.JPEG) {
- validateJpegData(data, width, height);
- } else {
- validateYuvData(data, width, height, format, image.getTimestamp());
- }
- }
-
- private void validateJpegData(byte[] jpegData, int width, int height) {
- BitmapFactory.Options bmpOptions = new BitmapFactory.Options();
- // DecodeBound mode: only parse the frame header to get width/height.
- // it doesn't decode the pixel.
- bmpOptions.inJustDecodeBounds = true;
- BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length, bmpOptions);
- assertEquals(width, bmpOptions.outWidth);
- assertEquals(height, bmpOptions.outHeight);
-
- // Pixel decoding mode: decode whole image. check if the image data
- // is decodable here.
- assertNotNull("Decoding jpeg failed",
- BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length));
- if (DUMP_FILE) {
- String fileName =
- DEBUG_FILE_NAME_BASE + width + "x" + height + ".yuv";
- dumpFile(fileName, jpegData);
- }
- }
-
- private void validateYuvData(byte[] yuvData, int width, int height, int format, long ts) {
- checkYuvFormat(format);
- 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);
-
- // TODO: Can add data validation if we have test pattern(tracked by b/9625427)
-
- if (DUMP_FILE) {
- String fileName =
- DEBUG_FILE_NAME_BASE + "/" + width + "x" + height + "_" + ts / 1e6 + ".yuv";
- dumpFile(fileName, yuvData);
- }
+ // Return all pending images to the ImageReader as the validateImage may
+ // take a while to return and there could be many images pending.
+ mListener.closePendingImages();
}
}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/RecordingTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/RecordingTest.java
new file mode 100644
index 0000000..43031da
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/RecordingTest.java
@@ -0,0 +1,628 @@
+/*
+ * 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 android.hardware.camera2.cts;
+
+import static android.hardware.camera2.cts.CameraTestUtils.*;
+import static com.android.ex.camera2.blocking.BlockingStateListener.*;
+
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CaptureRequest;
+import android.util.Size;
+import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase;
+import android.media.CamcorderProfile;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaCodecInfo.CodecProfileLevel;
+import android.media.MediaCodecList;
+import android.media.MediaPlayer;
+import android.media.MediaRecorder;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+import android.util.Range;
+import android.view.SurfaceHolder;
+import android.view.Surface;
+
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * CameraDevice video recording use case tests by using MediaRecorder and
+ * MediaCodec.
+ */
+@LargeTest
+public class RecordingTest extends Camera2SurfaceViewTestCase {
+ private static final String TAG = "RecordingTest";
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+ private static final boolean DEBUG_DUMP = false;
+ private static final Size VIDEO_SIZE_BOUND = new Size(1920, 1080);
+ private static final int RECORDING_DURATION_MS = 2000;
+ private static final int DURATION_MARGIN_MS = 400;
+ private static final int WAIT_FOR_SURFACE_CHANGE_TIMEOUT_MS = 1000;
+ private static final int BIT_RATE_1080P = 16000000;
+ private static final int BIT_RATE_MIN = 64000;
+ private static final int BIT_RATE_MAX = 40000000;
+ private static final int VIDEO_FRAME_RATE = 30;
+ private final String VIDEO_FILE_PATH = Environment.getExternalStorageDirectory().getPath();
+ private static final int[] mCamcorderProfileList = {
+ CamcorderProfile.QUALITY_1080P,
+ CamcorderProfile.QUALITY_480P,
+ CamcorderProfile.QUALITY_720P,
+ CamcorderProfile.QUALITY_CIF,
+ CamcorderProfile.QUALITY_LOW,
+ CamcorderProfile.QUALITY_HIGH,
+ CamcorderProfile.QUALITY_QCIF,
+ CamcorderProfile.QUALITY_QVGA,
+ };
+
+ private List<Size> mSupportedVideoSizes;
+ private Surface mRecordingSurface;
+ private Surface mPreviewSurface;
+ private MediaRecorder mMediaRecorder;
+ private Size mPreviewSz = new Size(0, 0);
+ private String mOutMediaFileName;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ /**
+ * <p>
+ * Test basic camera recording.
+ * </p>
+ * <p>
+ * This test covers the typical basic use case of camera recording.
+ * MediaRecorder is used to record the audio and video, CamcorderProfile is
+ * used to configure the MediaRecorder. It goes through the pre-defined
+ * CamcorderProfile list, test each profile configuration and validate the
+ * recorded video. Preview is set to the video size.
+ * </p>
+ */
+ public void testBasicRecording() throws Exception {
+ for (int i = 0; i < mCameraIds.length; i++) {
+ try {
+ Log.i(TAG, "Testing basic recording for camera " + mCameraIds[i]);
+ // Re-use the MediaRecorder object for the same camera device.
+ mMediaRecorder = new MediaRecorder();
+ openDevice(mCameraIds[i]);
+ mSupportedVideoSizes = getSupportedVideoSizes(mCamera.getId(), mCameraManager,
+ VIDEO_SIZE_BOUND);
+
+ basicRecordingTestByCamera();
+ } finally {
+ closeDevice();
+ releasRecorder();
+ }
+ }
+ }
+
+ /**
+ * <p>
+ * Test camera recording for all supported sizes by using MediaRecorder.
+ * </p>
+ * <p>
+ * This test covers camera recording for all supported sizes by camera. MediaRecorder
+ * is used to encode the video. Preview is set to the video size. Recorded videos are
+ * validated according to the recording configuration.
+ * </p>
+ */
+ public void testSupportedVideoSizes() throws Exception {
+ for (int i = 0; i < mCameraIds.length; i++) {
+ try {
+ Log.i(TAG, "Testing supported video size recording for camera " + mCameraIds[i]);
+ // Re-use the MediaRecorder object for the same camera device.
+ mMediaRecorder = new MediaRecorder();
+ openDevice(mCameraIds[i]);
+
+ mSupportedVideoSizes = getSupportedVideoSizes(mCamera.getId(), mCameraManager,
+ VIDEO_SIZE_BOUND);
+
+ recordingSizeTestByCamera();
+ } finally {
+ closeDevice();
+ releasRecorder();
+ }
+ }
+ }
+
+ /**
+ * Test different start/stop orders of Camera and Recorder.
+ *
+ * <p>The recording should be working fine for any kind of start/stop orders.</p>
+ */
+ public void testCameraRecorderOrdering() {
+ // TODO: need implement
+ }
+
+ /**
+ * <p>
+ * Test camera recording for all supported sizes by using MediaCodec.
+ * </p>
+ * <p>
+ * This test covers video only recording for all supported sizes (camera and
+ * encoder). MediaCodec is used to encode the video. The recorded videos are
+ * validated according to the recording configuration.
+ * </p>
+ */
+ public void testMediaCodecRecording() throws Exception {
+ // TODO. Need implement.
+ }
+
+ /**
+ * <p>
+ * Test video snapshot for each camera by using MediaRecorder.
+ * </p>
+ * <p>
+ * This test covers video snapshot typical use case. The MediaRecorder is
+ * used to record the video for each supported CamcorderProfile
+ * configuration. The largest still capture size is selected to capture the
+ * JPEG image. The still capture images are validated according to the
+ * capture configuration. The preview/recording jitters are evaluated such
+ * that still capture doesn't disrupt the recording session.
+ * </p>
+ */
+ public void testVideoSnapShot() throws Exception {
+ // TODO. Need implement.
+ }
+
+ public void testTimelapseRecording() {
+ // TODO. Need implement.
+ }
+
+ /**
+ * Test camera recording by using each available CamcorderProfile for a
+ * given camera. preview size is set to the video size.
+ */
+ private void basicRecordingTestByCamera() throws Exception {
+ for (int profileId : mCamcorderProfileList) {
+ int cameraId = Integer.valueOf(mCamera.getId());
+ if (!CamcorderProfile.hasProfile(cameraId, profileId)) {
+ continue;
+ }
+
+ CamcorderProfile profile = CamcorderProfile.get(cameraId, profileId);
+ Size videoSz = new Size(profile.videoFrameWidth, profile.videoFrameHeight);
+ assertTrue("Video size " + videoSz.toString()
+ + " must be one of the camera device supported video size!",
+ mSupportedVideoSizes.contains(videoSz));
+
+ if (VERBOSE) {
+ Log.v(TAG, "Testing camera recording with video size " + videoSz.toString());
+ }
+
+ // Configure preview and recording surfaces.
+ mOutMediaFileName = VIDEO_FILE_PATH + "/test_video.mp4";
+ if (DEBUG_DUMP) {
+ mOutMediaFileName = VIDEO_FILE_PATH + "/test_video_" + cameraId + "_"
+ + videoSz.toString() + ".mp4";
+ }
+
+ prepareRecordingWithProfile(profile);
+
+ // prepare preview surface: preview size is same as video size.
+ preparePreview(videoSz);
+
+ // Start recording
+ startRecording(/* useMediaRecorder */true);
+
+ // Record certain duration.
+ SystemClock.sleep(RECORDING_DURATION_MS);
+
+ // Stop recording and preview
+ stopRecording(/* useMediaRecorder */true);
+
+ // Validation.
+ validateRecording(videoSz, RECORDING_DURATION_MS);
+ }
+ }
+
+ /**
+ * Test camera recording for each supported video size by camera, preview
+ * size is set to the video size.
+ */
+ private void recordingSizeTestByCamera() throws Exception {
+ for (Size sz : mSupportedVideoSizes) {
+ if (!isSupported(sz, VIDEO_FRAME_RATE, VIDEO_FRAME_RATE)) {
+ continue;
+ }
+
+ if (VERBOSE) {
+ Log.v(TAG, "Testing camera recording with video size " + sz.toString());
+ }
+
+ // Configure preview and recording surfaces.
+ mOutMediaFileName = VIDEO_FILE_PATH + "/test_video.mp4";
+ if (DEBUG_DUMP) {
+ mOutMediaFileName = VIDEO_FILE_PATH + "/test_video_" + mCamera.getId() + "_"
+ + sz.toString() + ".mp4";
+ }
+
+ // Use AVC and AAC a/v compression format.
+ prepareRecording(sz, VIDEO_FRAME_RATE);
+
+ // prepare preview surface: preview size is same as video size.
+ preparePreview(sz);
+
+ // Start recording
+ startRecording(/* useMediaRecorder */true);
+
+ // Record certain duration.
+ SystemClock.sleep(RECORDING_DURATION_MS);
+
+ // Stop recording and preview
+ stopRecording(/* useMediaRecorder */true);
+
+ // Validation.
+ validateRecording(sz, RECORDING_DURATION_MS);
+ }
+ }
+
+ /**
+ * Configure MediaRecorder recording session with CamcorderProfile, prepare
+ * the recording surface.
+ */
+ private void prepareRecordingWithProfile(CamcorderProfile profile)
+ throws Exception {
+ // Prepare MediaRecorder.
+ mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
+ mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
+ mMediaRecorder.setProfile(profile);
+ mMediaRecorder.setOutputFile(mOutMediaFileName);
+ mMediaRecorder.prepare();
+ mRecordingSurface = mMediaRecorder.getSurface();
+ assertNotNull("Recording surface must be non-null!", mRecordingSurface);
+ }
+
+ /**
+ * Configure MediaRecorder recording session with CamcorderProfile, prepare
+ * the recording surface. Use AVC for video compression, AAC for audio compression.
+ * Both are required for android devices by android CDD.
+ */
+ private void prepareRecording(Size sz, int frameRate) throws Exception {
+ // Prepare MediaRecorder.
+ mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
+ mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
+ mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
+ mMediaRecorder.setOutputFile(mOutMediaFileName);
+ mMediaRecorder.setVideoEncodingBitRate(getVideoBitRate(sz));
+ mMediaRecorder.setVideoFrameRate(frameRate);
+ mMediaRecorder.setVideoSize(sz.getWidth(), sz.getHeight());
+ mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
+ mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
+ mMediaRecorder.prepare();
+ mRecordingSurface = mMediaRecorder.getSurface();
+ assertNotNull("Recording surface must be non-null!", mRecordingSurface);
+ }
+
+ private void startRecording(boolean useMediaRecorder) throws Exception {
+ List<Surface> outputSurfaces = new ArrayList<Surface>(2);
+ assertTrue("Both preview and recording surfaces should be valid",
+ mPreviewSurface.isValid() && mRecordingSurface.isValid());
+ outputSurfaces.add(mPreviewSurface);
+ outputSurfaces.add(mRecordingSurface);
+ mCamera.configureOutputs(outputSurfaces);
+ mCameraListener.waitForState(STATE_BUSY, CAMERA_BUSY_TIMEOUT_MS);
+ mCameraListener.waitForState(STATE_IDLE, CAMERA_IDLE_TIMEOUT_MS);
+
+ CaptureRequest.Builder recordingRequestBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
+ // Make sure camera output frame rate is set to correct value.
+ Range<Integer> fpsRange = Range.create(VIDEO_FRAME_RATE, VIDEO_FRAME_RATE);
+ recordingRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange);
+ recordingRequestBuilder.addTarget(mRecordingSurface);
+ recordingRequestBuilder.addTarget(mPreviewSurface);
+ mCamera.setRepeatingRequest(recordingRequestBuilder.build(), null, null);
+
+ if (useMediaRecorder) {
+ mMediaRecorder.start();
+ } else {
+ // TODO: need implement MediaCodec path.
+ }
+ }
+
+ /**
+ * Set the preview surface with given size.
+ *
+ * <p>This method shouldn't be called from UI/mail thread.</p>
+ */
+ private void preparePreview(final Size sz) {
+ // Don't need change the preview size if it is same as current one.
+ if (sz.equals(mPreviewSz)) {
+ return;
+ }
+ mPreviewSz = sz;
+
+ Camera2SurfaceViewStubActivity stubActivity = getActivity();
+ final SurfaceHolder holder = stubActivity.getSurfaceView().getHolder();
+ Handler handler = new Handler(Looper.getMainLooper());
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ holder.setFixedSize(sz.getWidth(), sz.getHeight());
+ }
+ });
+
+ boolean res = stubActivity.waitForSurfaceSizeChanged(
+ WAIT_FOR_SURFACE_CHANGE_TIMEOUT_MS, sz.getWidth(), sz.getHeight());
+ assertTrue("wait for surface change to " + sz.toString() + " timed out", res);
+ mPreviewSurface = holder.getSurface();
+ assertTrue("Preview surface is invalid", mPreviewSurface.isValid());
+ }
+
+ private void stopCameraStreaming() throws Exception {
+ if (VERBOSE) {
+ Log.v(TAG, "Stopping camera streaming and waiting for idle");
+ }
+ // Stop repeating, wait for captures to complete, and disconnect from
+ // surfaces
+ mCamera.configureOutputs(/* outputs */null);
+ mCameraListener.waitForState(STATE_BUSY, CAMERA_BUSY_TIMEOUT_MS);
+ mCameraListener.waitForState(STATE_UNCONFIGURED, CAMERA_IDLE_TIMEOUT_MS);
+ }
+
+ private void stopRecording(boolean useMediaRecorder) throws Exception {
+ if (useMediaRecorder) {
+ stopCameraStreaming();
+
+ mMediaRecorder.stop();
+ // Can reuse the MediaRecorder object after reset.
+ mMediaRecorder.reset();
+ } else {
+ // TODO: need implement MediaCodec path.
+ }
+ if (mRecordingSurface != null) {
+ mRecordingSurface.release();
+ mRecordingSurface = null;
+ }
+ }
+
+ private void releasRecorder() {
+ if (mMediaRecorder != null) {
+ mMediaRecorder.release();
+ mMediaRecorder = null;
+ }
+ }
+
+ private void validateRecording(Size sz, int durationMs) throws Exception {
+ File outFile = new File(mOutMediaFileName);
+ assertTrue("No video is recorded", outFile.exists());
+
+ MediaPlayer mediaPlayer = new MediaPlayer();
+ try {
+ mediaPlayer.setDataSource(mOutMediaFileName);
+ mediaPlayer.prepare();
+ Size videoSz = new Size(mediaPlayer.getVideoWidth(), mediaPlayer.getVideoHeight());
+ assertTrue("Video size doesn't match", videoSz.equals(sz));
+ int duration = mediaPlayer.getDuration();
+ assertTrue(String.format(
+ "Video duration doesn't match: recorded %dms, expected %dms", duration,
+ durationMs), Math.abs(duration - durationMs) < DURATION_MARGIN_MS);
+ } finally {
+ mediaPlayer.release();
+ if (!DEBUG_DUMP) {
+ outFile.delete();
+ }
+ }
+ }
+
+ /**
+ * Calculate a video bit rate based on the size. The bit rate is scaled
+ * based on ratio of video size to 1080p size.
+ */
+ private int getVideoBitRate(Size sz) {
+ int rate = BIT_RATE_1080P;
+ float scaleFactor = sz.getHeight() * sz.getWidth() / (float)(1920 * 1080);
+ rate = (int)(rate * scaleFactor);
+
+ // Clamp to the MIN, MAX range.
+ return Math.max(BIT_RATE_MIN, Math.min(BIT_RATE_MAX, rate));
+ }
+
+ /**
+ * Check if the encoder and camera are able to support this size and frame rate.
+ * Assume the video compression format is AVC.
+ */
+ private boolean isSupported(Size sz, int captureRate, int encodingRate) throws Exception {
+ // Check camera capability.
+ if (!isSupportedByCamera(sz, captureRate)) {
+ return false;
+ }
+
+ // Check encode capability.
+ if (!isSupportedByAVCEncoder(sz, encodingRate)){
+ return false;
+ }
+
+ if(VERBOSE) {
+ Log.v(TAG, "Both encoder and camera support " + sz.toString() + "@" + encodingRate + "@"
+ + getVideoBitRate(sz) / 1000 + "Kbps");
+ }
+
+ return true;
+ }
+
+ private boolean isSupportedByCamera(Size sz, int frameRate) {
+ // Check if camera can support this sz and frame rate combination.
+ // FIXME: enable below code when bug 12957740 is fixed.
+ /*
+ CameraCharacteristics props = mCameraManager.getCameraCharacteristics(mCamera.getId());
+ assertNotNull("CameraCharacteristics shouldn't be null", props);
+ long[] minDurations = props.get(CameraCharacteristics.SCALER_AVAILABLE_MIN_FRAME_DURATIONS);
+ assertNotNull("android.scaler.availableMinFrameDurations shouldn't be null", minDurations);
+ // Layout of this array: (format, width, height, duration).
+ boolean foundSz = false;
+ int maxFrameRate = 0;
+ for (int i = 0; i < minDurations.length; i += 4) {
+ if (sz.getHeight() == minDurations[i + 1] && sz.getWidth() == minDurations[i + 2]) {
+ assertTrue("Min duration should be a positive number", minDurations[i + 3] > 0);
+ foundSz = true;
+ maxFrameRate = (int)(1e9f / minDurations[i + 3]);
+ break;
+ }
+ }
+ if (!foundSz || maxFrameRate < frameRate) {
+ return false;
+ }
+ */
+
+ return true;
+ }
+
+ /**
+ * Check if encoder can support this size and frame rate combination by querying
+ * MediaCodec capability. Check is based on size and frame rate. Ignore the bit rate
+ * as the bit rates targeted in this test are well below the bit rate max value specified
+ * by AVC specification for certain level.
+ */
+ private static boolean isSupportedByAVCEncoder(Size sz, int frameRate) {
+ String mimeType = "video/avc";
+ MediaCodecInfo codecInfo = getEncoderInfo(mimeType);
+ if (codecInfo == null) {
+ return false;
+ }
+ CodecCapabilities cap = codecInfo.getCapabilitiesForType(mimeType);
+ if (cap == null) {
+ return false;
+ }
+
+ int highestLevel = 0;
+ for (CodecProfileLevel lvl : cap.profileLevels) {
+ if (lvl.level > highestLevel) {
+ highestLevel = lvl.level;
+ }
+ }
+ // Don't support anything meaningful for level 1 or 2.
+ if (highestLevel <= CodecProfileLevel.AVCLevel2) {
+ return false;
+ }
+
+ if(VERBOSE) {
+ Log.v(TAG, "The highest level supported by encoder is: " + highestLevel);
+ }
+
+ // Put bitRate here for future use.
+ int maxW, maxH, bitRate;
+ // Max encoding speed.
+ int maxMacroblocksPerSecond = 0;
+ switch(highestLevel) {
+ case CodecProfileLevel.AVCLevel21:
+ maxW = 352;
+ maxH = 576;
+ bitRate = 4000000;
+ maxMacroblocksPerSecond = 19800;
+ break;
+ case CodecProfileLevel.AVCLevel22:
+ maxW = 720;
+ maxH = 480;
+ bitRate = 4000000;
+ maxMacroblocksPerSecond = 20250;
+ break;
+ case CodecProfileLevel.AVCLevel3:
+ maxW = 720;
+ maxH = 480;
+ bitRate = 10000000;
+ maxMacroblocksPerSecond = 40500;
+ break;
+ case CodecProfileLevel.AVCLevel31:
+ maxW = 1280;
+ maxH = 720;
+ bitRate = 14000000;
+ maxMacroblocksPerSecond = 108000;
+ break;
+ case CodecProfileLevel.AVCLevel32:
+ maxW = 1280;
+ maxH = 720;
+ bitRate = 20000000;
+ maxMacroblocksPerSecond = 216000;
+ break;
+ case CodecProfileLevel.AVCLevel4:
+ maxW = 1920;
+ maxH = 1088; // It should be 1088 in terms of AVC capability.
+ bitRate = 20000000;
+ maxMacroblocksPerSecond = 245760;
+ break;
+ case CodecProfileLevel.AVCLevel41:
+ maxW = 1920;
+ maxH = 1088; // It should be 1088 in terms of AVC capability.
+ bitRate = 50000000;
+ maxMacroblocksPerSecond = 245760;
+ break;
+ case CodecProfileLevel.AVCLevel42:
+ maxW = 2048;
+ maxH = 1088; // It should be 1088 in terms of AVC capability.
+ bitRate = 50000000;
+ maxMacroblocksPerSecond = 522240;
+ break;
+ case CodecProfileLevel.AVCLevel5:
+ maxW = 3672;
+ maxH = 1536;
+ bitRate = 135000000;
+ maxMacroblocksPerSecond = 589824;
+ break;
+ case CodecProfileLevel.AVCLevel51:
+ default:
+ maxW = 4096;
+ maxH = 2304;
+ bitRate = 240000000;
+ maxMacroblocksPerSecond = 983040;
+ break;
+ }
+
+ // Check size limit.
+ if (sz.getWidth() > maxW || sz.getHeight() > maxH) {
+ Log.i(TAG, "Requested resolution " + sz.toString() + " exceeds (" +
+ maxW + "," + maxH + ")");
+ return false;
+ }
+
+ // Check frame rate limit.
+ Size sizeInMb = new Size((sz.getWidth() + 15) / 16, (sz.getHeight() + 15) / 16);
+ int maxFps = maxMacroblocksPerSecond / (sizeInMb.getWidth() * sizeInMb.getHeight());
+ if (frameRate > maxFps) {
+ Log.i(TAG, "Requested frame rate " + frameRate + " exceeds " + maxFps);
+ return false;
+ }
+
+ return true;
+ }
+
+ private static MediaCodecInfo getEncoderInfo(String mimeType) {
+ int numCodecs = MediaCodecList.getCodecCount();
+ for (int i = 0; i < numCodecs; i++) {
+ MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
+
+ if (!codecInfo.isEncoder()) {
+ continue;
+ }
+
+ String[] types = codecInfo.getSupportedTypes();
+ for (int j = 0; j < types.length; j++) {
+ if (types[j].equalsIgnoreCase(mimeType)) {
+ return codecInfo;
+ }
+ }
+ }
+ return null;
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/StaticMetadataTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/StaticMetadataTest.java
new file mode 100644
index 0000000..3a0ae09
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/StaticMetadataTest.java
@@ -0,0 +1,146 @@
+/*
+ * 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 android.hardware.camera2.cts;
+
+import static android.hardware.camera2.CameraCharacteristics.*;
+
+import android.graphics.ImageFormat;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.cts.helpers.StaticMetadata;
+import android.hardware.camera2.cts.helpers.StaticMetadata.CheckLevel;
+import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
+import android.util.Log;
+import android.util.Size;
+
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * <p>
+ * This class covers the {@link CameraCharacteristics} tests that are not
+ * covered by {@link CaptureRequestTest} and {@link CameraCharacteristicsTest}
+ * (auto-generated tests that only do the non-null checks).
+ * </p>
+ * <p>
+ * Note that most of the tests in this class don't require camera open.
+ * </p>
+ */
+public class StaticMetadataTest extends Camera2AndroidTestCase {
+ private static final String TAG = "StaticMetadataTest";
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+ private static final float MIN_FPS_FOR_FULL_DEVICE = 20.0f;
+ private static final int RAW_STREAM_IDX = 0;
+ private static final int PROCESSED_NON_STALLING_STREAM_IDX = 1;
+ private static final int PROCESSED_STALLING_STREAM_IDX = 2;
+
+
+ /**
+ * Test the available capability for different hardware support level devices.
+ */
+ public void testHwSupportedLevel() throws Exception {
+ for (String id : mCameraIds) {
+ initStaticMetadata(id);
+ List<Integer> availabeCaps = mStaticInfo.getAvailableCapabilitiesChecked();
+
+ mCollector.expectTrue("All device must contains BACKWARD_COMPATIBLE capability",
+ availabeCaps.contains(REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE));
+
+ if (mStaticInfo.isHardwareLevelFull()) {
+ // Capability advertisement must be right.
+ mCollector.expectTrue("Full device must contains MANUAL_SENSOR capability",
+ availabeCaps.contains(REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR));
+ mCollector.expectTrue("Full device must contains MANUAL_POST_PROCESSING capability",
+ availabeCaps.contains(
+ REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING));
+
+ // Max resolution fps must be >= 20.
+ mCollector.expectTrue("Full device must support at least 20fps for max resolution",
+ getFpsForMaxSize(id) >= MIN_FPS_FOR_FULL_DEVICE);
+
+ // Need support per frame control
+ mCollector.expectTrue("Full device must support per frame control",
+ mStaticInfo.isPerFrameControlSupported());
+ }
+
+ // TODO: test all the keys mandatory for all capability devices.
+ }
+ }
+
+ /**
+ * Test max number of output stream reported by device
+ */
+ public void testMaxNumOutputStreams() throws Exception {
+ for (String id : mCameraIds) {
+ initStaticMetadata(id);
+ int[] maxNumStreams = mStaticInfo.getMaxNumOutputStreamsChecked();
+ assertTrue("max number of streams must be a 3 element array",
+ maxNumStreams.length == 3);
+
+ mCollector.expectTrue("max number of raw output streams must be a non negative number",
+ maxNumStreams[RAW_STREAM_IDX] >= 0);
+ mCollector.expectTrue("max number of processed (stalling) output streams must be >= 1",
+ maxNumStreams[PROCESSED_STALLING_STREAM_IDX] >= 1);
+
+ if (mStaticInfo.isHardwareLevelFull()) {
+ mCollector.expectTrue("max number of processed (non-stalling) output streams" +
+ "must be >= 3 for FULL device",
+ maxNumStreams[PROCESSED_NON_STALLING_STREAM_IDX] >= 3);
+ } else {
+ mCollector.expectTrue("max number of processed (non-stalling) output streams" +
+ "must be >= 2 for LIMITED device",
+ maxNumStreams[PROCESSED_NON_STALLING_STREAM_IDX] >= 2);
+ }
+ }
+
+ }
+
+ /**
+ * Test lens facing.
+ */
+ public void testLensFacing() throws Exception {
+ for (String id : mCameraIds) {
+ initStaticMetadata(id);
+ mStaticInfo.getLensFacingChecked();
+ }
+ }
+
+ private float getFpsForMaxSize(String cameraId) throws Exception {
+ HashMap<Size, Long> minFrameDurationMap =
+ mStaticInfo.getAvailableMinFrameDurationsForFormatChecked(ImageFormat.YUV_420_888);
+
+ Size[] sizes = CameraTestUtils.getSupportedSizeForFormat(ImageFormat.YUV_420_888,
+ cameraId, mCameraManager);
+ Size maxSize = CameraTestUtils.getMaxSize(sizes);
+ Long minDuration = minFrameDurationMap.get(maxSize);
+ if (VERBOSE) {
+ Log.v(TAG, "min frame duration for size " + maxSize + " is " + minDuration);
+ }
+ assertTrue("min duration for max size must be postive number",
+ minDuration != null && minDuration > 0);
+
+ return 1e9f / minDuration;
+ }
+
+ /**
+ * Initialize static metadata for a given camera id.
+ */
+ private void initStaticMetadata(String cameraId) throws Exception {
+ mCollector.setCameraId(cameraId);
+ mStaticInfo = new StaticMetadata(mCameraManager.getCameraCharacteristics(cameraId),
+ CheckLevel.COLLECT, /* collector */mCollector);
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/StillCaptureTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/StillCaptureTest.java
new file mode 100644
index 0000000..7daf859
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/StillCaptureTest.java
@@ -0,0 +1,1307 @@
+/*
+ * 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 android.hardware.camera2.cts;
+
+import static android.hardware.camera2.cts.CameraTestUtils.*;
+
+import android.graphics.ImageFormat;
+import android.graphics.Rect;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.location.Location;
+import android.location.LocationManager;
+import android.util.Size;
+import android.hardware.camera2.cts.CameraTestUtils.SimpleCaptureListener;
+import android.hardware.camera2.cts.CameraTestUtils.SimpleImageReaderListener;
+import android.hardware.camera2.cts.helpers.Camera2Focuser;
+import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase;
+import android.hardware.camera2.params.MeteringRectangle;
+import android.media.ExifInterface;
+import android.media.Image;
+import android.os.Build;
+import android.os.ConditionVariable;
+import android.util.Log;
+import android.util.Range;
+import android.util.Rational;
+
+import com.android.ex.camera2.exceptions.TimeoutRuntimeException;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+public class StillCaptureTest extends Camera2SurfaceViewTestCase {
+ private static final String TAG = "StillCaptureTest";
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final String JPEG_FILE_NAME = DEBUG_FILE_NAME_BASE + "/test.jpeg";
+ // 60 second to accommodate the possible long exposure time.
+ private static final int EXIF_DATETIME_ERROR_MARGIN_SEC = 60;
+ private static final float EXIF_FOCAL_LENGTH_ERROR_MARGIN = 0.001f;
+ // TODO: exposure time error margin need to be scaled with exposure time.
+ private static final float EXIF_EXPOSURE_TIME_ERROR_MARGIN_SEC = 0.002f;
+ private static final float EXIF_APERTURE_ERROR_MARGIN = 0.001f;
+ 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);
+ static {
+ sTestLocation0.setTime(1199145600L);
+ sTestLocation0.setLatitude(37.736071);
+ sTestLocation0.setLongitude(-122.441983);
+ sTestLocation0.setAltitude(21.0);
+
+ sTestLocation1.setTime(1199145601L);
+ sTestLocation1.setLatitude(0.736071);
+ sTestLocation1.setLongitude(0.441983);
+ sTestLocation1.setAltitude(1.0);
+
+ sTestLocation2.setTime(1199145602L);
+ sTestLocation2.setLatitude(-89.736071);
+ sTestLocation2.setLongitude(-179.441983);
+ sTestLocation2.setAltitude(100000.0);
+ }
+ // Exif test data vectors.
+ private static final ExifTestData[] EXIF_TEST_DATA = {
+ new ExifTestData(
+ /*gpsLocation*/ sTestLocation0,
+ /* orientation */90,
+ /* jpgQuality */(byte) 80,
+ /* thumbQuality */(byte) 75),
+ new ExifTestData(
+ /*gpsLocation*/ sTestLocation1,
+ /* orientation */180,
+ /* jpgQuality */(byte) 90,
+ /* thumbQuality */(byte) 85),
+ new ExifTestData(
+ /*gpsLocation*/ sTestLocation2,
+ /* orientation */270,
+ /* jpgQuality */(byte) 100,
+ /* thumbQuality */(byte) 100)
+ };
+
+ // Some exif tags that are not defined by ExifInterface but supported.
+ private static final String TAG_DATETIME_DIGITIZED = "DateTimeDigitized";
+ private static final String TAG_SUBSEC_TIME = "SubSecTime";
+ private static final String TAG_SUBSEC_TIME_ORIG = "SubSecTimeOriginal";
+ private static final String TAG_SUBSEC_TIME_DIG = "SubSecTimeDigitized";
+ private static final int EXIF_DATETIME_LENGTH = 19;
+ private static final int MAX_REGIONS_AE_INDEX = 0;
+ private static final int MAX_REGIONS_AWB_INDEX = 1;
+ private static final int MAX_REGIONS_AF_INDEX = 2;
+ private static final int WAIT_FOR_FOCUS_DONE_TIMEOUT_MS = 3000;
+ private static final double AE_COMPENSATION_ERROR_TOLERANCE = 0.2;
+ private static final int NUM_FRAMES_WAITED = 30;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ /**
+ * Test JPEG capture exif fields for each camera.
+ */
+ public void testJpegExif() throws Exception {
+ for (int i = 0; i < mCameraIds.length; i++) {
+ try {
+ Log.i(TAG, "Testing JPEG exif for Camera " + mCameraIds[i]);
+ openDevice(mCameraIds[i]);
+
+ jpegExifTestByCamera();
+ } finally {
+ closeDevice();
+ closeImageReader();
+ }
+ }
+ }
+
+ /**
+ * Test normal still capture sequence.
+ * <p>
+ * Preview and and jpeg output streams are configured. Max still capture
+ * size is used for jpeg capture. The sequence of still capture being test
+ * is: start preview, auto focus, precapture metering (if AE is not
+ * converged), then capture jpeg. The AWB and AE are in auto modes. AF mode
+ * is CONTINUOUS_PICTURE.
+ * </p>
+ */
+ public void testTakePicture() throws Exception{
+ for (String id : mCameraIds) {
+ try {
+ Log.i(TAG, "Testing touch for focus for Camera " + id);
+ openDevice(id);
+
+ takePictureTestByCamera(/*aeRegions*/null, /*awbRegions*/null, /*afRegions*/null);
+ } finally {
+ closeDevice();
+ closeImageReader();
+ }
+ }
+ }
+
+ /**
+ * Test basic Raw capture. Raw buffer avaiablility is checked, but raw buffer data is not.
+ */
+ public void testBasicRawCapture() throws Exception {
+ for (int i = 0; i < mCameraIds.length; i++) {
+ try {
+ Log.i(TAG, "Testing raw capture for Camera " + mCameraIds[i]);
+ openDevice(mCameraIds[i]);
+
+ rawCaptureTestByCamera();
+ } finally {
+ closeDevice();
+ closeImageReader();
+ }
+ }
+ }
+
+ /**
+ * Test touch for focus.
+ * <p>
+ * AF is in CAF mode when preview is started, test uses several pre-selected
+ * regions to simulate touches. Active scan is triggered to make sure the AF
+ * converges in reasonable time.
+ * </p>
+ */
+ public void testTouchForFocus() throws Exception {
+ for (String id : mCameraIds) {
+ try {
+ Log.i(TAG, "Testing touch for focus for Camera " + id);
+ openDevice(id);
+ int[] max3ARegions = mStaticInfo.get3aMaxRegionsChecked();
+ if (!(mStaticInfo.hasFocuser() && max3ARegions[MAX_REGIONS_AF_INDEX] > 0)) {
+ continue;
+ }
+
+ touchForFocusTestByCamera();
+ } finally {
+ closeDevice();
+ closeImageReader();
+ }
+ }
+ }
+
+ /**
+ * Test all combination of available preview sizes and still sizes.
+ * <p>
+ * For each still capture, Only the jpeg buffer is validated, capture
+ * result validation is covered by {@link #jpegExifTestByCamera} test.
+ * </p>
+ */
+ public void testStillPreviewCombination() throws Exception {
+ for (String id : mCameraIds) {
+ try {
+ Log.i(TAG, "Testing Still preview capture combination for Camera " + id);
+ openDevice(id);
+
+ previewStillCombinationTestByCamera();
+ } finally {
+ closeDevice();
+ closeImageReader();
+ }
+ }
+ }
+
+ /**
+ * Test AE compensation.
+ * <p>
+ * For each integer EV compensation setting: retrieve the exposure value (exposure time *
+ * sensitivity) with or without compensation, verify if the exposure value is legal (conformed
+ * to what static info has) and the ratio between two exposure values matches EV compensation
+ * setting. Also test for the behavior that exposure settings should be changed when AE
+ * compensation settings is changed, even when AE lock is ON.
+ * </p>
+ */
+ public void testAeCompensation() throws Exception {
+ for (String id : mCameraIds) {
+ try {
+ Log.i(TAG, "Testing AE compensation for Camera " + id);
+ openDevice(id);
+ if (!mStaticInfo.isPerFrameControlSupported()) {
+ continue;
+ }
+
+ aeCompensationTestByCamera();
+ } finally {
+ closeDevice();
+ closeImageReader();
+ }
+ }
+ }
+
+ /**
+ * Test Ae region for still capture.
+ */
+ public void testAeRegions() throws Exception {
+ for (String id : mCameraIds) {
+ try {
+ Log.i(TAG, "Testing AE regions for Camera " + id);
+ openDevice(id);
+
+ boolean aeRegionsSupported = isRegionsSupportedFor3A(MAX_REGIONS_AE_INDEX);
+ if (!mStaticInfo.isPerFrameControlSupported() || !aeRegionsSupported) {
+ continue;
+ }
+
+ ArrayList<MeteringRectangle[]> aeRegionTestCases = get3ARegionTestCasesForCamera();
+ for (MeteringRectangle[] aeRegions : aeRegionTestCases) {
+ takePictureTestByCamera(aeRegions, /*awbRegions*/null, /*afRegions*/null);
+ }
+ } finally {
+ closeDevice();
+ closeImageReader();
+ }
+ }
+ }
+
+ /**
+ * Test AWB region for still capture.
+ */
+ public void testAwbRegions() throws Exception {
+ for (String id : mCameraIds) {
+ try {
+ Log.i(TAG, "Testing AE regions for Camera " + id);
+ openDevice(id);
+
+ boolean awbRegionsSupported = isRegionsSupportedFor3A(MAX_REGIONS_AWB_INDEX);
+ if (!mStaticInfo.isPerFrameControlSupported() || !awbRegionsSupported) {
+ continue;
+ }
+
+ ArrayList<MeteringRectangle[]> awbRegionTestCases = get3ARegionTestCasesForCamera();
+ for (MeteringRectangle[] awbRegions : awbRegionTestCases) {
+ takePictureTestByCamera(/*aeRegions*/null, awbRegions, /*afRegions*/null);
+ }
+ } finally {
+ closeDevice();
+ closeImageReader();
+ }
+ }
+ }
+
+ /**
+ * Test Af region for still capture.
+ */
+ public void testAfRegions() throws Exception {
+ for (String id : mCameraIds) {
+ try {
+ Log.i(TAG, "Testing AE regions for Camera " + id);
+ openDevice(id);
+
+ boolean afRegionsSupported = isRegionsSupportedFor3A(MAX_REGIONS_AF_INDEX);
+ if (!mStaticInfo.isPerFrameControlSupported() || !afRegionsSupported) {
+ continue;
+ }
+
+ ArrayList<MeteringRectangle[]> afRegionTestCases = get3ARegionTestCasesForCamera();
+ for (MeteringRectangle[] afRegions : afRegionTestCases) {
+ takePictureTestByCamera(/*aeRegions*/null, /*awbRegions*/null, afRegions);
+ }
+ } finally {
+ closeDevice();
+ closeImageReader();
+ }
+ }
+ }
+
+ /**
+ * Test preview is still running after a still request
+ */
+ public void testPreviewPersistence() throws Exception {
+ for (String id : mCameraIds) {
+ try {
+ Log.i(TAG, "Testing preview persistence for Camera " + id);
+ openDevice(id);
+ previewPersistenceTestByCamera();
+ } finally {
+ closeDevice();
+ closeImageReader();
+ }
+ }
+ }
+
+ /**
+ * Start preview,take a picture and test preview is still running after snapshot
+ */
+ private void previewPersistenceTestByCamera() throws Exception {
+ Size maxStillSz = mOrderedStillSizes.get(0);
+ Size maxPreviewSz = mOrderedPreviewSizes.get(0);
+
+ SimpleCaptureListener resultListener = new SimpleCaptureListener();
+ SimpleCaptureListener stillResultListener = new SimpleCaptureListener();
+ SimpleImageReaderListener imageListener = new SimpleImageReaderListener();
+ CaptureRequest.Builder previewRequest =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ CaptureRequest.Builder stillRequest =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
+ prepareStillCaptureAndStartPreview(previewRequest, stillRequest, maxPreviewSz,
+ maxStillSz, resultListener, imageListener);
+
+ // make sure preview is actually running
+ waitForNumResults(resultListener, NUM_FRAMES_WAITED);
+
+ // take a picture
+ CaptureRequest request = stillRequest.build();
+ mCamera.capture(request, stillResultListener, mHandler);
+ stillResultListener.getCaptureResultForRequest(request,
+ WAIT_FOR_RESULT_TIMEOUT_MS);
+
+ // validate image
+ Image image = imageListener.getImage(CAPTURE_IMAGE_TIMEOUT_MS);
+ validateJpegCapture(image, maxStillSz);
+
+ // make sure preview is still running after still capture
+ waitForNumResults(resultListener, NUM_FRAMES_WAITED);
+
+ stopPreview();
+ return;
+ }
+
+ /**
+ * Take a picture for a given set of 3A regions for a particular camera.
+ * <p>
+ * Before take a still capture, it triggers an auto focus and lock it first,
+ * then wait for AWB to converge and lock it, then trigger a precapture
+ * metering sequence and wait for AE converged. After capture is received, the
+ * capture result and image are validated.
+ * </p>
+ *
+ * @param aeRegions AE regions for this capture
+ * @param awbRegions AWB regions for this capture
+ * @param afRegions AF regions for this capture
+ */
+ private void takePictureTestByCamera(
+ MeteringRectangle[] aeRegions, MeteringRectangle[] awbRegions,
+ MeteringRectangle[] afRegions) throws Exception {
+
+ boolean hasFocuser = mStaticInfo.hasFocuser();
+
+ Size maxStillSz = mOrderedStillSizes.get(0);
+ Size maxPreviewSz = mOrderedPreviewSizes.get(0);
+ CaptureResult result;
+ SimpleCaptureListener resultListener = new SimpleCaptureListener();
+ SimpleImageReaderListener imageListener = new SimpleImageReaderListener();
+ CaptureRequest.Builder previewRequest =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ CaptureRequest.Builder stillRequest =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
+ prepareStillCaptureAndStartPreview(previewRequest, stillRequest, maxPreviewSz,
+ maxStillSz, resultListener, imageListener);
+
+ // Set AE mode to ON_AUTO_FLASH if flash is available.
+ if (mStaticInfo.hasFlash()) {
+ previewRequest.set(CaptureRequest.CONTROL_AE_MODE,
+ CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
+ stillRequest.set(CaptureRequest.CONTROL_AE_MODE,
+ CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
+ }
+
+ Camera2Focuser focuser = null;
+ /**
+ * Step 1: trigger an auto focus run, and wait for AF locked.
+ */
+ boolean canSetAfRegion = hasFocuser && (afRegions != null) &&
+ isRegionsSupportedFor3A(MAX_REGIONS_AF_INDEX);
+ if (hasFocuser) {
+ SimpleAutoFocusListener afListener = new SimpleAutoFocusListener();
+ focuser = new Camera2Focuser(mCamera, mPreviewSurface, afListener,
+ mStaticInfo.getCharacteristics(), mHandler);
+ if (canSetAfRegion) {
+ stillRequest.set(CaptureRequest.CONTROL_AF_REGIONS, afRegions);
+ }
+ focuser.startAutoFocus(afRegions);
+ afListener.waitForAutoFocusDone(WAIT_FOR_FOCUS_DONE_TIMEOUT_MS);
+ }
+
+ /**
+ * Have to get the current AF mode to be used for other 3A repeating
+ * request, otherwise, the new AF mode in AE/AWB request could be
+ * different with existing repeating requests being sent by focuser,
+ * then it could make AF unlocked too early. Beside that, for still
+ * capture, AF mode must not be different with the one in current
+ * repeating request, otherwise, the still capture itself would trigger
+ * an AF mode change, and the AF lock would be lost for this capture.
+ */
+ int currentAfMode = CaptureRequest.CONTROL_AF_MODE_OFF;
+ if (hasFocuser) {
+ currentAfMode = focuser.getCurrentAfMode();
+ }
+ previewRequest.set(CaptureRequest.CONTROL_AF_MODE, currentAfMode);
+ stillRequest.set(CaptureRequest.CONTROL_AF_MODE, currentAfMode);
+
+ /**
+ * Step 2: AF is already locked, wait for AWB converged, then lock it.
+ */
+ resultListener = new SimpleCaptureListener();
+ boolean canSetAwbRegion =
+ (awbRegions != null) && isRegionsSupportedFor3A(MAX_REGIONS_AWB_INDEX);
+ if (canSetAwbRegion) {
+ previewRequest.set(CaptureRequest.CONTROL_AWB_REGIONS, awbRegions);
+ stillRequest.set(CaptureRequest.CONTROL_AWB_REGIONS, awbRegions);
+ }
+ mCamera.setRepeatingRequest(previewRequest.build(), resultListener, mHandler);
+ waitForResultValue(resultListener, CaptureResult.CONTROL_AWB_STATE,
+ CaptureResult.CONTROL_AWB_STATE_CONVERGED, NUM_RESULTS_WAIT_TIMEOUT);
+ previewRequest.set(CaptureRequest.CONTROL_AWB_LOCK, true);
+ mCamera.setRepeatingRequest(previewRequest.build(), resultListener, mHandler);
+ // Validate the next result immediately for region and mode.
+ result = resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
+ mCollector.expectEquals("AWB mode in result and request should be same",
+ previewRequest.get(CaptureRequest.CONTROL_AWB_MODE),
+ result.get(CaptureResult.CONTROL_AWB_MODE));
+ if (canSetAwbRegion) {
+ MeteringRectangle[] resultAwbRegions =
+ getValueNotNull(result, CaptureResult.CONTROL_AWB_REGIONS);
+ mCollector.expectEquals("AWB regions in result and request should be same",
+ awbRegions, resultAwbRegions);
+ }
+
+ /**
+ * Step 3: trigger an AE precapture metering sequence and wait for AE converged.
+ */
+ resultListener = new SimpleCaptureListener();
+ boolean canSetAeRegion =
+ (aeRegions != null) && isRegionsSupportedFor3A(MAX_REGIONS_AE_INDEX);
+ if (canSetAeRegion) {
+ previewRequest.set(CaptureRequest.CONTROL_AE_REGIONS, aeRegions);
+ stillRequest.set(CaptureRequest.CONTROL_AE_REGIONS, aeRegions);
+ }
+ mCamera.setRepeatingRequest(previewRequest.build(), resultListener, mHandler);
+ previewRequest.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
+ CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);
+ mCamera.capture(previewRequest.build(), resultListener, mHandler);
+ waitForAeStable(resultListener);
+ // Validate the next result immediately for region and mode.
+ result = resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
+ mCollector.expectEquals("AE mode in result and request should be same",
+ previewRequest.get(CaptureRequest.CONTROL_AE_MODE),
+ result.get(CaptureResult.CONTROL_AE_MODE));
+ if (canSetAeRegion) {
+ MeteringRectangle[] resultAeRegions =
+ getValueNotNull(result, CaptureResult.CONTROL_AE_REGIONS);
+ mCollector.expectEquals("AE regions in result and request should be same",
+ aeRegions, resultAeRegions);
+ }
+
+ /**
+ * Step 4: take a picture when all 3A are in good state.
+ */
+ resultListener = new SimpleCaptureListener();
+ CaptureRequest request = stillRequest.build();
+ mCamera.capture(request, resultListener, mHandler);
+ // Validate the next result immediately for region and mode.
+ result = resultListener.getCaptureResultForRequest(request, WAIT_FOR_RESULT_TIMEOUT_MS);
+ mCollector.expectEquals("AF mode in result and request should be same",
+ stillRequest.get(CaptureRequest.CONTROL_AF_MODE),
+ result.get(CaptureResult.CONTROL_AF_MODE));
+ if (canSetAfRegion) {
+ MeteringRectangle[] resultAfRegions =
+ getValueNotNull(result, CaptureResult.CONTROL_AF_REGIONS);
+ mCollector.expectEquals("AF regions in result and request should be same",
+ afRegions, resultAfRegions);
+ }
+
+ if (hasFocuser) {
+ // Unlock auto focus.
+ focuser.cancelAutoFocus();
+ }
+
+ // validate image
+ Image image = imageListener.getImage(CAPTURE_IMAGE_TIMEOUT_MS);
+ validateJpegCapture(image, maxStillSz);
+
+ stopPreview();
+ }
+
+ /**
+ * Test touch region for focus by camera.
+ */
+ private void touchForFocusTestByCamera() throws Exception {
+ SimpleCaptureListener listener = new SimpleCaptureListener();
+ CaptureRequest.Builder requestBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ Size maxPreviewSz = mOrderedPreviewSizes.get(0);
+ startPreview(requestBuilder, maxPreviewSz, listener);
+
+ SimpleAutoFocusListener afListener = new SimpleAutoFocusListener();
+ Camera2Focuser focuser = new Camera2Focuser(mCamera, mPreviewSurface, afListener,
+ mStaticInfo.getCharacteristics(), mHandler);
+ ArrayList<MeteringRectangle[]> testAfRegions = get3ARegionTestCasesForCamera();
+
+ for (MeteringRectangle[] afRegions : testAfRegions) {
+ focuser.touchForAutoFocus(afRegions);
+ afListener.waitForAutoFocusDone(WAIT_FOR_FOCUS_DONE_TIMEOUT_MS);
+ focuser.cancelAutoFocus();
+ }
+ }
+
+ private void previewStillCombinationTestByCamera() throws Exception {
+ SimpleCaptureListener resultListener = new SimpleCaptureListener();
+ SimpleImageReaderListener imageListener = new SimpleImageReaderListener();
+
+ for (Size stillSz : mOrderedStillSizes)
+ for (Size previewSz : mOrderedPreviewSizes) {
+ if (VERBOSE) {
+ Log.v(TAG, "Testing JPEG capture size " + stillSz.toString()
+ + " with preview size " + previewSz.toString() + " for camera "
+ + mCamera.getId());
+ }
+ CaptureRequest.Builder previewRequest =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ CaptureRequest.Builder stillRequest =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
+ prepareStillCaptureAndStartPreview(previewRequest, stillRequest, previewSz,
+ stillSz, resultListener, imageListener);
+ mCamera.capture(stillRequest.build(), resultListener, mHandler);
+ Image image = imageListener.getImage(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.
+ stopPreview();
+ }
+ }
+
+ /**
+ * Basic raw capture test for each camera.
+ */
+ private void rawCaptureTestByCamera() throws Exception {
+ Size maxPreviewSz = mOrderedPreviewSizes.get(0);
+ Size[] rawSizes = mStaticInfo.getRawOutputSizesChecked();
+ for (Size size : rawSizes) {
+ if (VERBOSE) {
+ Log.v(TAG, "Testing Raw capture with size " + size.toString()
+ + ", preview size " + maxPreviewSz);
+ }
+
+ // Prepare raw capture and start preview.
+ CaptureRequest.Builder previewBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ CaptureRequest.Builder rawBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
+ SimpleCaptureListener resultListener = new SimpleCaptureListener();
+ SimpleImageReaderListener imageListener = new SimpleImageReaderListener();
+ prepareRawCaptureAndStartPreview(previewBuilder, rawBuilder, maxPreviewSz, size,
+ resultListener, imageListener);
+
+ CaptureRequest rawRequest = rawBuilder.build();
+ mCamera.capture(rawRequest, resultListener, mHandler);
+
+ Image image = imageListener.getImage(CAPTURE_IMAGE_TIMEOUT_MS);
+ validateRaw16Image(image, size);
+ if (DEBUG) {
+ byte[] rawBuffer = getDataFromImage(image);
+ String rawFileName =
+ DEBUG_FILE_NAME_BASE + "/test" + "_" + size.toString() +
+ "_cam" + mCamera.getId() + ".raw16";
+ Log.d(TAG, "Dump raw file into " + rawFileName);
+ dumpFile(rawFileName, rawBuffer);
+ }
+
+ verifyRawCaptureResult(rawRequest, resultListener);
+ stopPreview();
+ }
+ }
+
+ private void verifyRawCaptureResult(CaptureRequest rawRequest,
+ SimpleCaptureListener resultListener) {
+ // TODO: validate DNG metadata tags.
+ }
+
+ private static boolean areGpsFieldsEqual(Location a, Location b) {
+ if (a == null || b == null) {
+ return false;
+ }
+
+ return a.getTime() == b.getTime() && a.getLatitude() == b.getLatitude() &&
+ a.getLongitude() == b.getLongitude() && a.getAltitude() == b.getAltitude() &&
+ a.getProvider() == b.getProvider();
+ }
+ /**
+ * Issue a Jpeg capture and validate the exif information.
+ * <p>
+ * TODO: Differentiate full and limited device, some of the checks rely on
+ * per frame control and synchronization, most of them don't.
+ * </p>
+ */
+ private void jpegExifTestByCamera() throws Exception {
+ Size maxPreviewSz = mOrderedPreviewSizes.get(0);
+ Size maxStillSz = mOrderedStillSizes.get(0);
+ if (VERBOSE) {
+ Log.v(TAG, "Testing JPEG exif with jpeg size " + maxStillSz.toString()
+ + ", preview size " + maxPreviewSz);
+ }
+
+ // prepare capture and start preview.
+ CaptureRequest.Builder previewBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ CaptureRequest.Builder stillBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
+ SimpleCaptureListener resultListener = new SimpleCaptureListener();
+ SimpleImageReaderListener imageListener = new SimpleImageReaderListener();
+ prepareStillCaptureAndStartPreview(previewBuilder, stillBuilder, maxPreviewSz, maxStillSz,
+ resultListener, imageListener);
+
+ // Set the jpeg keys, then issue a capture
+ Size[] thumbnailSizes = mStaticInfo.getAvailableThumbnailSizesChecked();
+ Size maxThumbnailSize = thumbnailSizes[thumbnailSizes.length - 1];
+ Size[] testThumbnailSizes = new Size[EXIF_TEST_DATA.length];
+ Arrays.fill(testThumbnailSizes, maxThumbnailSize);
+ // Make sure thumbnail size (0, 0) is covered.
+ testThumbnailSizes[0] = new Size(0, 0);
+
+ for (int i = 0; i < EXIF_TEST_DATA.length; i++) {
+ /**
+ * Capture multiple shots.
+ *
+ * Verify that:
+ * - Capture request get values are same as were set.
+ * - capture result's exif data is the same as was set by
+ * the capture request.
+ * - new tags in the result set by the camera service are
+ * present and semantically correct.
+ */
+ stillBuilder.set(CaptureRequest.JPEG_THUMBNAIL_SIZE, testThumbnailSizes[i]);
+ stillBuilder.set(CaptureRequest.JPEG_GPS_LOCATION, EXIF_TEST_DATA[i].gpsLocation);
+ stillBuilder.set(CaptureRequest.JPEG_ORIENTATION, EXIF_TEST_DATA[i].jpegOrientation);
+ stillBuilder.set(CaptureRequest.JPEG_QUALITY, EXIF_TEST_DATA[i].jpegQuality);
+ stillBuilder.set(CaptureRequest.JPEG_THUMBNAIL_QUALITY,
+ EXIF_TEST_DATA[i].thumbnailQuality);
+
+ // Validate request set and get.
+ mCollector.expectEquals("JPEG thumbnail size request set and get should match",
+ testThumbnailSizes[i],
+ stillBuilder.get(CaptureRequest.JPEG_THUMBNAIL_SIZE));
+ mCollector.expectTrue("GPS locations request set and get should match.",
+ areGpsFieldsEqual(EXIF_TEST_DATA[i].gpsLocation,
+ stillBuilder.get(CaptureRequest.JPEG_GPS_LOCATION)));
+ mCollector.expectEquals("JPEG orientation request set and get should match",
+ EXIF_TEST_DATA[i].jpegOrientation,
+ stillBuilder.get(CaptureRequest.JPEG_ORIENTATION));
+ mCollector.expectEquals("JPEG quality request set and get should match",
+ EXIF_TEST_DATA[i].jpegQuality, stillBuilder.get(CaptureRequest.JPEG_QUALITY));
+ mCollector.expectEquals("JPEG thumbnail quality request set and get should match",
+ EXIF_TEST_DATA[i].thumbnailQuality,
+ stillBuilder.get(CaptureRequest.JPEG_THUMBNAIL_QUALITY));
+
+ // Capture a jpeg image.
+ CaptureRequest request = stillBuilder.build();
+ mCamera.capture(request, resultListener, mHandler);
+ CaptureResult stillResult =
+ resultListener.getCaptureResultForRequest(request, NUM_RESULTS_WAIT_TIMEOUT);
+ Image image = imageListener.getImage(CAPTURE_IMAGE_TIMEOUT_MS);
+ basicValidateJpegImage(image, maxStillSz);
+
+ byte[] jpegBuffer = getDataFromImage(image);
+ // Have to dump into a file to be able to use ExifInterface
+ dumpFile(JPEG_FILE_NAME, jpegBuffer);
+ ExifInterface exif = new ExifInterface(JPEG_FILE_NAME);
+
+ if (testThumbnailSizes[i].equals(new Size(0,0))) {
+ mCollector.expectTrue(
+ "Jpeg shouldn't have thumbnail when thumbnail size is (0, 0)",
+ !exif.hasThumbnail());
+ } else {
+ mCollector.expectTrue(
+ "Jpeg must have thumbnail for thumbnail size " + testThumbnailSizes[i],
+ exif.hasThumbnail());
+ }
+
+ // Validate capture result vs. request
+ mCollector.expectEquals("JPEG thumbnail size result and request should match",
+ testThumbnailSizes[i],
+ stillResult.get(CaptureResult.JPEG_THUMBNAIL_SIZE));
+ if (mCollector.expectKeyValueNotNull(stillResult, CaptureResult.JPEG_GPS_LOCATION) !=
+ null) {
+ mCollector.expectTrue("GPS location result and request should match.",
+ areGpsFieldsEqual(EXIF_TEST_DATA[i].gpsLocation,
+ stillResult.get(CaptureResult.JPEG_GPS_LOCATION)));
+ }
+ mCollector.expectEquals("JPEG orientation result and request should match",
+ EXIF_TEST_DATA[i].jpegOrientation,
+ stillResult.get(CaptureResult.JPEG_ORIENTATION));
+ mCollector.expectEquals("JPEG quality result and request should match",
+ EXIF_TEST_DATA[i].jpegQuality, stillResult.get(CaptureResult.JPEG_QUALITY));
+ mCollector.expectEquals("JPEG thumbnail quality result and request should match",
+ EXIF_TEST_DATA[i].thumbnailQuality,
+ stillResult.get(CaptureResult.JPEG_THUMBNAIL_QUALITY));
+
+ // Validate other exif tags.
+ jpegTestExifExtraTags(exif, maxStillSz, stillResult);
+ }
+ }
+
+ private void jpegTestExifExtraTags(ExifInterface exif, Size jpegSize, CaptureResult result)
+ throws ParseException {
+ /**
+ * TAG_IMAGE_WIDTH and TAG_IMAGE_LENGTH and TAG_ORIENTATION.
+ * Orientation and exif width/height need to be tested carefully, two cases:
+ *
+ * 1. Device rotate the image buffer physically, then exif width/height may not match
+ * the requested still capture size, we need swap them to check.
+ *
+ * 2. Device use the exif tag to record the image orientation, it doesn't rotate
+ * the jpeg image buffer itself. In this case, the exif width/height should always match
+ * the requested still capture size, and the exif orientation should always match the
+ * requested orientation.
+ *
+ */
+ int exifWidth = exif.getAttributeInt(ExifInterface.TAG_IMAGE_WIDTH, /*defaultValue*/0);
+ int exifHeight = exif.getAttributeInt(ExifInterface.TAG_IMAGE_LENGTH, /*defaultValue*/0);
+ Size exifSize = new Size(exifWidth, exifHeight);
+ // Orientation could be missing, which is ok, default to 0.
+ int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,
+ /*defaultValue*/-1);
+ // Get requested orientation from result, because they should be same.
+ if (mCollector.expectKeyValueNotNull(result, CaptureResult.JPEG_ORIENTATION) != null) {
+ int requestedOrientation = result.get(CaptureResult.JPEG_ORIENTATION);
+ final int ORIENTATION_MIN = ExifInterface.ORIENTATION_UNDEFINED;
+ final int ORIENTATION_MAX = ExifInterface.ORIENTATION_ROTATE_270;
+ boolean orientationValid = mCollector.expectTrue(String.format(
+ "Exif orientation must be in range of [%d, %d]",
+ ORIENTATION_MIN, ORIENTATION_MAX),
+ exifOrientation >= ORIENTATION_MIN && exifOrientation <= ORIENTATION_MAX);
+ if (orientationValid) {
+ /**
+ * Device captured image doesn't respect the requested orientation,
+ * which means it rotates the image buffer physically. Then we
+ * should swap the exif width/height accordingly to compare.
+ */
+ boolean deviceRotatedImage = exifOrientation == ExifInterface.ORIENTATION_UNDEFINED;
+
+ if (deviceRotatedImage) {
+ // Case 1.
+ boolean needSwap = (requestedOrientation % 180 == 90);
+ if (needSwap) {
+ exifSize = new Size(exifHeight, exifWidth);
+ }
+ } else {
+ // Case 2.
+ mCollector.expectEquals("Exif orientaiton should match requested orientation",
+ requestedOrientation, getExifOrientationInDegress(exifOrientation));
+ }
+ }
+ }
+
+ /**
+ * Ideally, need check exifSize == jpegSize == actual buffer size. But
+ * jpegSize == jpeg decode bounds size(from jpeg jpeg frame
+ * header, not exif) was validated in ImageReaderTest, no need to
+ * validate again here.
+ */
+ mCollector.expectEquals("Exif size should match jpeg capture size", jpegSize, exifSize);
+
+ // TAG_DATETIME, it should be local time
+ long currentTimeInMs = System.currentTimeMillis();
+ long currentTimeInSecond = currentTimeInMs / 1000;
+ Date date = new Date(currentTimeInMs);
+ String localDatetime = new SimpleDateFormat("yyyy:MM:dd HH:").format(date);
+ String dateTime = exif.getAttribute(ExifInterface.TAG_DATETIME);
+ if (mCollector.expectTrue("Exif TAG_DATETIME shouldn't be null", dateTime != null)) {
+ mCollector.expectTrue("Exif TAG_DATETIME is wrong",
+ dateTime.length() == EXIF_DATETIME_LENGTH);
+ long exifTimeInSecond =
+ new SimpleDateFormat("yyyy:MM:dd HH:mm:ss").parse(dateTime).getTime() / 1000;
+ long delta = currentTimeInSecond - exifTimeInSecond;
+ mCollector.expectTrue("Capture time deviates too much from the current time",
+ Math.abs(delta) < EXIF_DATETIME_ERROR_MARGIN_SEC);
+ // It should be local time.
+ mCollector.expectTrue("Exif date time should be local time",
+ dateTime.startsWith(localDatetime));
+ }
+
+ // TAG_FOCAL_LENGTH.
+ float[] focalLengths = mStaticInfo.getAvailableFocalLengthsChecked();
+ float exifFocalLength = (float)exif.getAttributeDouble(ExifInterface.TAG_FOCAL_LENGTH, -1);
+ mCollector.expectEquals("Focal length should match",
+ getClosestValueInArray(focalLengths, exifFocalLength),
+ exifFocalLength, EXIF_FOCAL_LENGTH_ERROR_MARGIN);
+ // More checks for focal length.
+ mCollector.expectEquals("Exif focal length should match capture result",
+ validateFocalLength(result), exifFocalLength);
+
+ // TAG_EXPOSURE_TIME
+ // ExifInterface API gives exposure time value in the form of float instead of rational
+ String exposureTime = exif.getAttribute(ExifInterface.TAG_EXPOSURE_TIME);
+ mCollector.expectNotNull("Exif TAG_EXPOSURE_TIME shouldn't be null", exposureTime);
+ if (exposureTime != null) {
+ double exposureTimeValue = Double.parseDouble(exposureTime);
+ long expTimeResult = result.get(CaptureResult.SENSOR_EXPOSURE_TIME);
+ double expected = expTimeResult / 1e9;
+ mCollector.expectEquals("Exif exposure time doesn't match", expected,
+ exposureTimeValue, EXIF_EXPOSURE_TIME_ERROR_MARGIN_SEC);
+ }
+
+ // TAG_APERTURE
+ // ExifInterface API gives aperture value in the form of float instead of rational
+ String exifAperture = exif.getAttribute(ExifInterface.TAG_APERTURE);
+ float[] apertures = mStaticInfo.getAvailableAperturesChecked();
+ mCollector.expectNotNull("Exif TAG_APERTURE shouldn't be null", exifAperture);
+ if (exifAperture != null) {
+ float apertureValue = Float.parseFloat(exifAperture);
+ mCollector.expectEquals("Aperture value should match",
+ getClosestValueInArray(apertures, apertureValue),
+ apertureValue, EXIF_APERTURE_ERROR_MARGIN);
+ // More checks for aperture.
+ mCollector.expectEquals("Exif aperture length should match capture result",
+ validateAperture(result), apertureValue);
+ }
+
+ /**
+ * TAG_FLASH. TODO: For full devices, can check a lot more info
+ * (http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html#Flash)
+ */
+ String flash = exif.getAttribute(ExifInterface.TAG_FLASH);
+ mCollector.expectNotNull("Exif TAG_FLASH shouldn't be null", flash);
+
+ /**
+ * TAG_WHITE_BALANCE. TODO: For full devices, with the DNG tags, we
+ * should be able to cross-check android.sensor.referenceIlluminant.
+ */
+ String whiteBalance = exif.getAttribute(ExifInterface.TAG_WHITE_BALANCE);
+ mCollector.expectNotNull("Exif TAG_WHITE_BALANCE shouldn't be null", whiteBalance);
+
+ // TAG_MAKE
+ String make = exif.getAttribute(ExifInterface.TAG_MAKE);
+ mCollector.expectEquals("Exif TAG_MAKE is incorrect", Build.MANUFACTURER, make);
+
+ // TAG_MODEL
+ String model = exif.getAttribute(ExifInterface.TAG_MODEL);
+ mCollector.expectEquals("Exif TAG_MODEL is incorrect", Build.MODEL, model);
+
+
+ // TAG_ISO
+ int iso = exif.getAttributeInt(ExifInterface.TAG_ISO, /*defaultValue*/-1);
+ int expectedIso = result.get(CaptureResult.SENSOR_SENSITIVITY);
+ mCollector.expectEquals("Exif TAG_ISO is incorrect", expectedIso, iso);
+
+ // TAG_DATETIME_DIGITIZED (a.k.a Create time for digital cameras).
+ String digitizedTime = exif.getAttribute(TAG_DATETIME_DIGITIZED);
+ mCollector.expectNotNull("Exif TAG_DATETIME_DIGITIZED shouldn't be null", digitizedTime);
+ if (digitizedTime != null) {
+ String expectedDateTime = exif.getAttribute(ExifInterface.TAG_DATETIME);
+ mCollector.expectNotNull("Exif TAG_DATETIME shouldn't be null", expectedDateTime);
+ if (expectedDateTime != null) {
+ mCollector.expectEquals("dataTime should match digitizedTime",
+ expectedDateTime, digitizedTime);
+ }
+ }
+
+ /**
+ * TAG_SUBSEC_TIME. Since the sub second tag strings are truncated to at
+ * most 9 digits in ExifInterface implementation, use getAttributeInt to
+ * sanitize it. When the default value -1 is returned, it means that
+ * this exif tag either doesn't exist or is a non-numerical invalid
+ * string. Same rule applies to the rest of sub second tags.
+ */
+ int subSecTime = exif.getAttributeInt(TAG_SUBSEC_TIME, /*defaultValue*/-1);
+ mCollector.expectTrue("Exif TAG_SUBSEC_TIME value is null or invalid!", subSecTime > 0);
+
+ // TAG_SUBSEC_TIME_ORIG
+ int subSecTimeOrig = exif.getAttributeInt(TAG_SUBSEC_TIME_ORIG, /*defaultValue*/-1);
+ mCollector.expectTrue("Exif TAG_SUBSEC_TIME_ORIG value is null or invalid!",
+ subSecTimeOrig > 0);
+
+ // TAG_SUBSEC_TIME_DIG
+ int subSecTimeDig = exif.getAttributeInt(TAG_SUBSEC_TIME_DIG, /*defaultValue*/-1);
+ mCollector.expectTrue(
+ "Exif TAG_SUBSEC_TIME_DIG value is null or invalid!", subSecTimeDig > 0);
+ }
+
+ private int getExifOrientationInDegress(int exifOrientation) {
+ switch (exifOrientation) {
+ case ExifInterface.ORIENTATION_NORMAL:
+ return 0;
+ case ExifInterface.ORIENTATION_ROTATE_90:
+ return 90;
+ case ExifInterface.ORIENTATION_ROTATE_180:
+ return 180;
+ case ExifInterface.ORIENTATION_ROTATE_270:
+ return 270;
+ default:
+ mCollector.addMessage("It is impossible to get non 0, 90, 180, 270 degress exif" +
+ "info based on the request orientation range");
+ return 0;
+ }
+ }
+ /**
+ * Immutable class wrapping the exif test data.
+ */
+ private static class ExifTestData {
+ public final Location gpsLocation;
+ public final int jpegOrientation;
+ public final byte jpegQuality;
+ public final byte thumbnailQuality;
+
+ public ExifTestData(Location location, int orientation,
+ byte jpgQuality, byte thumbQuality) {
+ gpsLocation = location;
+ jpegOrientation = orientation;
+ jpegQuality = jpgQuality;
+ thumbnailQuality = thumbQuality;
+ }
+ }
+
+ private void aeCompensationTestByCamera() throws Exception {
+ Range<Integer> compensationRange = mStaticInfo.getAeCompensationRangeChecked();
+ Rational step = mStaticInfo.getAeCompensationStepChecked();
+ int stepsPerEv = (int) Math.round(1.0 / step.toFloat());
+ int numSteps = (compensationRange.getUpper() - compensationRange.getLower()) / stepsPerEv;
+
+ Size maxStillSz = mOrderedStillSizes.get(0);
+ Size maxPreviewSz = mOrderedPreviewSizes.get(0);
+ SimpleCaptureListener resultListener = new SimpleCaptureListener();
+ SimpleImageReaderListener imageListener = new SimpleImageReaderListener();
+ CaptureRequest.Builder previewRequest =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ CaptureRequest.Builder stillRequest =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
+ stillRequest.set(CaptureRequest.CONTROL_AE_LOCK, true);
+
+ // Minimum exposure settings is mostly static while maximum exposure setting depends on
+ // frame rate range which in term depends on capture request.
+ long minExposureValue = mStaticInfo.getSensitivityMinimumOrDefault() *
+ mStaticInfo.getExposureMinimumOrDefault() / 1000;
+ long maxSensitivity = mStaticInfo.getSensitivityMaximumOrDefault();
+ long maxExposureTimeUs = mStaticInfo.getExposureMaximumOrDefault() / 1000;
+ long maxExposureValuePreview = getMaxExposureValue(previewRequest, maxExposureTimeUs,
+ maxSensitivity);
+ long maxExposureValueStill = getMaxExposureValue(stillRequest, maxExposureTimeUs,
+ maxSensitivity);
+
+ // Set the max number of images to be same as the burst count, as the verification
+ // could be much slower than producing rate, and we don't want to starve producer.
+ prepareStillCaptureAndStartPreview(previewRequest, stillRequest, maxPreviewSz,
+ maxStillSz, resultListener, numSteps, imageListener);
+
+ for (int i = 0; i <= numSteps; i++) {
+ int exposureCompensation = i * stepsPerEv + compensationRange.getLower();
+
+ // Wait for AE to be stabilized before capture: CONVERGED or FLASH_REQUIRED.
+ waitForAeStable(resultListener);
+ CaptureResult result = resultListener.getCaptureResult(NUM_RESULTS_WAIT_TIMEOUT);
+
+ // get and check if current exposure value is valid
+ long normalExposureValue = getExposureValue(result);
+ mCollector.expectInRange("Exposure setting out of bound", normalExposureValue,
+ minExposureValue, maxExposureValuePreview);
+
+ // Only run the test if expectedExposureValue is within valid range
+ double expectedRatio = Math.pow(2.0, exposureCompensation / stepsPerEv);
+ long expectedExposureValue = (long) (normalExposureValue * expectedRatio);
+ if (expectedExposureValue < minExposureValue ||
+ expectedExposureValue > maxExposureValueStill) {
+ continue;
+ }
+
+ // Now issue exposure compensation and wait for AE locked. AE could take a few
+ // frames to go back to locked state
+ previewRequest.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION,
+ exposureCompensation);
+ previewRequest.set(CaptureRequest.CONTROL_AE_LOCK, true);
+ mCamera.setRepeatingRequest(previewRequest.build(), resultListener, mHandler);
+ waitForAeLocked(resultListener);
+
+ // Issue still capture
+ if (VERBOSE) {
+ Log.v(TAG, "Verifying capture result for ae compensation value "
+ + exposureCompensation);
+ }
+
+ stillRequest.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, exposureCompensation);
+ CaptureRequest request = stillRequest.build();
+ mCamera.capture(request, resultListener, mHandler);
+
+ result = resultListener.getCaptureResultForRequest(request, NUM_RESULTS_WAIT_TIMEOUT);
+
+ // Verify the exposure value compensates as requested
+ long compensatedExposureValue = getExposureValue(result);
+ mCollector.expectInRange("Exposure setting out of bound", compensatedExposureValue,
+ minExposureValue, maxExposureValueStill);
+ double observedRatio = (double) compensatedExposureValue / normalExposureValue;
+ double error = observedRatio / expectedRatio;
+ mCollector.expectInRange(String.format(
+ "Exposure compensation ratio exceeds error tolerence:"
+ + " expected(%f) observed(%f) ", expectedRatio, observedRatio),
+ error,
+ 1.0 - AE_COMPENSATION_ERROR_TOLERANCE,
+ 1.0 + AE_COMPENSATION_ERROR_TOLERANCE);
+
+
+ // TODO: enable below code once bug 14059883 is fixed.
+ /*
+ mCollector.expectEquals("Exposure compensation result should match requested value.",
+ aeCompensationValue,
+ result.get(CaptureResult.CONTROL_AE_EXPOSURE_COMPENSATION));
+ */
+
+ Image image = imageListener.getImage(CAPTURE_IMAGE_TIMEOUT_MS);
+ validateJpegCapture(image, maxStillSz);
+ image.close();
+
+ // Recover AE compensation and lock
+ previewRequest.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, 0);
+ previewRequest.set(CaptureRequest.CONTROL_AE_LOCK, false);
+ mCamera.setRepeatingRequest(previewRequest.build(), resultListener, mHandler);
+ }
+ }
+
+ private long getExposureValue(CaptureResult result) throws Exception {
+ int expTimeUs = (int) (getValueNotNull(result, CaptureResult.SENSOR_EXPOSURE_TIME) / 1000);
+ int sensitivity = getValueNotNull(result, CaptureResult.SENSOR_SENSITIVITY);
+ return expTimeUs * sensitivity;
+ }
+
+ private long getMaxExposureValue(CaptureRequest.Builder request, long maxExposureTimeUs,
+ long maxSensitivity) throws Exception {
+ Range<Integer> fpsRange = request.get(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE);
+ long maxFrameDurationUs = Math.round(1000000.0 / fpsRange.getLower());
+ long currentMaxExposureTimeUs = Math.min(maxFrameDurationUs, maxExposureTimeUs);
+ return currentMaxExposureTimeUs * maxSensitivity;
+ }
+
+
+ //----------------------------------------------------------------
+ //---------Below are common functions for all tests.--------------
+ //----------------------------------------------------------------
+
+ /**
+ * Simple validation of JPEG image size and format.
+ * <p>
+ * Only validate the image object sanity. It is fast, but doesn't actually
+ * check the buffer data. Assert is used here as it make no sense to
+ * continue the test if the jpeg image captured has some serious failures.
+ * </p>
+ *
+ * @param image The captured jpeg image
+ * @param expectedSize Expected capture jpeg size
+ */
+ private static void basicValidateJpegImage(Image image, Size expectedSize) {
+ Size imageSz = new Size(image.getWidth(), image.getHeight());
+ assertTrue(
+ String.format("Image size doesn't match (expected %s, actual %s) ",
+ expectedSize.toString(), imageSz.toString()), expectedSize.equals(imageSz));
+ assertEquals("Image format should be JPEG", ImageFormat.JPEG, image.getFormat());
+ assertNotNull("Image plane shouldn't be null", image.getPlanes());
+ assertEquals("Image plane number should be 1", 1, image.getPlanes().length);
+
+ // Jpeg decoding validate was done in ImageReaderTest, no need to duplicate the test here.
+ }
+
+ /**
+ * Validate standard raw (RAW16) capture image.
+ *
+ * @param image The raw16 format image captured
+ * @param rawSize The expected raw size
+ */
+ private static void validateRaw16Image(Image image, Size rawSize) {
+ CameraTestUtils.validateImage(image, rawSize.getWidth(), rawSize.getHeight(),
+ ImageFormat.RAW_SENSOR, /*filePath*/null);
+ }
+
+ /**
+ * Validate JPEG capture image object sanity and test.
+ * <p>
+ * In addition to image object sanity, this function also does the decoding
+ * test, which is slower.
+ * </p>
+ *
+ * @param image The JPEG image to be verified.
+ * @param jpegSize The JPEG capture size to be verified against.
+ */
+ private static void validateJpegCapture(Image image, Size jpegSize) {
+ CameraTestUtils.validateImage(image, jpegSize.getWidth(), jpegSize.getHeight(),
+ ImageFormat.JPEG, /*filePath*/null);
+ }
+
+ private static float getClosestValueInArray(float[] values, float target) {
+ int minIdx = 0;
+ float minDistance = Math.abs(values[0] - target);
+ for(int i = 0; i < values.length; i++) {
+ float distance = Math.abs(values[i] - target);
+ if (minDistance > distance) {
+ minDistance = distance;
+ minIdx = i;
+ }
+ }
+
+ return values[minIdx];
+ }
+
+ /**
+ * Validate and return the focal length.
+ *
+ * @param result Capture result to get the focal length
+ * @return Focal length from capture result or -1 if focal length is not available.
+ */
+ private float validateFocalLength(CaptureResult result) {
+ float[] focalLengths = mStaticInfo.getAvailableFocalLengthsChecked();
+ Float resultFocalLength = result.get(CaptureResult.LENS_FOCAL_LENGTH);
+ if (mCollector.expectTrue("Focal length is invalid",
+ resultFocalLength != null && resultFocalLength > 0)) {
+ List<Float> focalLengthList =
+ Arrays.asList(CameraTestUtils.toObject(focalLengths));
+ mCollector.expectTrue("Focal length should be one of the available focal length",
+ focalLengthList.contains(resultFocalLength));
+ return resultFocalLength;
+ }
+ return -1;
+ }
+
+ /**
+ * Validate and return the aperture.
+ *
+ * @param result Capture result to get the aperture
+ * @return Aperture from capture result or -1 if aperture is not available.
+ */
+ private float validateAperture(CaptureResult result) {
+ float[] apertures = mStaticInfo.getAvailableAperturesChecked();
+ Float resultAperture = result.get(CaptureResult.LENS_APERTURE);
+ if (mCollector.expectTrue("Capture result aperture is invalid",
+ resultAperture != null && resultAperture > 0)) {
+ List<Float> apertureList =
+ Arrays.asList(CameraTestUtils.toObject(apertures));
+ mCollector.expectTrue("Aperture should be one of the available apertures",
+ apertureList.contains(resultAperture));
+ return resultAperture;
+ }
+ return -1;
+ }
+
+ private static class SimpleAutoFocusListener implements Camera2Focuser.AutoFocusListener {
+ final ConditionVariable focusDone = new ConditionVariable();
+ @Override
+ public void onAutoFocusLocked(boolean success) {
+ focusDone.open();
+ }
+
+ public void waitForAutoFocusDone(long timeoutMs) {
+ if (focusDone.block(timeoutMs)) {
+ focusDone.close();
+ } else {
+ throw new TimeoutRuntimeException("Wait for auto focus done timed out after "
+ + timeoutMs + "ms");
+ }
+ }
+ }
+
+ /**
+ * Get 5 3A region test cases, each with one square region in it.
+ * The first one is at center, the other four are at corners of
+ * active array rectangle.
+ *
+ * @return array of test 3A regions
+ */
+ private ArrayList<MeteringRectangle[]> get3ARegionTestCasesForCamera() {
+ final int TEST_3A_REGION_NUM = 5;
+ final int DEFAULT_REGION_WEIGHT = 30;
+ final int DEFAULT_REGION_SCALE_RATIO = 8;
+ ArrayList<MeteringRectangle[]> testCases =
+ new ArrayList<MeteringRectangle[]>(TEST_3A_REGION_NUM);
+ final Rect activeArraySize = mStaticInfo.getActiveArraySizeChecked();
+ int regionWidth = activeArraySize.width() / DEFAULT_REGION_SCALE_RATIO - 1;
+ int regionHeight = activeArraySize.height() / DEFAULT_REGION_SCALE_RATIO - 1;
+ int centerX = activeArraySize.width() / 2;
+ int centerY = activeArraySize.height() / 2;
+ int bottomRightX = activeArraySize.width() - 1;
+ int bottomRightY = activeArraySize.height() - 1;
+
+ // Center region
+ testCases.add(
+ new MeteringRectangle[] {
+ new MeteringRectangle(
+ centerX - regionWidth / 2, // x
+ centerY - regionHeight / 2, // y
+ regionWidth, // width
+ regionHeight, // height
+ DEFAULT_REGION_WEIGHT)});
+
+ // Upper left corner
+ testCases.add(
+ new MeteringRectangle[] {
+ new MeteringRectangle(
+ 0, // x
+ 0, // y
+ regionWidth, // width
+ regionHeight, // height
+ DEFAULT_REGION_WEIGHT)});
+
+ // Upper right corner
+ testCases.add(
+ new MeteringRectangle[] {
+ new MeteringRectangle(
+ bottomRightX - regionWidth, // x
+ 0, // y
+ regionWidth, // width
+ regionHeight, // height
+ DEFAULT_REGION_WEIGHT)});
+
+ // Bootom left corner
+ testCases.add(
+ new MeteringRectangle[] {
+ new MeteringRectangle(
+ 0, // x
+ bottomRightY - regionHeight, // y
+ regionWidth, // width
+ regionHeight, // height
+ DEFAULT_REGION_WEIGHT)});
+
+ // Bootom right corner
+ testCases.add(
+ new MeteringRectangle[] {
+ new MeteringRectangle(
+ bottomRightX - regionWidth, // x
+ bottomRightY - regionHeight, // y
+ regionWidth, // width
+ regionHeight, // height
+ DEFAULT_REGION_WEIGHT)});
+
+ if (VERBOSE) {
+ StringBuilder sb = new StringBuilder();
+ for (MeteringRectangle[] mr : testCases) {
+ sb.append("{");
+ sb.append(Arrays.toString(mr));
+ sb.append("}, ");
+ }
+ if (sb.length() > 1)
+ sb.setLength(sb.length() - 2); // Remove the redundant comma and space at the end
+ Log.v(TAG, "Generated test regions are: " + sb.toString());
+ }
+
+ return testCases;
+ }
+
+ private boolean isRegionsSupportedFor3A(int index) {
+ boolean isRegionsSupported = mStaticInfo.get3aMaxRegionsChecked()[index] > 0;
+ if (index == MAX_REGIONS_AF_INDEX && isRegionsSupported) {
+ mCollector.expectTrue(
+ "Device reports non-zero max AF region count for a camera without focuser!",
+ mStaticInfo.hasFocuser());
+ isRegionsSupported = isRegionsSupported && mStaticInfo.hasFocuser();
+ }
+
+ return isRegionsSupported;
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java
new file mode 100644
index 0000000..37b6357
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.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 android.hardware.camera2.cts;
+
+import static android.hardware.camera2.cts.CameraTestUtils.*;
+
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraDevice.CaptureListener;
+import android.hardware.camera2.CaptureFailure;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.TotalCaptureResult;
+import android.util.Size;
+import android.hardware.camera2.cts.CameraTestUtils.SimpleCaptureListener;
+import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase;
+import android.util.Log;
+import android.util.Range;
+
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
+
+import static org.mockito.Mockito.*;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * CameraDevice preview test by using SurfaceView.
+ */
+public class SurfaceViewPreviewTest extends Camera2SurfaceViewTestCase {
+ private static final String TAG = "SurfaceViewPreviewTest";
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+ private static final int FRAME_TIMEOUT_MS = 1000;
+ private static final int NUM_FRAMES_VERIFIED = 30;
+ private static final int NUM_TEST_PATTERN_FRAMES_VERIFIED = 60;
+ private static final float FRAME_DURATION_ERROR_MARGIN = 0.005f; // 0.5 percent error margin.
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ /**
+ * Test all supported preview sizes for each camera device.
+ * <p>
+ * For the first {@link #NUM_FRAMES_VERIFIED} of capture results,
+ * the {@link CaptureListener} callback availability and the capture timestamp
+ * (monotonically increasing) ordering are verified.
+ * </p>
+ */
+ public void testCameraPreview() throws Exception {
+ for (int i = 0; i < mCameraIds.length; i++) {
+ try {
+ Log.i(TAG, "Testing preview for Camera " + mCameraIds[i]);
+ openDevice(mCameraIds[i]);
+
+ previewTestByCamera();
+ } finally {
+ closeDevice();
+ }
+ }
+ }
+
+ /**
+ * Basic test pattern mode preview.
+ * <p>
+ * Only test the test pattern preview and capture result, the image buffer
+ * is not validated.
+ * </p>
+ */
+ public void testBasicTestPatternPreview() throws Exception{
+ for (int i = 0; i < mCameraIds.length; i++) {
+ try {
+ Log.i(TAG, "Testing preview for Camera " + mCameraIds[i]);
+ openDevice(mCameraIds[i]);
+
+ previewTestPatternTestByCamera();
+ } finally {
+ closeDevice();
+ }
+ }
+ }
+
+ /**
+ * Test {@link CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE} for preview, validate the preview
+ * frame duration and exposure time.
+ */
+ public void testPreviewFpsRange() throws Exception {
+ for (String id : mCameraIds) {
+ try {
+ openDevice(id);
+
+ previewFpsRangeTestByCamera();
+ } finally {
+ closeDevice();
+ }
+ }
+ }
+
+ /**
+ * Test preview fps range for all supported ranges. The exposure time are frame duration are
+ * validated.
+ */
+ private void previewFpsRangeTestByCamera() throws Exception {
+ final int FPS_RANGE_SIZE = 2;
+ Size maxPreviewSz = mOrderedPreviewSizes.get(0);
+ Range<Integer>[] fpsRanges = mStaticInfo.getAeAvailableTargetFpsRangesChecked();
+ boolean antiBandingOffIsSupported = mStaticInfo.isAntiBandingOffModeSupported();
+ Range<Integer> fpsRange;
+ CaptureRequest.Builder requestBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ SimpleCaptureListener resultListener = new SimpleCaptureListener();
+ startPreview(requestBuilder, maxPreviewSz, resultListener);
+
+ for (int i = 0; i < fpsRanges.length; i += 1) {
+ fpsRange = fpsRanges[i];
+
+ requestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange);
+ // Turn off auto antibanding to avoid exposure time and frame duration interference
+ // from antibanding algorithm.
+ if (antiBandingOffIsSupported) {
+ requestBuilder.set(CaptureRequest.CONTROL_AE_ANTIBANDING_MODE,
+ CaptureRequest.CONTROL_AE_ANTIBANDING_MODE_OFF);
+ } else {
+ // The device doesn't implement the OFF mode, test continues. It need make sure
+ // that the antibanding algorithm doesn't interfere with the fps range control.
+ Log.i(TAG, "OFF antibanding mode is not supported, the camera device output must" +
+ " satisfy the specified fps range regardless of its current antibanding" +
+ " mode");
+ }
+
+ resultListener = new SimpleCaptureListener();
+ mCamera.setRepeatingRequest(requestBuilder.build(), resultListener, mHandler);
+
+ verifyPreviewTargetFpsRange(resultListener, NUM_FRAMES_VERIFIED, fpsRange,
+ maxPreviewSz);
+ }
+
+ stopPreview();
+ }
+
+ private void verifyPreviewTargetFpsRange(SimpleCaptureListener resultListener,
+ int numFramesVerified, Range<Integer> fpsRange, Size previewSz) {
+ CaptureResult result = resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
+ long frameDuration = getValueNotNull(result, CaptureResult.SENSOR_FRAME_DURATION);
+ long[] frameDurationRange =
+ new long[]{(long) (1e9 / fpsRange.getUpper()), (long) (1e9 / fpsRange.getLower())};
+ mCollector.expectInRange(
+ "Frame duration must be in the range of " + Arrays.toString(frameDurationRange),
+ frameDuration, (long) (frameDurationRange[0] * (1 - FRAME_DURATION_ERROR_MARGIN)),
+ (long) (frameDurationRange[1] * (1 + FRAME_DURATION_ERROR_MARGIN)));
+ long expTime = getValueNotNull(result, CaptureResult.SENSOR_EXPOSURE_TIME);
+ mCollector.expectTrue(String.format("Exposure time %d must be no larger than frame"
+ + "duration %d", expTime, frameDuration), expTime <= frameDuration);
+
+ Long minFrameDuration = mMinPreviewFrameDurationMap.get(previewSz);
+ boolean findDuration = mCollector.expectTrue("Unable to find minFrameDuration for size "
+ + previewSz.toString(), minFrameDuration != null);
+ if (findDuration) {
+ mCollector.expectTrue("Frame duration " + frameDuration + " must be no smaller than"
+ + " minFrameDuration " + minFrameDuration, frameDuration >= minFrameDuration);
+ }
+ }
+
+ /**
+ * Test all supported preview sizes for a camera device
+ *
+ * @throws Exception
+ */
+ private void previewTestByCamera() throws Exception {
+ List<Size> previewSizes = getSupportedPreviewSizes(
+ mCamera.getId(), mCameraManager, PREVIEW_SIZE_BOUND);
+
+ for (final Size sz : previewSizes) {
+ if (VERBOSE) {
+ Log.v(TAG, "Testing camera preview size: " + sz.toString());
+ }
+
+ // TODO: vary the different settings like crop region to cover more cases.
+ CaptureRequest.Builder requestBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ CaptureListener mockCaptureListener =
+ mock(CameraDevice.CaptureListener.class);
+
+ startPreview(requestBuilder, sz, mockCaptureListener);
+ verifyCaptureResults(mCamera, mockCaptureListener, NUM_FRAMES_VERIFIED,
+ NUM_FRAMES_VERIFIED * FRAME_TIMEOUT_MS);
+ stopPreview();
+ }
+ }
+
+ private void previewTestPatternTestByCamera() throws Exception {
+ Size maxPreviewSize = mOrderedPreviewSizes.get(0);
+ int[] testPatternModes = mStaticInfo.getAvailableTestPatternModesChecked();
+ CaptureRequest.Builder requestBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ CaptureListener mockCaptureListener;
+
+ final int[] TEST_PATTERN_DATA = {0, 0xFFFFFFFF, 0xFFFFFFFF, 0}; // G:100%, RB:0.
+ for (int mode : testPatternModes) {
+ if (VERBOSE) {
+ Log.v(TAG, "Test pattern mode: " + mode);
+ }
+ requestBuilder.set(CaptureRequest.SENSOR_TEST_PATTERN_MODE, mode);
+ if (mode == CaptureRequest.SENSOR_TEST_PATTERN_MODE_SOLID_COLOR) {
+ // Assign color pattern to SENSOR_TEST_PATTERN_MODE_DATA
+ requestBuilder.set(CaptureRequest.SENSOR_TEST_PATTERN_DATA, TEST_PATTERN_DATA);
+ }
+ mockCaptureListener = mock(CameraDevice.CaptureListener.class);
+ startPreview(requestBuilder, maxPreviewSize, mockCaptureListener);
+ verifyCaptureResults(mCamera, mockCaptureListener, NUM_TEST_PATTERN_FRAMES_VERIFIED,
+ NUM_TEST_PATTERN_FRAMES_VERIFIED * FRAME_TIMEOUT_MS);
+ }
+
+ stopPreview();
+ }
+
+ private class IsCaptureResultValid extends ArgumentMatcher<TotalCaptureResult> {
+ @Override
+ public boolean matches(Object obj) {
+ TotalCaptureResult result = (TotalCaptureResult)obj;
+ Long timeStamp = result.get(CaptureResult.SENSOR_TIMESTAMP);
+ if (timeStamp != null && timeStamp.longValue() > 0L) {
+ return true;
+ }
+ return false;
+ }
+ }
+
+ private void verifyCaptureResults(
+ CameraDevice camera,
+ CameraDevice.CaptureListener mockListener,
+ int expectResultCount,
+ int timeOutMs) {
+ // Should receive expected number of onCaptureStarted callbacks.
+ ArgumentCaptor<Long> timestamps = ArgumentCaptor.forClass(Long.class);
+ verify(mockListener,
+ timeout(timeOutMs).atLeast(expectResultCount))
+ .onCaptureStarted(
+ eq(camera),
+ isA(CaptureRequest.class),
+ timestamps.capture());
+
+ // Validate timestamps: all timestamps should be larger than 0 and monotonically increase.
+ long timestamp = 0;
+ for (Long nextTimestamp : timestamps.getAllValues()) {
+ assertNotNull("Next timestamp is null!", nextTimestamp);
+ assertTrue("Captures are out of order", timestamp < nextTimestamp);
+ timestamp = nextTimestamp;
+ }
+
+ // Should receive expected number of capture results.
+ verify(mockListener,
+ timeout(timeOutMs).atLeast(expectResultCount))
+ .onCaptureCompleted(
+ eq(camera),
+ isA(CaptureRequest.class),
+ argThat(new IsCaptureResultValid()));
+
+ // Should not receive any capture failed callbacks.
+ verify(mockListener, never())
+ .onCaptureFailed(
+ eq(camera),
+ isA(CaptureRequest.class),
+ isA(CaptureFailure.class));
+ }
+
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/AssertHelpers.java b/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/AssertHelpers.java
new file mode 100644
index 0000000..08aa8bf
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/AssertHelpers.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 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 android.hardware.camera2.cts.helpers;
+
+import static junit.framework.Assert.*;
+
+import java.util.Arrays;
+
+/**
+ * Helper set of methods to add extra useful assert functionality missing in junit.
+ */
+public class AssertHelpers {
+ /**
+ * Assert that at least one of the elements in data is non-zero.
+ *
+ * <p>An empty or a null array always fails.</p>
+ */
+ public static void assertArrayNotAllZeroes(String message, byte[] data) {
+ int size = data.length;
+
+ int i = 0;
+ for (i = 0; i < size; ++i) {
+ if (data[i] != 0) {
+ break;
+ }
+ }
+
+ assertTrue(message, i < size);
+ }
+
+ /**
+ * Assert that every element in left is less than or equals to the corresponding element in
+ * right.
+ *
+ * <p>Array sizes must match.</p>
+ *
+ * @param message Message to use in case the assertion fails
+ * @param left Left array
+ * @param right Right array
+ */
+ public static void assertArrayNotGreater(String message, float[] left, float[] right) {
+ assertEquals("Array lengths did not match", left.length, right.length);
+
+ String leftString = Arrays.toString(left);
+ String rightString = Arrays.toString(right);
+
+ for (int i = 0; i < left.length; ++i) {
+ String msg = String.format(
+ "%s: (%s should be less than or equals than %s; item index %d; left = %s; " +
+ "right = %s)",
+ message, left[i], right[i], i, leftString, rightString);
+
+ assertTrue(msg, left[i] <= right[i]);
+ }
+ }
+
+ /**
+ * Assert that every element in the value array is greater than the lower bound (exclusive).
+ *
+ * @param value an array of items
+ * @param lowerBound the exclusive lower bound
+ */
+ public static void assertArrayWithinLowerBound(String message, float[] value, float lowerBound)
+ {
+ for (int i = 0; i < value.length; ++i) {
+ assertTrue(
+ String.format("%s: (%s should be greater than than %s; item index %d in %s)",
+ message, value[i], lowerBound, i, Arrays.toString(value)),
+ value[i] > lowerBound);
+ }
+ }
+
+ /**
+ * Assert that every element in the value array is less than the upper bound (exclusive).
+ *
+ * @param value an array of items
+ * @param upperBound the exclusive upper bound
+ */
+ public static void assertArrayWithinUpperBound(String message, float[] value, float upperBound)
+ {
+ for (int i = 0; i < value.length; ++i) {
+ assertTrue(
+ String.format("%s: (%s should be less than than %s; item index %d in %s)",
+ message, value[i], upperBound, i, Arrays.toString(value)),
+ value[i] < upperBound);
+ }
+ }
+
+ /**
+ * Assert that {@code low <= value <= high}
+ */
+ public static void assertInRange(float value, float low, float high) {
+ assertTrue(
+ String.format("Value %s must be greater or equal to %s, but was lower", value, low),
+ value >= low);
+ assertTrue(
+ String.format("Value %s must be less than or equal to %s, but was higher",
+ value, high),
+ value <= high);
+
+ // TODO: generic by using comparators
+ }
+
+ // Suppress default constructor for noninstantiability
+ private AssertHelpers() { throw new AssertionError(); }
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/Camera2Focuser.java b/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/Camera2Focuser.java
new file mode 100644
index 0000000..ff4af2d
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/Camera2Focuser.java
@@ -0,0 +1,388 @@
+/*
+ * 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 android.hardware.camera2.cts.helpers;
+
+import android.graphics.Rect;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraDevice.CaptureListener;
+import android.hardware.camera2.params.MeteringRectangle;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.TotalCaptureResult;
+import android.os.Handler;
+import android.util.Log;
+import android.view.Surface;
+
+import com.android.ex.camera2.pos.AutoFocusStateMachine;
+import com.android.ex.camera2.pos.AutoFocusStateMachine.AutoFocusStateListener;
+
+/**
+ * A focuser utility class to assist camera to do auto focus.
+ * <p>
+ * This class need create repeating request and single request to do auto focus.
+ * The repeating request is used to get the auto focus states; the single
+ * request is used to trigger the auto focus. This class assumes the camera device
+ * supports auto-focus. Don't use this class if the camera device doesn't have focuser
+ * unit.
+ * </p>
+ */
+public class Camera2Focuser implements AutoFocusStateListener {
+ private static final String TAG = "Focuser";
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+ private final AutoFocusStateMachine mAutoFocus = new AutoFocusStateMachine(this);
+ private final Handler mHandler;
+ private final AutoFocusListener mAutoFocusListener;
+ private final CameraDevice mCamera;
+ private final Surface mRequestSurface;
+ private final CameraCharacteristics mStaticInfo;
+
+ private int mAfRun = 0;
+ private MeteringRectangle[] mAfRegions;
+ private boolean mLocked = false;
+ private boolean mSuccess = false;
+ private CaptureRequest.Builder mRepeatingBuilder;
+
+ /**
+ * The callback interface to notify auto focus result.
+ */
+ public interface AutoFocusListener {
+ /**
+ * This callback is called when auto focus completes and locked.
+ *
+ * @param success true if focus was successful, false if otherwise
+ */
+ void onAutoFocusLocked(boolean success);
+ }
+
+ /**
+ * Construct a focuser object, with given capture requestSurface, listener
+ * and handler.
+ * <p>
+ * The focuser object will use camera and requestSurface to submit capture
+ * request and receive focus state changes. The {@link AutoFocusListener} is
+ * used to notify the auto focus callback.
+ * </p>
+ *
+ * @param camera The camera device associated with this focuser
+ * @param requestSurface The surface to issue the capture request with
+ * @param listener The auto focus listener to notify AF result
+ * @param staticInfo The CameraCharacteristics of the camera device
+ * @param handler The handler used to post auto focus callbacks
+ * @throws CameraAccessException
+ */
+ public Camera2Focuser(CameraDevice camera, Surface requestSurface, AutoFocusListener listener,
+ CameraCharacteristics staticInfo, Handler handler) throws CameraAccessException {
+ if (camera == null) {
+ throw new IllegalArgumentException("camera must not be null");
+ }
+ if (listener == null) {
+ throw new IllegalArgumentException("listener must not be null");
+ }
+ if (handler == null) {
+ throw new IllegalArgumentException("handler must not be null");
+ }
+ if (requestSurface == null) {
+ throw new IllegalArgumentException("requestSurface must not be null");
+ }
+ if (staticInfo == null) {
+ throw new IllegalArgumentException("staticInfo must not be null");
+ }
+ Float minFocusDist = staticInfo.get(CameraCharacteristics.LENS_INFO_MINIMUM_FOCUS_DISTANCE);
+ if (minFocusDist == null || minFocusDist == 0) {
+ throw new IllegalArgumentException("this camera doesn't have a focuser");
+ }
+
+ mCamera = camera;
+ mRequestSurface = requestSurface;
+ mAutoFocusListener = listener;
+ mStaticInfo = staticInfo;
+ mHandler = handler;
+
+ /**
+ * Begin by always being in passive auto focus.
+ */
+ cancelAutoFocus();
+ }
+
+ @Override
+ public synchronized void onAutoFocusSuccess(CaptureResult result, boolean locked) {
+ mSuccess = true;
+ mLocked = locked;
+
+ if (locked) {
+ dispatchAutoFocusStatusLocked(/*success*/true);
+ }
+ }
+
+ @Override
+ public synchronized void onAutoFocusFail(CaptureResult result, boolean locked) {
+ mSuccess = false;
+ mLocked = locked;
+
+ if (locked) {
+ dispatchAutoFocusStatusLocked(/*success*/false);
+ }
+ }
+
+ @Override
+ public synchronized void onAutoFocusScan(CaptureResult result) {
+ mSuccess = false;
+ mLocked = false;
+ }
+
+ @Override
+ public synchronized void onAutoFocusInactive(CaptureResult result) {
+ mSuccess = false;
+ mLocked = false;
+ }
+
+ /**
+ * Start a active auto focus scan based on the given regions.
+ *
+ * <p>This is usually used for touch for focus, it can make the auto-focus converge based
+ * on some particular region aggressively. But it is usually slow as a full active scan
+ * is initiated. After the auto focus is converged, the {@link cancelAutoFocus} must be called
+ * to resume the continuous auto-focus.</p>
+ *
+ * @param afRegions The AF regions used by focuser auto focus, full active
+ * array size is used if afRegions is null.
+ * @throws CameraAccessException
+ */
+ public synchronized void touchForAutoFocus(MeteringRectangle[] afRegions)
+ throws CameraAccessException {
+ startAutoFocusLocked(/*active*/true, afRegions);
+ }
+
+ /**
+ * Start auto focus scan.
+ * <p>
+ * Start an auto focus scan if it was not done yet. If AF passively focused,
+ * lock it. If AF is already locked, return. Otherwise, initiate a full
+ * active scan. This is suitable for still capture: focus should need to be
+ * accurate, but the AF latency also need to be as short as possible.
+ * </p>
+ *
+ * @param afRegions The AF regions used by focuser auto focus, full active
+ * array size is used if afRegions is null.
+ * @throws CameraAccessException
+ */
+ public synchronized void startAutoFocus(MeteringRectangle[] afRegions)
+ throws CameraAccessException {
+ startAutoFocusLocked(/*forceActive*/false, afRegions);
+ }
+
+ /**
+ * Cancel ongoing auto focus, unlock the auto-focus if it was locked, and
+ * resume to passive continuous auto focus.
+ *
+ * @throws CameraAccessException
+ */
+ public synchronized void cancelAutoFocus() throws CameraAccessException {
+ mSuccess = false;
+ mLocked = false;
+
+ // reset the AF regions:
+ setAfRegions(null);
+
+ // Create request builders, the af regions are automatically updated.
+ mRepeatingBuilder = createRequestBuilder();
+ CaptureRequest.Builder requestBuilder = createRequestBuilder();
+ mAutoFocus.setPassiveAutoFocus(/*picture*/true, mRepeatingBuilder);
+ mAutoFocus.unlockAutoFocus(mRepeatingBuilder, requestBuilder);
+ CaptureListener listener = createCaptureListener();
+ mCamera.setRepeatingRequest(mRepeatingBuilder.build(), listener, mHandler);
+ mCamera.capture(requestBuilder.build(), listener, mHandler);
+ }
+
+ /**
+ * Get current AF mode.
+ * @return current AF mode
+ * @throws IllegalStateException if there auto focus is not running.
+ */
+ public synchronized int getCurrentAfMode() {
+ if (mRepeatingBuilder == null) {
+ throw new IllegalStateException("Auto focus is not running, unable to get AF mode");
+ }
+
+ return mRepeatingBuilder.get(CaptureRequest.CONTROL_AF_MODE);
+ }
+
+ private void startAutoFocusLocked(
+ boolean forceActive, MeteringRectangle[] afRegions) throws CameraAccessException {
+
+ setAfRegions(afRegions);
+ mAfRun++;
+
+ // Create request builders, the af regions are automatically updated.
+ mRepeatingBuilder = createRequestBuilder();
+ CaptureRequest.Builder requestBuilder = createRequestBuilder();
+ if (forceActive) {
+ startAutoFocusFullActiveLocked();
+ } else {
+ // Not forcing a full active scan. If AF passively focused, lock it. If AF is already
+ // locked, return. Otherwise, initiate a full active scan.
+ if (mSuccess && mLocked) {
+ dispatchAutoFocusStatusLocked(/*success*/true);
+ return;
+ } else if (mSuccess) {
+ mAutoFocus.lockAutoFocus(mRepeatingBuilder, requestBuilder);
+ CaptureListener listener = createCaptureListener();
+ mCamera.setRepeatingRequest(mRepeatingBuilder.build(), listener, mHandler);
+ mCamera.capture(requestBuilder.build(), listener, mHandler);
+ } else {
+ startAutoFocusFullActiveLocked();
+ }
+ }
+ }
+
+ private void startAutoFocusFullActiveLocked() throws CameraAccessException {
+ // Create request builders, the af regions are automatically updated.
+ mRepeatingBuilder = createRequestBuilder();
+ CaptureRequest.Builder requestBuilder = createRequestBuilder();
+ mAutoFocus.setActiveAutoFocus(mRepeatingBuilder, requestBuilder);
+ if (mRepeatingBuilder.get(CaptureRequest.CONTROL_AF_TRIGGER)
+ != CaptureRequest.CONTROL_AF_TRIGGER_IDLE) {
+ throw new AssertionError("Wrong trigger set in repeating request");
+ }
+ if (requestBuilder.get(CaptureRequest.CONTROL_AF_TRIGGER)
+ != CaptureRequest.CONTROL_AF_TRIGGER_START) {
+ throw new AssertionError("Wrong trigger set in queued request");
+ }
+ mAutoFocus.resetState();
+
+ CaptureListener listener = createCaptureListener();
+ mCamera.setRepeatingRequest(mRepeatingBuilder.build(), listener, mHandler);
+ mCamera.capture(requestBuilder.build(), listener, mHandler);
+ }
+
+ private void dispatchAutoFocusStatusLocked(final boolean success) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mAutoFocusListener.onAutoFocusLocked(success);
+ }
+ });
+ }
+
+ /**
+ * Create request builder, set the af regions.
+ * @throws CameraAccessException
+ */
+ private CaptureRequest.Builder createRequestBuilder() throws CameraAccessException {
+ CaptureRequest.Builder requestBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+
+ requestBuilder.set(CaptureRequest.CONTROL_AF_REGIONS, mAfRegions);
+ requestBuilder.addTarget(mRequestSurface);
+
+ return requestBuilder;
+ }
+
+ /**
+ * Set AF regions, fall back to default region if afRegions is null.
+ *
+ * @param afRegions The AF regions to set
+ * @throws IllegalArgumentException if the region is malformed (length is 0).
+ */
+ private void setAfRegions(MeteringRectangle[] afRegions) {
+ if (afRegions == null) {
+ setDefaultAfRegions();
+ return;
+ }
+ // Throw IAE if AF regions are malformed.
+ if (afRegions.length == 0) {
+ throw new IllegalArgumentException("afRegions is malformed, length: 0");
+ }
+
+ mAfRegions = afRegions;
+ }
+
+ /**
+ * Set default AF region to full active array size.
+ */
+ private void setDefaultAfRegions() {
+ Rect activeArraySize = mStaticInfo.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
+ if (activeArraySize == null) {
+ throw new AssertionError("Active array size shouldn't be null");
+ }
+
+ // Initialize AF regions with all zeros, meaning that it is up to camera device to device
+ // the regions used by AF.
+ mAfRegions = new MeteringRectangle[] {
+ new MeteringRectangle(0, 0, 0, 0, 0)};
+ }
+ private CaptureListener createCaptureListener() {
+
+ int thisAfRun;
+ synchronized (this) {
+ thisAfRun = mAfRun;
+ }
+
+ final int finalAfRun = thisAfRun;
+
+ return new CaptureListener() {
+ private int mLatestFrameCount = -1;
+
+ @Override
+ public void onCapturePartial(CameraDevice camera, CaptureRequest request,
+ CaptureResult result) {
+ // In case of a partial result, send to focuser if necessary
+ // 3A fields are present
+ if (result.get(CaptureResult.CONTROL_AF_STATE) != null &&
+ result.get(CaptureResult.CONTROL_AF_MODE) != null) {
+ if (VERBOSE) {
+ Log.v(TAG, "Focuser - got early AF state");
+ }
+
+ dispatchToFocuser(result);
+ }
+ }
+
+ @Override
+ public void onCaptureCompleted(CameraDevice camera, CaptureRequest request,
+ TotalCaptureResult result) {
+ dispatchToFocuser(result);
+ }
+
+ private void dispatchToFocuser(CaptureResult result) {
+ int afRun;
+ synchronized (Camera2Focuser.this) {
+ // In case of partial results, don't send AF update twice
+ int frameCount = result.get(CaptureResult.REQUEST_FRAME_COUNT);
+ if (frameCount <= mLatestFrameCount) return;
+ mLatestFrameCount = frameCount;
+
+ afRun = mAfRun;
+ }
+
+ if (afRun != finalAfRun) {
+ if (VERBOSE) {
+ Log.w(TAG,
+ "onCaptureCompleted - Ignoring results from previous AF run "
+ + finalAfRun);
+ }
+ return;
+ }
+
+ mAutoFocus.onCaptureCompleted(result);
+ }
+ };
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/CameraErrorCollector.java b/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/CameraErrorCollector.java
new file mode 100644
index 0000000..689bb0d
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/CameraErrorCollector.java
@@ -0,0 +1,426 @@
+/*
+ * Copyright 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 android.hardware.camera2.cts.helpers;
+
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureRequest.Builder;
+import android.hardware.camera2.CaptureResult;
+import android.util.Log;
+
+import org.hamcrest.CoreMatchers;
+import org.hamcrest.Matcher;
+import org.junit.rules.ErrorCollector;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * A camera test ErrorCollector class to gather the test failures during a test,
+ * instead of failing the test immediately for each failure.
+ */
+public class CameraErrorCollector extends ErrorCollector {
+
+ private static final String TAG = "CameraErrorCollector";
+ private static final boolean LOG_ERRORS = Log.isLoggable(TAG, Log.ERROR);
+
+ private String mCameraMsg = "";
+
+ @Override
+ public void verify() throws Throwable {
+ // Do not remove if using JUnit 3 test runners. super.verify() is protected.
+ super.verify();
+ }
+
+ /**
+ * Adds an unconditional error to the table. Execution continues, but test will fail at the end.
+ *
+ * @param message A string containing the failure reason.
+ */
+ public void addMessage(String message) {
+ addErrorSuper(new Throwable(mCameraMsg + message));
+ }
+
+ /**
+ * Adds a Throwable to the table. Execution continues, but the test will fail at the end.
+ */
+ @Override
+ public void addError(Throwable error) {
+ addErrorSuper(new Throwable(mCameraMsg + error.getMessage(), error));
+ }
+
+ private void addErrorSuper(Throwable error) {
+ if (LOG_ERRORS) Log.e(TAG, error.getMessage());
+ super.addError(error);
+ }
+
+ /**
+ * Adds a failure to the table if {@code matcher} does not match {@code value}.
+ * Execution continues, but the test will fail at the end if the match fails.
+ * The camera id is included into the failure log.
+ */
+ @Override
+ public <T> void checkThat(final T value, final Matcher<T> matcher) {
+ super.checkThat(mCameraMsg, value, matcher);
+ }
+
+ /**
+ * Adds a failure with the given {@code reason} to the table if
+ * {@code matcher} does not match {@code value}. Execution continues, but
+ * the test will fail at the end if the match fails. The camera id is
+ * included into the failure log.
+ */
+ @Override
+ public <T> void checkThat(final String reason, final T value, final Matcher<T> matcher) {
+ super.checkThat(mCameraMsg + reason, value, matcher);
+
+ }
+
+ /**
+ * Set the camera id to this error collector object for logging purpose.
+ *
+ * @param id The camera id to be set.
+ */
+ public void setCameraId(String id) {
+ if (id != null) {
+ mCameraMsg = "Test failed for camera " + id + ": ";
+ } else {
+ mCameraMsg = "";
+ }
+ }
+
+ /**
+ * Adds a failure to the table if {@code condition} is not {@code true}.
+ * <p>
+ * Execution continues, but the test will fail at the end if the condition
+ * failed.
+ * </p>
+ *
+ * @param msg Message to be logged when check fails.
+ * @param condition Log the failure if it is not true.
+ */
+ public boolean expectTrue(String msg, boolean condition) {
+ if (!condition) {
+ addMessage(msg);
+ }
+
+ return condition;
+ }
+
+ /**
+ * Check if the two values are equal.
+ *
+ * @param msg Message to be logged when check fails.
+ * @param expected Expected value to be checked against.
+ * @param actual Actual value to be checked.
+ * @return {@code true} if the two values are equal, {@code false} otherwise.
+ */
+ public <T> boolean expectEquals(String msg, T expected, T actual) {
+ if (expected == null) {
+ throw new IllegalArgumentException("expected value shouldn't be null");
+ }
+
+ if (!Objects.equals(expected, actual)) {
+ if (actual == null) {
+ addMessage(msg + ", actual value is null");
+ return false;
+ }
+
+ addMessage(String.format("%s (expected = %s, actual = %s) ", msg, expected.toString(),
+ actual.toString()));
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Check if the two arrays of values are deeply equal.
+ *
+ * @param msg Message to be logged when check fails.
+ * @param expected Expected array of values to be checked against.
+ * @param actual Actual array of values to be checked.
+ * @return {@code true} if the two arrays of values are deeply equal, {@code false} otherwise.
+ */
+ public <T> boolean expectEquals(String msg, T[] expected, T[] actual) {
+ if (expected == null) {
+ throw new IllegalArgumentException("expected value shouldn't be null");
+ }
+
+ if (!Arrays.deepEquals(expected, actual)) {
+ if (actual == null) {
+ addMessage(msg + ", actual value is null");
+ return false;
+ }
+
+ addMessage(String.format("%s (expected = %s, actual = %s) ", msg,
+ Arrays.deepToString(expected), Arrays.deepToString(actual)));
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Check if the two float values are equal with given error tolerance.
+ *
+ * @param msg Message to be logged when check fails.
+ * @param expected Expected value to be checked against.
+ * @param actual Actual value to be checked.
+ * @param tolerance The error margin for the equality check.
+ * @return {@code true} if the two values are equal, {@code false} otherwise.
+ */
+ public <T> boolean expectEquals(String msg, float expected, float actual, float tolerance) {
+ if (expected == actual) {
+ return true;
+ }
+
+ if (!(Math.abs(expected - actual) <= tolerance)) {
+ addMessage(String.format("%s (expected = %s, actual = %s, tolerance = %s) ", msg,
+ expected, actual, tolerance));
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Check if the two double values are equal with given error tolerance.
+ *
+ * @param msg Message to be logged when check fails.
+ * @param expected Expected value to be checked against.
+ * @param actual Actual value to be checked.
+ * @param tolerance The error margin for the equality check
+ * @return {@code true} if the two values are equal, {@code false} otherwise.
+ */
+ public <T> boolean expectEquals(String msg, double expected, double actual, double tolerance) {
+ if (expected == actual)
+ {
+ return true;
+ }
+
+ if (!(Math.abs(expected - actual) <= tolerance)) {
+ addMessage(String.format("%s (expected = %s, actual = %s, tolerance = %s) ", msg,
+ expected, actual, tolerance));
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Expect the list of values are in the range.
+ *
+ * @param msg Message to be logged
+ * @param list The list of values to be checked
+ * @param min The min value of the range
+ * @param max The max value of the range
+ */
+ public <T extends Comparable<? super T>> void expectValuesInRange(String msg, List<T> list,
+ T min, T max) {
+ for (T value : list) {
+ expectTrue(msg + String.format(", array value " + value.toString() +
+ " is out of range [%s, %s]",
+ min.toString(), max.toString()),
+ value.compareTo(max)<= 0 && value.compareTo(min) >= 0);
+ }
+ }
+
+ /**
+ * Expect the array of values are in the range.
+ *
+ * @param msg Message to be logged
+ * @param array The array of values to be checked
+ * @param min The min value of the range
+ * @param max The max value of the range
+ */
+ public <T extends Comparable<? super T>> void expectValuesInRange(String msg, T[] array,
+ T min, T max) {
+ expectValuesInRange(msg, Arrays.asList(array), min, max);
+ }
+
+ /**
+ * Expect the value is in the range.
+ *
+ * @param msg Message to be logged
+ * @param value The value to be checked
+ * @param min The min value of the range
+ * @param max The max value of the range
+ */
+ public <T extends Comparable<? super T>> void expectInRange(String msg, T value,
+ T min, T max) {
+ expectTrue(msg + String.format(", value " + value.toString() + " is out of range [%s, %s]",
+ min.toString(), max.toString()),
+ value.compareTo(max)<= 0 && value.compareTo(min) >= 0);
+ }
+
+ public void expectNotNull(String msg, Object obj) {
+ checkThat(msg, obj, CoreMatchers.notNullValue());
+ }
+
+ /**
+ * Check if the values in the array are monotonically increasing (decreasing) and not all
+ * equal.
+ *
+ * @param array The array of values to be checked
+ * @param ascendingOrder The monotonicity ordering to be checked with
+ */
+ public <T extends Comparable<? super T>> void checkArrayMonotonicityAndNotAllEqual(T[] array,
+ boolean ascendingOrder) {
+ String orderMsg = ascendingOrder ? ("increasing order") : ("decreasing order");
+ for (int i = 0; i < array.length - 1; i++) {
+ int compareResult = array[i + 1].compareTo(array[i]);
+ boolean condition = compareResult >= 0;
+ if (!ascendingOrder) {
+ condition = compareResult <= 0;
+ }
+
+ expectTrue(String.format("Adjacent values (%s and %s) %s monotonicity is broken",
+ array[i].toString(), array[i + 1].toString(), orderMsg), condition);
+ }
+
+ expectTrue("All values of this array are equal: " + array[0].toString(),
+ array[0].compareTo(array[array.length - 1]) != 0);
+ }
+
+ /**
+ * Check if the key value is not null and return the value.
+ *
+ * @param request The {@link CaptureRequest#Builder} to get the key from.
+ * @param key The {@link CaptureRequest} key to be checked.
+ * @return The value of the key.
+ */
+ public <T> T expectKeyValueNotNull(Builder request, CaptureRequest.Key<T> key) {
+
+ T value = request.get(key);
+ if (value == null) {
+ addMessage("Key " + key.getName() + " shouldn't be null");
+ }
+
+ return value;
+ }
+
+ /**
+ * Check if the key value is not null and return the value.
+ *
+ * @param result The {@link CaptureResult} to get the key from.
+ * @param key The {@link CaptureResult} key to be checked.
+ * @return The value of the key.
+ */
+ public <T> T expectKeyValueNotNull(CaptureResult result, CaptureResult.Key<T> key) {
+ return expectKeyValueNotNull("", result, key);
+ }
+
+ /**
+ * Check if the key value is not null and return the value.
+ *
+ * @param msg The message to be logged.
+ * @param result The {@link CaptureResult} to get the key from.
+ * @param key The {@link CaptureResult} key to be checked.
+ * @return The value of the key.
+ */
+ public <T> T expectKeyValueNotNull(String msg, CaptureResult result, CaptureResult.Key<T> key) {
+
+ T value = result.get(key);
+ if (value == null) {
+ addMessage(msg + " Key " + key.getName() + " shouldn't be null");
+ }
+
+ return value;
+ }
+
+ /**
+ * Check if the key is non-null and the value is not equal to target.
+ *
+ * @param request The The {@link CaptureRequest#Builder} to get the key from.
+ * @param key The {@link CaptureRequest} key to be checked.
+ * @param expected The expected value of the CaptureRequest key.
+ */
+ public <T> void expectKeyValueNotEquals(
+ Builder request, CaptureRequest.Key<T> key, T expected) {
+ if (request == null || key == null || expected == null) {
+ throw new IllegalArgumentException("request, key and target shouldn't be null");
+ }
+
+ T value;
+ if ((value = expectKeyValueNotNull(request, key)) == null) {
+ return;
+ }
+
+ String reason = "Key " + key.getName() + " shouldn't have value " + value.toString();
+ checkThat(reason, value, CoreMatchers.not(expected));
+ }
+
+ /**
+ * Check if the key is non-null and the value is not equal to target.
+ *
+ * @param result The The {@link CaptureResult} to get the key from.
+ * @param key The {@link CaptureResult} key to be checked.
+ * @param expected The expected value of the CaptureResult key.
+ */
+ public <T> void expectKeyValueNotEquals(
+ CaptureResult result, CaptureResult.Key<T> key, T expected) {
+ if (result == null || key == null || expected == null) {
+ throw new IllegalArgumentException("result, key and target shouldn't be null");
+ }
+
+ T value;
+ if ((value = expectKeyValueNotNull(result, key)) == null) {
+ return;
+ }
+
+ String reason = "Key " + key.getName() + " shouldn't have value " + value.toString();
+ checkThat(reason, value, CoreMatchers.not(expected));
+ }
+
+ /**
+ * Check if the key is non-null and the value is equal to target.
+ *
+ * <p>Only check non-null if the target is null.</p>
+ *
+ * @param request The The {@link CaptureRequest#Builder} to get the key from.
+ * @param key The {@link CaptureRequest} key to be checked.
+ * @param expected The expected value of the CaptureRequest key.
+ */
+ public <T> void expectKeyValueEquals(Builder request, CaptureRequest.Key<T> key, T expected) {
+ if (request == null || key == null || expected == null) {
+ throw new IllegalArgumentException("request, key and target shouldn't be null");
+ }
+
+ T value;
+ if ((value = expectKeyValueNotNull(request, key)) == null) {
+ return;
+ }
+
+ String reason = "Key " + key.getName() + " value " + value.toString()
+ + " doesn't match the expected value " + expected.toString();
+ checkThat(reason, value, CoreMatchers.equalTo(expected));
+ }
+
+ /**
+ * Check if the element inside of the list are unique.
+ *
+ * @param msg The message to be logged
+ * @param list The list of values to be checked
+ */
+ public <T> void expectValuesUnique(String msg, List<T> list) {
+ Set<T> sizeSet = new HashSet<T>(list);
+ expectTrue(msg + " each size must be distinct", sizeSet.size() == list.size());
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/MaybeNull.java b/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/MaybeNull.java
new file mode 100644
index 0000000..029ab03
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/MaybeNull.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 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 android.hardware.camera2.cts.helpers;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+/**
+ * Helper set of methods for dealing with objects that are sometimes {@code null}.
+ *
+ * <p>Used to remove common patterns like: <pre>{@code
+ * if (obj != null) {
+ * obj.doSomething();
+ * }</pre>
+ *
+ * If this is common, consider adding {@code doSomething} to this class so that the code
+ * looks more like <pre>{@code
+ * MaybeNull.doSomething(obj);
+ * }</pre>
+ */
+public class MaybeNull {
+ /**
+ * Close the underlying {@link AutoCloseable}, if it's not {@code null}.
+ *
+ * @param closeable An object which implements {@link AutoCloseable}.
+ * @throws Exception If {@link AutoCloseable#close} fails.
+ */
+ public static <T extends AutoCloseable> void close(T closeable) throws Exception {
+ if (closeable != null) {
+ closeable.close();
+ }
+ }
+
+ /**
+ * Close the underlying {@link UncheckedCloseable}, if it's not {@code null}.
+ *
+ * <p>No checked exceptions are thrown. An unknown runtime exception might still
+ * be raised.</p>
+ *
+ * @param closeable An object which implements {@link UncheckedCloseable}.
+ */
+ public static <T extends UncheckedCloseable> void close(T closeable) {
+ if (closeable != null) {
+ closeable.close();
+ }
+ }
+
+ /**
+ * Close the underlying {@link Closeable}, if it's not {@code null}.
+ *
+ * @param closeable An object which implements {@link Closeable}.
+ * @throws Exception If {@link Closeable#close} fails.
+ */
+ public static <T extends Closeable> void close(T closeable) throws IOException {
+ if (closeable != null) {
+ closeable.close();
+ }
+ }
+
+ // Suppress default constructor for noninstantiability
+ private MaybeNull() { throw new AssertionError(); }
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/Preconditions.java b/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/Preconditions.java
new file mode 100644
index 0000000..8520a48
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/Preconditions.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 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 android.hardware.camera2.cts.helpers;
+
+import java.util.Objects;
+
+/**
+ * Helper set of methods to perform precondition checks before starting method execution.
+ *
+ * <p>Typically used to sanity check arguments or the current object state.</p>
+ */
+public final class Preconditions {
+
+ /**
+ * Checks that the value has the expected bitwise flags set.
+ *
+ * @param argName Name of the argument
+ * @param arg Argument to check
+ * @param flagsName Name of the bitwise flags
+ * @param flags Bit flags to check.
+ * @return arg
+ *
+ * @throws IllegalArgumentException if the bitwise flags weren't set
+ */
+ public static int checkBitFlags(String argName, int arg, String flagsName, int flags) {
+ if ((arg & flags) == 0) {
+ throw new IllegalArgumentException(
+ String.format("Argument '%s' must have flags '%s' set", argName, flagsName));
+ }
+
+ return arg;
+ }
+
+ /**
+ * Checks that the value is {@link Object#equals equal} to the expected value.
+ *
+ * @param argName Name of the argument
+ * @param arg Argument to check
+ * @param expectedName Name of the expected value
+ * @param expectedValue Expected value
+ * @return arg
+ *
+ * @throws IllegalArgumentException if the values were not equal
+ */
+ public static <T> T checkEquals(String argName, T arg,
+ String expectedName, T expectedValue) {
+ if (!Objects.equals(arg, expectedValue)) {
+ throw new IllegalArgumentException(
+ String.format(
+ "Argument '%s' must be equal to '%s' (was '%s', but expected '%s')",
+ argName, expectedName, arg, expectedValue));
+ }
+
+ return arg;
+ }
+
+ /**
+ * Checks that the value is not {@code null}.
+ *
+ * <p>
+ * Returns the value directly, so you can use {@code checkNotNull("value", value)} inline.
+ * </p>
+ *
+ * @param argName Name of the argument
+ * @param arg Argument to check
+ * @return arg
+ *
+ * @throws NullPointerException if arg was {@code null}
+ */
+ public static <T> T checkNotNull(String argName, T arg) {
+ if (arg == null) {
+ throw new NullPointerException("Argument '" + argName + "' must not be null");
+ }
+
+ return arg;
+ }
+
+ /**
+ * Checks that the state is currently {@link true}.
+ *
+ * @param message Message to raise an exception with if the state checking fails.
+ * @param state State to check
+ *
+ * @throws IllegalStateException if state was {@code false}
+ *
+ * @return The state value (always {@code true}).
+ */
+ public static boolean checkState(String message, boolean state) {
+ if (!state) {
+ throw new IllegalStateException(message);
+ }
+
+ return state;
+ }
+
+ // Suppress default constructor for noninstantiability
+ private Preconditions() { throw new AssertionError(); }
+}
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
new file mode 100644
index 0000000..bca554e
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/StaticMetadata.java
@@ -0,0 +1,1484 @@
+/*
+ * Copyright 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 android.hardware.camera2.cts.helpers;
+
+import android.graphics.Rect;
+import android.graphics.ImageFormat;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraCharacteristics.Key;
+import android.hardware.camera2.CameraMetadata;
+import android.util.Range;
+import android.util.Size;
+import android.hardware.camera2.cts.CameraTestUtils;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.util.Log;
+import android.util.Rational;
+
+import junit.framework.Assert;
+
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Helpers to get common static info out of the camera.
+ *
+ * <p>Avoid boiler plate by putting repetitive get/set patterns in this class.</p>
+ *
+ * <p>Attempt to be durable against the camera device having bad or missing metadata
+ * by providing reasonable defaults and logging warnings when that happens.</p>
+ */
+public class StaticMetadata {
+
+ private static final String TAG = "StaticMetadata";
+ private static final int IGNORE_SIZE_CHECK = -1;
+
+ // TODO: don't hardcode, generate from metadata XML
+ private static final int SENSOR_INFO_EXPOSURE_TIME_RANGE_SIZE = 2;
+ private static final int SENSOR_INFO_EXPOSURE_TIME_RANGE_MIN = 0;
+ private static final int SENSOR_INFO_EXPOSURE_TIME_RANGE_MAX = 1;
+ private static final long SENSOR_INFO_EXPOSURE_TIME_RANGE_MIN_AT_MOST = 100000L; // 100us
+ private static final long SENSOR_INFO_EXPOSURE_TIME_RANGE_MAX_AT_LEAST = 100000000; // 100ms
+ private static final int SENSOR_INFO_SENSITIVITY_RANGE_SIZE = 2;
+ private static final int SENSOR_INFO_SENSITIVITY_RANGE_MIN = 0;
+ private static final int SENSOR_INFO_SENSITIVITY_RANGE_MAX = 1;
+ private static final int SENSOR_INFO_SENSITIVITY_RANGE_MIN_AT_MOST = 100;
+ private static final int SENSOR_INFO_SENSITIVITY_RANGE_MAX_AT_LEAST = 1600;
+ private static final int STATISTICS_INFO_MAX_FACE_COUNT_MIN_AT_LEAST = 4;
+ private static final int TONEMAP_MAX_CURVE_POINTS_AT_LEAST = 64;
+ private static final int CONTROL_AE_COMPENSATION_RANGE_DEFAULT_MIN = -2;
+ private static final int CONTROL_AE_COMPENSATION_RANGE_DEFAULT_MAX = 2;
+ private static final Rational CONTROL_AE_COMPENSATION_STEP_DEFAULT = new Rational(1, 2);
+ private static final byte REQUEST_PIPELINE_MAX_DEPTH_MAX = 8;
+
+ // TODO: Consider making this work across any metadata object, not just camera characteristics
+ private final CameraCharacteristics mCharacteristics;
+ private final CheckLevel mLevel;
+ private final CameraErrorCollector mCollector;
+
+ public enum CheckLevel {
+ /** Only log warnings for metadata check failures. Execution continues. */
+ WARN,
+ /**
+ * Use ErrorCollector to collect the metadata check failures, Execution
+ * continues.
+ */
+ COLLECT,
+ /** Assert the metadata check failures. Execution aborts. */
+ ASSERT
+ }
+
+ /**
+ * Construct a new StaticMetadata object.
+ *
+ *<p> Default constructor, only log warnings for the static metadata check failures</p>
+ *
+ * @param characteristics static info for a camera
+ * @throws IllegalArgumentException if characteristics was null
+ */
+ public StaticMetadata(CameraCharacteristics characteristics) {
+ this(characteristics, CheckLevel.WARN, /*collector*/null);
+ }
+
+ /**
+ * Construct a new StaticMetadata object with {@link CameraErrorCollector}.
+ * <p>
+ * When level is not {@link CheckLevel.COLLECT}, the {@link CameraErrorCollector} will be
+ * ignored, otherwise, it will be used to log the check failures.
+ * </p>
+ *
+ * @param characteristics static info for a camera
+ * @param collector The {@link CameraErrorCollector} used by this StaticMetadata
+ * @throws IllegalArgumentException if characteristics or collector was null.
+ */
+ public StaticMetadata(CameraCharacteristics characteristics, CameraErrorCollector collector) {
+ this(characteristics, CheckLevel.COLLECT, collector);
+ }
+
+ /**
+ * Construct a new StaticMetadata object with {@link CheckLevel} and
+ * {@link CameraErrorCollector}.
+ * <p>
+ * When level is not {@link CheckLevel.COLLECT}, the {@link CameraErrorCollector} will be
+ * ignored, otherwise, it will be used to log the check failures.
+ * </p>
+ *
+ * @param characteristics static info for a camera
+ * @param level The {@link CheckLevel} of this StaticMetadata
+ * @param collector The {@link CameraErrorCollector} used by this StaticMetadata
+ * @throws IllegalArgumentException if characteristics was null or level was
+ * {@link CheckLevel.COLLECT} but collector was null.
+ */
+ public StaticMetadata(CameraCharacteristics characteristics, CheckLevel level,
+ CameraErrorCollector collector) {
+ if (characteristics == null) {
+ throw new IllegalArgumentException("characteristics was null");
+ }
+ if (level == CheckLevel.COLLECT && collector == null) {
+ throw new IllegalArgumentException("collector must valid when COLLECT level is set");
+ }
+
+ mCharacteristics = characteristics;
+ mLevel = level;
+ mCollector = collector;
+ }
+
+ /**
+ * Get the CameraCharacteristics associated with this StaticMetadata.
+ *
+ * @return A non-null CameraCharacteristics object
+ */
+ public CameraCharacteristics getCharacteristics() {
+ return mCharacteristics;
+ }
+
+ /**
+ * Whether or not the hardware level reported by android.info.supportedHardwareLevel
+ * is {@value CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_FULL}.
+ *
+ * <p>If the camera device is incorrectly reporting the hardwareLevel, this
+ * will always return {@code false}.</p>
+ *
+ * @return true if the device is FULL, false otherwise.
+ */
+ public boolean isHardwareLevelFull() {
+ Integer hwLevel = getValueFromKeyNonNull(
+ CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
+
+ if (hwLevel == null) {
+ return false;
+ }
+
+ // Normal. Device could be limited.
+ int hwLevelInt = hwLevel;
+ return hwLevelInt == CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_FULL;
+ }
+
+ /**
+ * Whether or not the per frame control is supported by the camera device.
+ *
+ * @return true if per frame control is supported, false otherwise.
+ */
+ public boolean isPerFrameControlSupported() {
+ Integer perFrameControl = getValueFromKeyNonNull(CameraCharacteristics.SYNC_MAX_LATENCY);
+
+ if (perFrameControl == null) {
+ return false;
+ }
+
+ return perFrameControl == CameraMetadata.SYNC_MAX_LATENCY_PER_FRAME_CONTROL;
+ }
+
+ /**
+ * Whether or not the hardware level reported by android.info.supportedHardwareLevel
+ * is {@value CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED}.
+ *
+ * <p>If the camera device is incorrectly reporting the hardwareLevel, this
+ * will always return {@code true}.</p>
+ *
+ * @return true if the device is LIMITED, false otherwise.
+ */
+ public boolean isHardwareLevelLimited() {
+ return !isHardwareLevelFull();
+ }
+
+ /**
+ * Get the exposure time value and clamp to the range if needed.
+ *
+ * @param exposure Input exposure time value to check.
+ * @return Exposure value in the legal range.
+ */
+ public long getExposureClampToRange(long exposure) {
+ long minExposure = getExposureMinimumOrDefault(Long.MAX_VALUE);
+ long maxExposure = getExposureMaximumOrDefault(Long.MIN_VALUE);
+ if (minExposure > SENSOR_INFO_EXPOSURE_TIME_RANGE_MIN_AT_MOST) {
+ failKeyCheck(CameraCharacteristics.SENSOR_INFO_EXPOSURE_TIME_RANGE,
+ String.format(
+ "Min value %d is too large, set to maximal legal value %d",
+ minExposure, SENSOR_INFO_EXPOSURE_TIME_RANGE_MIN_AT_MOST));
+ minExposure = SENSOR_INFO_EXPOSURE_TIME_RANGE_MIN_AT_MOST;
+ }
+ if (maxExposure < SENSOR_INFO_EXPOSURE_TIME_RANGE_MAX_AT_LEAST) {
+ failKeyCheck(CameraCharacteristics.SENSOR_INFO_EXPOSURE_TIME_RANGE,
+ String.format(
+ "Max value %d is too small, set to minimal legal value %d",
+ maxExposure, SENSOR_INFO_EXPOSURE_TIME_RANGE_MAX_AT_LEAST));
+ maxExposure = SENSOR_INFO_EXPOSURE_TIME_RANGE_MAX_AT_LEAST;
+ }
+
+ return Math.max(minExposure, Math.min(maxExposure, exposure));
+ }
+
+ /**
+ * Check if the camera device support focuser.
+ *
+ * @return true if camera device support focuser, false otherwise.
+ */
+ public boolean hasFocuser() {
+ return (getMinimumFocusDistanceChecked() > 0);
+ }
+
+ /**
+ * Check if the camera device has flash unit.
+ * @return true if flash unit is available, false otherwise.
+ */
+ public boolean hasFlash() {
+ return getFlashInfoChecked();
+ }
+
+ /**
+ * Get minimum focus distance.
+ *
+ * @return minimum focus distance, 0 if minimum focus distance is invalid.
+ */
+ public float getMinimumFocusDistanceChecked() {
+ Key<Float> key = CameraCharacteristics.LENS_INFO_MINIMUM_FOCUS_DISTANCE;
+ Float minFocusDistance = getValueFromKeyNonNull(key);
+
+ if (minFocusDistance == null) {
+ return 0.0f;
+ }
+
+ checkTrueForKey(key, " minFocusDistance value shouldn't be negative",
+ minFocusDistance >= 0);
+ if (minFocusDistance < 0) {
+ minFocusDistance = 0.0f;
+ }
+
+ return minFocusDistance;
+ }
+
+ /**
+ * Get focusDistanceCalibration.
+ *
+ * @return focusDistanceCalibration, UNCALIBRATED if value is invalid.
+ */
+ public int getFocusDistanceCalibrationChecked() {
+ Key<Integer> key = CameraCharacteristics.LENS_INFO_FOCUS_DISTANCE_CALIBRATION;
+ Integer calibration = getValueFromKeyNonNull(key);
+
+ if (calibration == null) {
+ return CameraMetadata.LENS_INFO_FOCUS_DISTANCE_CALIBRATION_UNCALIBRATED;
+ }
+
+ checkTrueForKey(key, " value is out of range" ,
+ calibration >= CameraMetadata.LENS_INFO_FOCUS_DISTANCE_CALIBRATION_UNCALIBRATED &&
+ calibration <= CameraMetadata.LENS_INFO_FOCUS_DISTANCE_CALIBRATION_CALIBRATED);
+
+ return calibration;
+ }
+
+ /**
+ * Get max 3A regions and do sanity check.
+ *
+ * @return 3A max regions supported by the camera device
+ */
+ public int[] get3aMaxRegionsChecked() {
+ Key<int[]> key = CameraCharacteristics.CONTROL_MAX_REGIONS;
+ int[] regionCounts = getValueFromKeyNonNull(key);
+
+ if (regionCounts == null) {
+ return new int[]{0, 0, 0};
+ }
+
+ checkTrueForKey(key, " value should contain 3 elements", regionCounts.length == 3);
+ return regionCounts;
+ }
+
+ /**
+ * Get the available anti-banding modes.
+ *
+ * @return The array contains available anti-banding modes.
+ */
+ public int[] getAeAvailableAntiBandingModesChecked() {
+ Key<int[]> key = CameraCharacteristics.CONTROL_AE_AVAILABLE_ANTIBANDING_MODES;
+ int[] modes = getValueFromKeyNonNull(key);
+
+ boolean foundAuto = false;
+ for (int mode : modes) {
+ checkTrueForKey(key, "mode value " + mode + " is out if range",
+ mode >= CameraMetadata.CONTROL_AE_ANTIBANDING_MODE_OFF ||
+ mode <= CameraMetadata.CONTROL_AE_ANTIBANDING_MODE_AUTO);
+ if (mode == CameraMetadata.CONTROL_AE_ANTIBANDING_MODE_AUTO) {
+ foundAuto = true;
+ return modes;
+ }
+ }
+ // Must contain AUTO mode.
+ checkTrueForKey(key, "AUTO mode is missing", foundAuto);
+
+ return modes;
+ }
+
+ /**
+ * Check if the antibanding OFF mode is supported.
+ *
+ * @return true if antibanding OFF mode is supported, false otherwise.
+ */
+ public boolean isAntiBandingOffModeSupported() {
+ List<Integer> antiBandingModes =
+ Arrays.asList(CameraTestUtils.toObject(getAeAvailableAntiBandingModesChecked()));
+
+ return antiBandingModes.contains(CameraMetadata.CONTROL_AE_ANTIBANDING_MODE_OFF);
+ }
+
+ public Boolean getFlashInfoChecked() {
+ Key<Boolean> key = CameraCharacteristics.FLASH_INFO_AVAILABLE;
+ Boolean hasFlash = getValueFromKeyNonNull(key);
+
+ // In case the failOnKey only gives warning.
+ if (hasFlash == null) {
+ return false;
+ }
+
+ return hasFlash;
+ }
+
+ public int[] getAvailableTestPatternModesChecked() {
+ Key<int[]> key =
+ CameraCharacteristics.SENSOR_AVAILABLE_TEST_PATTERN_MODES;
+ int[] modes = getValueFromKeyNonNull(key);
+
+ if (modes == null) {
+ return new int[0];
+ }
+
+ int expectValue = CameraCharacteristics.SENSOR_TEST_PATTERN_MODE_OFF;
+ Integer[] boxedModes = CameraTestUtils.toObject(modes);
+ checkTrueForKey(key, " value must contain OFF mode",
+ Arrays.asList(boxedModes).contains(expectValue));
+
+ return modes;
+ }
+
+ /**
+ * Get available thumbnail sizes and do the sanity check.
+ *
+ * @return The array of available thumbnail sizes
+ */
+ public Size[] getAvailableThumbnailSizesChecked() {
+ Key<Size[]> key = CameraCharacteristics.JPEG_AVAILABLE_THUMBNAIL_SIZES;
+ Size[] sizes = getValueFromKeyNonNull(key);
+ final List<Size> sizeList = Arrays.asList(sizes);
+
+ // Size must contain (0, 0).
+ checkTrueForKey(key, "size should contain (0, 0)", sizeList.contains(new Size(0, 0)));
+
+ // Each size must be distinct.
+ checkElementDistinct(key, sizeList);
+
+ // Must be sorted in ascending order by area, by width if areas are same.
+ List<Size> orderedSizes =
+ CameraTestUtils.getAscendingOrderSizes(sizeList, /*ascending*/true);
+ checkTrueForKey(key, "Sizes should be in ascending order: Original " + sizeList.toString()
+ + ", Expected " + orderedSizes.toString(), orderedSizes.equals(sizeList));
+
+ // TODO: Aspect ratio match, need wait for android.scaler.availableStreamConfigurations
+ // implementation see b/12958122.
+
+ return sizes;
+ }
+
+ /**
+ * Get available focal lengths and do the sanity check.
+ *
+ * @return The array of available focal lengths
+ */
+ public float[] getAvailableFocalLengthsChecked() {
+ Key<float[]> key = CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS;
+ float[] focalLengths = getValueFromKeyNonNull(key);
+
+ checkTrueForKey(key, "Array should contain at least one element", focalLengths.length >= 1);
+
+ for (int i = 0; i < focalLengths.length; i++) {
+ checkTrueForKey(key,
+ String.format("focalLength[%d] %f should be positive.", i, focalLengths[i]),
+ focalLengths[i] > 0);
+ }
+ checkElementDistinct(key, Arrays.asList(CameraTestUtils.toObject(focalLengths)));
+
+ return focalLengths;
+ }
+
+ /**
+ * Get available apertures and do the sanity check.
+ *
+ * @return The non-null array of available apertures
+ */
+ public float[] getAvailableAperturesChecked() {
+ Key<float[]> key = CameraCharacteristics.LENS_INFO_AVAILABLE_APERTURES;
+ float[] apertures = getValueFromKeyNonNull(key);
+
+ checkTrueForKey(key, "Array should contain at least one element", apertures.length >= 1);
+
+ for (int i = 0; i < apertures.length; i++) {
+ checkTrueForKey(key,
+ String.format("apertures[%d] %f should be positive.", i, apertures[i]),
+ apertures[i] > 0);
+ }
+ checkElementDistinct(key, Arrays.asList(CameraTestUtils.toObject(apertures)));
+
+ return apertures;
+ }
+
+ /**
+ * Get and check available face detection modes.
+ *
+ * @return The non-null array of available face detection modes
+ */
+ public int[] getAvailableFaceDetectModesChecked() {
+ Key<int[]> key = CameraCharacteristics.STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES;
+ int[] modes = getValueFromKeyNonNull(key);
+
+ if (modes == null) {
+ return new int[0];
+ }
+
+ List<Integer> modeList = Arrays.asList(CameraTestUtils.toObject(modes));
+ checkTrueForKey(key, "Array should contain OFF mode",
+ modeList.contains(CameraMetadata.STATISTICS_FACE_DETECT_MODE_OFF));
+ checkElementDistinct(key, modeList);
+ checkArrayValuesInRange(key, modes, CameraMetadata.STATISTICS_FACE_DETECT_MODE_OFF,
+ CameraMetadata.STATISTICS_FACE_DETECT_MODE_FULL);
+
+ return modes;
+ }
+
+ /**
+ * Get and check max face detected count.
+ *
+ * @return max number of faces that can be detected
+ */
+ public int getMaxFaceCountChecked() {
+ Key<Integer> key = CameraCharacteristics.STATISTICS_INFO_MAX_FACE_COUNT;
+ Integer count = getValueFromKeyNonNull(key);
+
+ if (count == null) {
+ return 0;
+ }
+
+ List<Integer> faceDetectModes =
+ Arrays.asList(CameraTestUtils.toObject(getAvailableFaceDetectModesChecked()));
+ if (faceDetectModes.contains(CameraMetadata.STATISTICS_FACE_DETECT_MODE_OFF) &&
+ faceDetectModes.size() == 1) {
+ checkTrueForKey(key, " value must be 0 if only OFF mode is supported in "
+ + "availableFaceDetectionModes", count == 0);
+ } else {
+ int maxFaceCountAtLeat = STATISTICS_INFO_MAX_FACE_COUNT_MIN_AT_LEAST;
+ checkTrueForKey(key, " value must be no less than " + maxFaceCountAtLeat + " if SIMPLE"
+ + "or FULL is also supported in availableFaceDetectionModes",
+ count >= maxFaceCountAtLeat);
+ }
+
+ return count;
+ }
+
+ /**
+ * Get and check the available tone map modes.
+ *
+ * @return the availalbe tone map modes
+ */
+ public int[] getAvailableToneMapModesChecked() {
+ Key<int[]> key = CameraCharacteristics.TONEMAP_AVAILABLE_TONE_MAP_MODES;
+ int[] modes = getValueFromKeyNonNull(key);
+
+ if (modes == null) {
+ return new int[0];
+ }
+
+ List<Integer> modeList = Arrays.asList(CameraTestUtils.toObject(modes));
+ checkTrueForKey(key, " Camera devices must always support FAST mode",
+ modeList.contains(CameraMetadata.TONEMAP_MODE_FAST));
+ if (isHardwareLevelFull()) {
+ checkTrueForKey(key, "Full-capability camera devices must support"
+ + "CONTRAST_CURVE mode",
+ modeList.contains(CameraMetadata.TONEMAP_MODE_CONTRAST_CURVE) &&
+ modeList.contains(CameraMetadata.TONEMAP_MODE_FAST));
+ }
+ checkElementDistinct(key, modeList);
+ checkArrayValuesInRange(key, modes, CameraMetadata.TONEMAP_MODE_CONTRAST_CURVE,
+ CameraMetadata.TONEMAP_MODE_HIGH_QUALITY);
+
+ return modes;
+ }
+
+ /**
+ * Get and check max tonemap curve point.
+ *
+ * @return Max tonemap curve points.
+ */
+ public int getMaxTonemapCurvePointChecked() {
+ Key<Integer> key = CameraCharacteristics.TONEMAP_MAX_CURVE_POINTS;
+ Integer count = getValueFromKeyNonNull(key);
+
+ if (count == null) {
+ return 0;
+ }
+
+ List<Integer> modeList =
+ Arrays.asList(CameraTestUtils.toObject(getAvailableToneMapModesChecked()));
+ if (modeList.contains(CameraMetadata.TONEMAP_MODE_CONTRAST_CURVE)) {
+ checkTrueForKey(key, "Full-capability camera device must support maxCurvePoints "
+ + ">= " + TONEMAP_MAX_CURVE_POINTS_AT_LEAST,
+ count >= TONEMAP_MAX_CURVE_POINTS_AT_LEAST);
+ }
+
+ return count;
+ }
+
+ /**
+ * Get and check pixel array size.
+ */
+ public Size getPixelArraySizeChecked() {
+ Key<Size> key = CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE;
+ Size pixelArray = getValueFromKeyNonNull(key);
+ if (pixelArray == null) {
+ return new Size(0, 0);
+ }
+
+ return pixelArray;
+ }
+
+ /**
+ * Get and check active array size.
+ */
+ public Rect getActiveArraySizeChecked() {
+ Key<Rect> key = CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE;
+ Rect activeArray = getValueFromKeyNonNull(key);
+
+ if (activeArray == null) {
+ return new Rect(0, 0, 0, 0);
+ }
+
+ Size pixelArraySize = getPixelArraySizeChecked();
+ checkTrueForKey(key, "values left/top are invalid", activeArray.left >= 0 && activeArray.top >= 0);
+ checkTrueForKey(key, "values width/height are invalid",
+ activeArray.width() <= pixelArraySize.getWidth() &&
+ activeArray.height() <= pixelArraySize.getHeight());
+
+ return activeArray;
+ }
+
+ /**
+ * Get the sensitivity value and clamp to the range if needed.
+ *
+ * @param sensitivity Input sensitivity value to check.
+ * @return Sensitivity value in legal range.
+ */
+ public int getSensitivityClampToRange(int sensitivity) {
+ int minSensitivity = getSensitivityMinimumOrDefault(Integer.MAX_VALUE);
+ int maxSensitivity = getSensitivityMaximumOrDefault(Integer.MIN_VALUE);
+ if (minSensitivity > SENSOR_INFO_SENSITIVITY_RANGE_MIN_AT_MOST) {
+ failKeyCheck(CameraCharacteristics.SENSOR_INFO_SENSITIVITY_RANGE,
+ String.format(
+ "Min value %d is too large, set to maximal legal value %d",
+ minSensitivity, SENSOR_INFO_SENSITIVITY_RANGE_MIN_AT_MOST));
+ minSensitivity = SENSOR_INFO_SENSITIVITY_RANGE_MIN_AT_MOST;
+ }
+ if (maxSensitivity < SENSOR_INFO_SENSITIVITY_RANGE_MAX_AT_LEAST) {
+ failKeyCheck(CameraCharacteristics.SENSOR_INFO_SENSITIVITY_RANGE,
+ String.format(
+ "Max value %d is too small, set to minimal legal value %d",
+ maxSensitivity, SENSOR_INFO_SENSITIVITY_RANGE_MAX_AT_LEAST));
+ maxSensitivity = SENSOR_INFO_SENSITIVITY_RANGE_MAX_AT_LEAST;
+ }
+
+ return Math.max(minSensitivity, Math.min(maxSensitivity, sensitivity));
+ }
+
+ /**
+ * Get maxAnalogSensitivity for a camera device.
+ * <p>
+ * This is only available for FULL capability device, return 0 if it is unavailable.
+ * </p>
+ *
+ * @return maxAnalogSensitivity, 0 if it is not available.
+ */
+ public int getMaxAnalogSensitivityChecked() {
+ if (!isHardwareLevelFull()) {
+ return 0;
+ }
+
+ Key<Integer> key = CameraCharacteristics.SENSOR_MAX_ANALOG_SENSITIVITY;
+ Integer maxAnalogsensitivity = getValueFromKeyNonNull(key);
+ int minSensitivity = getSensitivityMinimumOrDefault();
+ int maxSensitivity = getSensitivityMaximumOrDefault();
+
+ if (maxAnalogsensitivity == null) {
+ return 0;
+ }
+
+ checkTrueForKey(key, " Max analog sensitivity " + maxAnalogsensitivity
+ + " should be no larger than max sensitivity " + maxSensitivity,
+ maxAnalogsensitivity <= maxSensitivity);
+ checkTrueForKey(key, " Max analog sensitivity " + maxAnalogsensitivity
+ + " should be larger than min sensitivity " + maxSensitivity,
+ maxAnalogsensitivity > minSensitivity);
+
+ return maxAnalogsensitivity;
+ }
+
+ /**
+ * Get hyperfocalDistance and do the sanity check.
+ * <p>
+ * Note that, this tag is optional, will return -1 if this tag is not
+ * available.
+ * </p>
+ *
+ * @return hyperfocalDistance of this device, -1 if this tag is not available.
+ */
+ public float getHyperfocalDistanceChecked() {
+ Key<Float> key = CameraCharacteristics.LENS_INFO_HYPERFOCAL_DISTANCE;
+ Float hyperfocalDistance = getValueFromKeyNonNull(key);
+ if (hyperfocalDistance == null) {
+ return -1;
+ }
+
+ if (hasFocuser()) {
+ float minFocusDistance = getMinimumFocusDistanceChecked();
+ checkTrueForKey(key, String.format(" hyperfocal distance %f should be in the range of"
+ + " should be in the range of (%f, %f]", hyperfocalDistance, 0.0f,
+ minFocusDistance),
+ hyperfocalDistance > 0 && hyperfocalDistance <= minFocusDistance);
+ }
+
+ return hyperfocalDistance;
+ }
+
+ /**
+ * Get the minimum value for a sensitivity range from android.sensor.info.sensitivityRange.
+ *
+ * <p>If the camera is incorrectly reporting values, log a warning and return
+ * the default value instead, which is the largest minimum value required to be supported
+ * by all camera devices.</p>
+ *
+ * @return The value reported by the camera device or the defaultValue otherwise.
+ */
+ public int getSensitivityMinimumOrDefault() {
+ return getSensitivityMinimumOrDefault(SENSOR_INFO_SENSITIVITY_RANGE_MIN_AT_MOST);
+ }
+
+ /**
+ * Get the minimum value for a sensitivity range from android.sensor.info.sensitivityRange.
+ *
+ * <p>If the camera is incorrectly reporting values, log a warning and return
+ * the default value instead.</p>
+ *
+ * @param defaultValue Value to return if no legal value is available
+ * @return The value reported by the camera device or the defaultValue otherwise.
+ */
+ public int getSensitivityMinimumOrDefault(int defaultValue) {
+ Range<Integer> range = getValueFromKeyNonNull(
+ CameraCharacteristics.SENSOR_INFO_SENSITIVITY_RANGE);
+ if (range == null) {
+ failKeyCheck(CameraCharacteristics.SENSOR_INFO_SENSITIVITY_RANGE,
+ "had no valid minimum value; using default of " + defaultValue);
+ return defaultValue;
+ }
+ return range.getLower();
+ }
+
+ /**
+ * Get the maximum value for a sensitivity range from android.sensor.info.sensitivityRange.
+ *
+ * <p>If the camera is incorrectly reporting values, log a warning and return
+ * the default value instead, which is the smallest maximum value required to be supported
+ * by all camera devices.</p>
+ *
+ * @return The value reported by the camera device or the defaultValue otherwise.
+ */
+ public int getSensitivityMaximumOrDefault() {
+ return getSensitivityMaximumOrDefault(SENSOR_INFO_SENSITIVITY_RANGE_MAX_AT_LEAST);
+ }
+
+ /**
+ * Get the maximum value for a sensitivity range from android.sensor.info.sensitivityRange.
+ *
+ * <p>If the camera is incorrectly reporting values, log a warning and return
+ * the default value instead.</p>
+ *
+ * @param defaultValue Value to return if no legal value is available
+ * @return The value reported by the camera device or the defaultValue otherwise.
+ */
+ public int getSensitivityMaximumOrDefault(int defaultValue) {
+ Range<Integer> range = getValueFromKeyNonNull(
+ CameraCharacteristics.SENSOR_INFO_SENSITIVITY_RANGE);
+ if (range == null) {
+ failKeyCheck(CameraCharacteristics.SENSOR_INFO_SENSITIVITY_RANGE,
+ "had no valid maximum value; using default of " + defaultValue);
+ return defaultValue;
+ }
+ return range.getUpper();
+ }
+
+ /**
+ * Get the minimum value for an exposure range from android.sensor.info.exposureTimeRange.
+ *
+ * <p>If the camera is incorrectly reporting values, log a warning and return
+ * the default value instead.</p>
+ *
+ * @param defaultValue Value to return if no legal value is available
+ * @return The value reported by the camera device or the defaultValue otherwise.
+ */
+ public long getExposureMinimumOrDefault(long defaultValue) {
+ Range<Long> range = getValueFromKeyNonNull(
+ CameraCharacteristics.SENSOR_INFO_EXPOSURE_TIME_RANGE);
+ if (range == null) {
+ failKeyCheck(CameraCharacteristics.SENSOR_INFO_EXPOSURE_TIME_RANGE,
+ "had no valid minimum value; using default of " + defaultValue);
+ return defaultValue;
+ }
+ return range.getLower();
+ }
+
+ /**
+ * Get the minimum value for an exposure range from android.sensor.info.exposureTimeRange.
+ *
+ * <p>If the camera is incorrectly reporting values, log a warning and return
+ * the default value instead, which is the largest minimum value required to be supported
+ * by all camera devices.</p>
+ *
+ * @return The value reported by the camera device or the defaultValue otherwise.
+ */
+ public long getExposureMinimumOrDefault() {
+ return getExposureMinimumOrDefault(SENSOR_INFO_EXPOSURE_TIME_RANGE_MIN_AT_MOST);
+ }
+
+ /**
+ * Get the maximum value for an exposure range from android.sensor.info.exposureTimeRange.
+ *
+ * <p>If the camera is incorrectly reporting values, log a warning and return
+ * the default value instead.</p>
+ *
+ * @param defaultValue Value to return if no legal value is available
+ * @return The value reported by the camera device or the defaultValue otherwise.
+ */
+ public long getExposureMaximumOrDefault(long defaultValue) {
+ Range<Long> range = getValueFromKeyNonNull(
+ CameraCharacteristics.SENSOR_INFO_EXPOSURE_TIME_RANGE);
+ if (range == null) {
+ failKeyCheck(CameraCharacteristics.SENSOR_INFO_EXPOSURE_TIME_RANGE,
+ "had no valid maximum value; using default of " + defaultValue);
+ return defaultValue;
+ }
+ return range.getUpper();
+ }
+
+ /**
+ * Get the maximum value for an exposure range from android.sensor.info.exposureTimeRange.
+ *
+ * <p>If the camera is incorrectly reporting values, log a warning and return
+ * the default value instead, which is the smallest maximum value required to be supported
+ * by all camera devices.</p>
+ *
+ * @return The value reported by the camera device or the defaultValue otherwise.
+ */
+ public long getExposureMaximumOrDefault() {
+ return getExposureMaximumOrDefault(SENSOR_INFO_EXPOSURE_TIME_RANGE_MAX_AT_LEAST);
+ }
+
+ /**
+ * Get aeAvailableModes and do the sanity check.
+ *
+ * <p>Depending on the check level this class has, for WAR or COLLECT levels,
+ * If the aeMode list is invalid, return an empty mode array. The the caller doesn't
+ * have to abort the execution even the aeMode list is invalid.</p>
+ * @return AE available modes
+ */
+ public int[] getAeAvailableModesChecked() {
+ Key<int[]> modesKey = CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES;
+ int[] modes = getValueFromKeyNonNull(modesKey);
+ if (modes == null) {
+ modes = new int[0];
+ }
+ List<Integer> modeList = new ArrayList<Integer>();
+ for (int mode : modes) {
+ modeList.add((int)(mode));
+ }
+ checkTrueForKey(modesKey, "value is empty", !modeList.isEmpty());
+
+ // All camera device must support ON
+ checkTrueForKey(modesKey, "values " + modeList.toString() + " must contain ON mode",
+ modeList.contains(CameraMetadata.CONTROL_AE_MODE_ON));
+
+ // All camera devices with flash units support ON_AUTO_FLASH and ON_ALWAYS_FLASH
+ Key<Boolean> flashKey= CameraCharacteristics.FLASH_INFO_AVAILABLE;
+ Boolean hasFlash = getValueFromKeyNonNull(flashKey);
+ if (hasFlash == null) {
+ hasFlash = false;
+ }
+ if (hasFlash) {
+ boolean flashModeConsistentWithFlash =
+ modeList.contains(CameraMetadata.CONTROL_AE_MODE_ON_AUTO_FLASH) &&
+ modeList.contains(CameraMetadata.CONTROL_AE_MODE_ON_ALWAYS_FLASH);
+ checkTrueForKey(modesKey,
+ "value must contain ON_AUTO_FLASH and ON_ALWAYS_FLASH and when flash is" +
+ "available", flashModeConsistentWithFlash);
+ } else {
+ boolean flashModeConsistentWithoutFlash =
+ !(modeList.contains(CameraMetadata.CONTROL_AE_MODE_ON_AUTO_FLASH) ||
+ modeList.contains(CameraMetadata.CONTROL_AE_MODE_ON_ALWAYS_FLASH) ||
+ modeList.contains(CameraMetadata.CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE));
+ checkTrueForKey(modesKey,
+ "value must not contain ON_AUTO_FLASH, ON_ALWAYS_FLASH and" +
+ "ON_AUTO_FLASH_REDEYE when flash is unavailable",
+ flashModeConsistentWithoutFlash);
+ }
+
+ // FULL mode camera devices always support OFF mode.
+ boolean condition =
+ !isHardwareLevelFull() || modeList.contains(CameraMetadata.CONTROL_AE_MODE_OFF);
+ checkTrueForKey(modesKey, "Full capability device must have OFF mode", condition);
+
+ // Boundary check.
+ for (int mode : modes) {
+ checkTrueForKey(modesKey, "Value " + mode + " is out of bound",
+ mode >= CameraMetadata.CONTROL_AE_MODE_OFF
+ && mode <= CameraMetadata.CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE);
+ }
+
+ return modes;
+ }
+
+ /**
+ * Get available AWB modes and do the sanity check.
+ *
+ * @return array that contains available AWB modes, empty array if awbAvailableModes is
+ * unavailable.
+ */
+ public int[] getAwbAvailableModesChecked() {
+ Key<int[]> key =
+ CameraCharacteristics.CONTROL_AWB_AVAILABLE_MODES;
+ int[] awbModes = getValueFromKeyNonNull(key);
+
+ if (awbModes == null) {
+ return new int[0];
+ }
+
+ List<Integer> modesList = Arrays.asList(CameraTestUtils.toObject(awbModes));
+ checkTrueForKey(key, " All camera devices must support AUTO mode",
+ modesList.contains(CameraMetadata.CONTROL_AWB_MODE_AUTO));
+ if (isHardwareLevelFull()) {
+ checkTrueForKey(key, " Full capability camera devices must support OFF mode",
+ modesList.contains(CameraMetadata.CONTROL_AWB_MODE_OFF));
+ }
+
+ return awbModes;
+ }
+
+ /**
+ * Get available AF modes and do the sanity check.
+ *
+ * @return array that contains available AF modes, empty array if afAvailableModes is
+ * unavailable.
+ */
+ public int[] getAfAvailableModesChecked() {
+ Key<int[]> key =
+ CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES;
+ int[] afModes = getValueFromKeyNonNull(key);
+
+ if (afModes == null) {
+ return new int[0];
+ }
+
+ List<Integer> modesList = Arrays.asList(CameraTestUtils.toObject(afModes));
+ checkTrueForKey(key, " All camera devices must support OFF mode",
+ modesList.contains(CameraMetadata.CONTROL_AF_MODE_OFF));
+ if (hasFocuser()) {
+ checkTrueForKey(key, " Camera devices that have focuser units must support AUTO mode",
+ modesList.contains(CameraMetadata.CONTROL_AF_MODE_AUTO));
+ }
+
+ return afModes;
+ }
+
+ /**
+ * Get supported raw output sizes and do the check.
+ *
+ * @return Empty size array if raw output is not supported
+ */
+ public Size[] getRawOutputSizesChecked() {
+ return getAvailableSizesForFormatChecked(ImageFormat.RAW_SENSOR,
+ StreamDirection.Output);
+ }
+
+ /**
+ * Get supported jpeg output sizes and do the check.
+ *
+ * @return Empty size array if jpeg output is not supported
+ */
+ public Size[] getJpegOutputSizeChecked() {
+ return getAvailableSizesForFormatChecked(ImageFormat.JPEG,
+ StreamDirection.Output);
+ }
+
+ /**
+ * Used to determine the stream direction for various helpers that look up
+ * format or size information.
+ */
+ public enum StreamDirection {
+ /** Stream is used with {@link android.hardware.camera2.CameraDevice#configureOutputs} */
+ Output,
+ /** Stream is used with {@code CameraDevice#configureInputs} -- NOT YET PUBLIC */
+ Input
+ }
+
+ /**
+ * Get available sizes for given user-defined format.
+ *
+ * <p><strong>Does not</strong> work with implementation-defined format.</p>
+ *
+ * @param format The format for the requested size array.
+ * @param direction The stream direction, input or output.
+ * @return The sizes of the given format, empty array if no available size is found.
+ */
+ public Size[] getAvailableSizesForFormatChecked(int format, StreamDirection direction) {
+ Key<StreamConfigurationMap> key =
+ CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP;
+ StreamConfigurationMap config = getValueFromKeyNonNull(key);
+
+ if (config == null) {
+ return new Size[0];
+ }
+
+ android.util.Size[] utilSizes;
+
+ switch (direction) {
+ case Output:
+ utilSizes = config.getOutputSizes(format);
+ break;
+ case Input:
+ utilSizes = config.getInputSizes(format);
+ break;
+ default:
+ throw new IllegalArgumentException("direction must be output or input");
+ }
+
+ // TODO: Get rid of android.util.Size
+ if (utilSizes == null) {
+ Log.i(TAG, "This camera doesn't support format " + format + " for " + direction);
+ return new Size[0];
+ }
+
+ Size[] sizes = new Size[utilSizes.length];
+ for (int i = 0; i < utilSizes.length; ++i) {
+ sizes[i] = new Size(utilSizes[i].getWidth(), utilSizes[i].getHeight());
+ }
+
+ return sizes;
+ }
+
+ /**
+ * Get available AE target fps ranges.
+ *
+ * @return Empty int array if aeAvailableTargetFpsRanges is invalid.
+ */
+ @SuppressWarnings("raw")
+ public Range<Integer>[] getAeAvailableTargetFpsRangesChecked() {
+ Key<Range<Integer>[]> key =
+ CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES;
+ Range<Integer>[] fpsRanges = getValueFromKeyNonNull(key);
+
+ if (fpsRanges == null) {
+ return new Range[0];
+ }
+
+ // Round down to 2 boundary if it is not integer times of 2, to avoid array out of bound
+ // in case the above check fails.
+ int fpsRangeLength = fpsRanges.length;
+ int minFps, maxFps;
+ long maxFrameDuration = getMaxFrameDurationChecked();
+ for (int i = 0; i < fpsRangeLength; i += 1) {
+ minFps = fpsRanges[i].getLower();
+ maxFps = fpsRanges[i].getUpper();
+ checkTrueForKey(key, " min fps must be no larger than max fps!",
+ minFps > 0 && maxFps >= minFps);
+ long maxDuration = (long) (1e9 / minFps);
+ checkTrueForKey(key, String.format(
+ " the frame duration %d for min fps %d must smaller than maxFrameDuration %d",
+ maxDuration, minFps, maxFrameDuration), maxDuration <= maxFrameDuration);
+ }
+
+ return fpsRanges;
+ }
+
+ /**
+ * Get max frame duration.
+ *
+ * @return 0 if maxFrameDuration is null
+ */
+ public long getMaxFrameDurationChecked() {
+ Key<Long> key =
+ CameraCharacteristics.SENSOR_INFO_MAX_FRAME_DURATION;
+ Long maxDuration = getValueFromKeyNonNull(key);
+
+ if (maxDuration == null) {
+ return 0;
+ }
+
+ return maxDuration;
+ }
+
+ /**
+ * Get available minimal frame durations for a given user-defined format.
+ *
+ * <p><strong>Does not</strong> work with implementation-defined format.</p>
+ *
+ * @param format One of the format from {@link ImageFormat}.
+ * @return HashMap of minimal frame durations for different sizes, empty HashMap
+ * if availableMinFrameDurations is null.
+ */
+ public HashMap<Size, Long> getAvailableMinFrameDurationsForFormatChecked(int format) {
+
+ HashMap<Size, Long> minDurationMap = new HashMap<Size, Long>();
+
+ Key<StreamConfigurationMap> key =
+ CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP;
+ StreamConfigurationMap config = getValueFromKeyNonNull(key);
+
+ if (config == null) {
+ return minDurationMap;
+ }
+
+ for (android.util.Size size : config.getOutputSizes(format)) {
+ long minFrameDuration = config.getOutputMinFrameDuration(format, size);
+
+ minDurationMap.put(new Size(size.getWidth(), size.getHeight()), minFrameDuration);
+ }
+
+ return minDurationMap;
+ }
+
+ public int[] getAvailableEdgeModesChecked() {
+ Key<int[]> key = CameraCharacteristics.EDGE_AVAILABLE_EDGE_MODES;
+ int[] edgeModes = getValueFromKeyNonNull(key);
+
+ if (edgeModes == null) {
+ return new int[0];
+ }
+
+ // Full device should always include OFF and FAST
+ if (isHardwareLevelFull()) {
+ List<Integer> modeList = Arrays.asList(CameraTestUtils.toObject(edgeModes));
+ checkTrueForKey(key, "Full device must contain OFF and FAST edge modes",
+ modeList.contains(CameraMetadata.EDGE_MODE_OFF) &&
+ modeList.contains(CameraMetadata.EDGE_MODE_FAST));
+ }
+
+ return edgeModes;
+ }
+
+ public int[] getAvailableNoiseReductionModesChecked() {
+ Key<int[]> key =
+ CameraCharacteristics.NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES;
+ int[] noiseReductionModes = getValueFromKeyNonNull(key);
+
+ if (noiseReductionModes == null) {
+ return new int[0];
+ }
+
+ // Full device should always include OFF and FAST
+ if (isHardwareLevelFull()) {
+ List<Integer> modeList = Arrays.asList(CameraTestUtils.toObject(noiseReductionModes));
+ checkTrueForKey(key, "Full device must contain OFF and FAST noise reduction modes",
+ modeList.contains(CameraMetadata.NOISE_REDUCTION_MODE_OFF) &&
+ modeList.contains(CameraMetadata.NOISE_REDUCTION_MODE_FAST));
+ }
+
+ return noiseReductionModes;
+ }
+
+ /**
+ * Get value of key android.control.aeCompensationStep and do the sanity check.
+ *
+ * @return default value if the value is null.
+ */
+ public Rational getAeCompensationStepChecked() {
+ Key<Rational> key =
+ CameraCharacteristics.CONTROL_AE_COMPENSATION_STEP;
+ Rational compensationStep = getValueFromKeyNonNull(key);
+
+ if (compensationStep == null) {
+ // Return default step.
+ return CONTROL_AE_COMPENSATION_STEP_DEFAULT;
+ }
+
+ checkTrueForKey(key, " value must be no more than 1/2", compensationStep.toFloat() < 0.5f);
+ return compensationStep;
+ }
+
+ /**
+ * Get value of key android.control.aeCompensationRange and do the sanity check.
+ *
+ * @return default value if the value is null or malformed.
+ */
+ public Range<Integer> getAeCompensationRangeChecked() {
+ Key<Range<Integer>> key =
+ CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE;
+ Range<Integer> compensationRange = getValueFromKeyNonNull(key);
+ float compensationStep = getAeCompensationStepChecked().toFloat();
+ final Range<Integer> DEFAULT_RANGE = Range.create(
+ (int)(CONTROL_AE_COMPENSATION_RANGE_DEFAULT_MIN / compensationStep),
+ (int)(CONTROL_AE_COMPENSATION_RANGE_DEFAULT_MAX / compensationStep));
+ if (compensationRange == null) {
+ return DEFAULT_RANGE;
+ }
+
+ checkTrueForKey(key, " range value must be at least " + DEFAULT_RANGE,
+ compensationRange.getLower() <= DEFAULT_RANGE.getLower() &&
+ compensationRange.getUpper() >= DEFAULT_RANGE.getUpper());
+
+ return compensationRange;
+ }
+
+ /**
+ * Get availableVideoStabilizationModes and do the sanity check.
+ *
+ * @return available video stabilization modes, empty array if it is unavailable.
+ */
+ public int[] getAvailableVideoStabilizationModesChecked() {
+ Key<int[]> key =
+ CameraCharacteristics.CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES;
+ int[] modes = getValueFromKeyNonNull(key);
+
+ if (modes == null) {
+ return new int[0];
+ }
+
+ List<Integer> modeList = Arrays.asList(CameraTestUtils.toObject(modes));
+ checkTrueForKey(key, " All device should support OFF mode",
+ modeList.contains(CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_OFF));
+ checkArrayValuesInRange(key, modes,
+ CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_OFF,
+ CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_ON);
+
+ return modes;
+ }
+
+ /**
+ * Get availableOpticalStabilization and do the sanity check.
+ *
+ * @return available optical stabilization modes, empty array if it is unavailable.
+ */
+ public int[] getAvailableOpticalStabilizationChecked() {
+ Key<int[]> key =
+ CameraCharacteristics.LENS_INFO_AVAILABLE_OPTICAL_STABILIZATION;
+ int[] modes = getValueFromKeyNonNull(key);
+
+ if (modes == null) {
+ return new int[0];
+ }
+
+ checkArrayValuesInRange(key, modes,
+ CameraMetadata.LENS_OPTICAL_STABILIZATION_MODE_OFF,
+ CameraMetadata.LENS_OPTICAL_STABILIZATION_MODE_ON);
+
+ return modes;
+ }
+
+ /**
+ * Get availableMaxDigitalZoom and do the sanity check.
+ *
+ * @return available max digitial zoom, default value (1.0) if it is not available.
+ */
+ public float getAvailableMaxDigitalZoomChecked() {
+ Key<Float> key =
+ CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM;
+ Float maxZoom = getValueFromKeyNonNull(key);
+ if (maxZoom == null) {
+ return 1.0f;
+ }
+
+ checkTrueForKey(key, " max digitial zoom should be no less than 1",
+ maxZoom > 1.0f && !Float.isNaN(maxZoom));
+
+ return maxZoom;
+ }
+
+ public int[] getAvailableSceneModesChecked() {
+ Key<int[]> key =
+ CameraCharacteristics.CONTROL_AVAILABLE_SCENE_MODES;
+ int[] modes = getValueFromKeyNonNull(key);
+
+ if (modes == null) {
+ return new int[0];
+ }
+
+ List<Integer> modeList = Arrays.asList(CameraTestUtils.toObject(modes));
+ // FACE_PRIORITY must be included if face detection is supported.
+ if (getMaxFaceCountChecked() > 0) {
+ checkTrueForKey(key, " FACE_PRIORITY must be included if face detection is supported",
+ modeList.contains(CameraMetadata.CONTROL_SCENE_MODE_FACE_PRIORITY));
+ }
+
+ return modes;
+ }
+
+ public int[] getAvailableEffectModesChecked() {
+ Key<int[]> key =
+ CameraCharacteristics.CONTROL_AVAILABLE_EFFECTS;
+ int[] modes = getValueFromKeyNonNull(key);
+
+ if (modes == null) {
+ return new int[0];
+ }
+
+ List<Integer> modeList = Arrays.asList(CameraTestUtils.toObject(modes));
+ // OFF must be included.
+ checkTrueForKey(key, " OFF must be included",
+ modeList.contains(CameraMetadata.CONTROL_EFFECT_MODE_OFF));
+
+ return modes;
+ }
+
+ /**
+ * Get max pipeline depth and do the sanity check.
+ *
+ * @return max pipeline depth, default value if it is not available.
+ */
+ public byte getPipelineMaxDepthChecked() {
+ Key<Byte> key =
+ CameraCharacteristics.REQUEST_PIPELINE_MAX_DEPTH;
+ Byte maxDepth = getValueFromKeyNonNull(key);
+
+ if (maxDepth == null) {
+ return REQUEST_PIPELINE_MAX_DEPTH_MAX;
+ }
+
+ checkTrueForKey(key, " max pipeline depth should be no larger than "
+ + REQUEST_PIPELINE_MAX_DEPTH_MAX, maxDepth <= REQUEST_PIPELINE_MAX_DEPTH_MAX);
+
+ return maxDepth;
+ }
+
+ /**
+ * Get available capabilities and do the sanity check.
+ *
+ * @return reported available capabilities list, empty list if the value is unavailable.
+ */
+ public List<Integer> getAvailableCapabilitiesChecked() {
+ Key<int[]> key =
+ CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES;
+ int[] availableCaps = getValueFromKeyNonNull(key);
+ List<Integer> capList;
+
+ if (availableCaps == null) {
+ return new ArrayList<Integer>();
+ }
+
+ checkArrayValuesInRange(key, availableCaps,
+ CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE,
+ CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_DNG);
+ capList = Arrays.asList(CameraTestUtils.toObject(availableCaps));
+ return capList;
+ }
+
+ /**
+ * Get max number of output streams and do the basic sanity check.
+ *
+ * @return reported max number of output stream array, empty array if the value is unavailable.
+ */
+ public int[] getMaxNumOutputStreamsChecked() {
+ Key<int[]> key =
+ CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_STREAMS;
+ int[] maxNumStreams = getValueFromKeyNonNull(key);
+
+ if (maxNumStreams == null) {
+ return new int[0];
+ }
+
+ return maxNumStreams;
+ }
+
+ /**
+ * Get lens facing and do the sanity check
+ * @return lens facing, return default value (BACK) if value is unavailable.
+ */
+ public int getLensFacingChecked() {
+ Key<Integer> key =
+ CameraCharacteristics.LENS_FACING;
+ Integer facing = getValueFromKeyNonNull(key);
+
+ if (facing == null) {
+ return CameraCharacteristics.LENS_FACING_BACK;
+ }
+
+ checkTrueForKey(key, " value is out of range ",
+ facing >= CameraCharacteristics.LENS_FACING_FRONT &&
+ facing <= CameraCharacteristics.LENS_FACING_BACK);
+ return facing;
+ }
+
+ /**
+ * Get the value in index for a fixed-size array from a given key.
+ *
+ * <p>If the camera device is incorrectly reporting values, log a warning and return
+ * the default value instead.</p>
+ *
+ * @param key Key to fetch
+ * @param defaultValue Default value to return if camera device uses invalid values
+ * @param name Human-readable name for the array index (logging only)
+ * @param index Array index of the subelement
+ * @param size Expected fixed size of the array
+ *
+ * @return The value reported by the camera device, or the defaultValue otherwise.
+ */
+ private <T> T getArrayElementOrDefault(Key<?> key, T defaultValue, String name, int index,
+ int size) {
+ T elementValue = getArrayElementCheckRangeNonNull(
+ key,
+ index,
+ size);
+
+ if (elementValue == null) {
+ failKeyCheck(key,
+ "had no valid " + name + " value; using default of " + defaultValue);
+ elementValue = defaultValue;
+ }
+
+ return elementValue;
+ }
+
+ /**
+ * Fetch an array sub-element from an array value given by a key.
+ *
+ * <p>
+ * Prints a warning if the sub-element was null.
+ * </p>
+ *
+ * <p>Use for variable-size arrays since this does not check the array size.</p>
+ *
+ * @param key Metadata key to look up
+ * @param element A non-negative index value.
+ * @return The array sub-element, or null if the checking failed.
+ */
+ private <T> T getArrayElementNonNull(Key<?> key, int element) {
+ return getArrayElementCheckRangeNonNull(key, element, IGNORE_SIZE_CHECK);
+ }
+
+ /**
+ * Fetch an array sub-element from an array value given by a key.
+ *
+ * <p>
+ * Prints a warning if the array size does not match the size, or if the sub-element was null.
+ * </p>
+ *
+ * @param key Metadata key to look up
+ * @param element The index in [0,size)
+ * @param size A positive size value or otherwise {@value #IGNORE_SIZE_CHECK}
+ * @return The array sub-element, or null if the checking failed.
+ */
+ private <T> T getArrayElementCheckRangeNonNull(Key<?> key, int element, int size) {
+ Object array = getValueFromKeyNonNull(key);
+
+ if (array == null) {
+ // Warning already printed
+ return null;
+ }
+
+ if (size != IGNORE_SIZE_CHECK) {
+ int actualLength = Array.getLength(array);
+ if (actualLength != size) {
+ failKeyCheck(key,
+ String.format("had the wrong number of elements (%d), expected (%d)",
+ actualLength, size));
+ return null;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ T val = (T) Array.get(array, element);
+
+ if (val == null) {
+ failKeyCheck(key, "had a null element at index" + element);
+ return null;
+ }
+
+ return val;
+ }
+
+ /**
+ * Gets the key, logging warnings for null values.
+ */
+ private <T> T getValueFromKeyNonNull(Key<T> key) {
+ if (key == null) {
+ throw new IllegalArgumentException("key was null");
+ }
+
+ T value = mCharacteristics.get(key);
+
+ if (value == null) {
+ failKeyCheck(key, "was null");
+ }
+
+ return value;
+ }
+
+ private void checkArrayValuesInRange(Key<int[]> key, int[] array, int min, int max) {
+ for (int value : array) {
+ checkTrueForKey(key, String.format(" value is out of range [%d, %d]", min, max),
+ value <= max && value >= min);
+ }
+ }
+
+ private void checkArrayValuesInRange(Key<byte[]> key, byte[] array, byte min, byte max) {
+ for (byte value : array) {
+ checkTrueForKey(key, String.format(" value is out of range [%d, %d]", min, max),
+ value <= max && value >= min);
+ }
+ }
+
+ /**
+ * Check the uniqueness of the values in a list.
+ *
+ * @param key The key to be checked
+ * @param list The list contains the value of the key
+ */
+ private <U, T> void checkElementDistinct(Key<U> key, List<T> list) {
+ // Each size must be distinct.
+ Set<T> sizeSet = new HashSet<T>(list);
+ checkTrueForKey(key, "Each size must be distinct", sizeSet.size() == list.size());
+ }
+
+ private <T> void checkTrueForKey(Key<T> key, String message, boolean condition) {
+ if (!condition) {
+ failKeyCheck(key, message);
+ }
+ }
+
+ private <T> void failKeyCheck(Key<T> key, String message) {
+ // TODO: Consider only warning once per key/message combination if it's too spammy.
+ // TODO: Consider offering other options such as throwing an assertion exception
+ String failureCause = String.format("The static info key '%s' %s", key.getName(), message);
+ switch (mLevel) {
+ case WARN:
+ Log.w(TAG, failureCause);
+ break;
+ case COLLECT:
+ mCollector.addMessage(failureCause);
+ break;
+ case ASSERT:
+ Assert.fail(failureCause);
+ default:
+ throw new UnsupportedOperationException("Unhandled level " + mLevel);
+ }
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/UncheckedCloseable.java b/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/UncheckedCloseable.java
new file mode 100644
index 0000000..570ef2c
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/UncheckedCloseable.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 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 android.hardware.camera2.cts.helpers;
+
+/**
+ * Defines an interface for classes that can (or need to) be closed once they
+ * are not used any longer; calling the {@code close} method releases resources
+ * that the object holds.</p>
+ *
+ * <p>This signifies that the implementor will never throw checked exceptions when closing,
+ * allowing for more fine grained exception handling at call sites handling this interface
+ * generically.</p>
+ *
+ * <p>A common pattern for using an {@code UncheckedCloseable} resource:
+ * <pre> {@code
+ * // where <Foo extends UncheckedCloseable>
+ * UncheckedCloseable foo = new Foo();
+ * try {
+ * ...;
+ * } finally {
+ * foo.close();
+ * }
+ * }</pre>
+ */
+public interface UncheckedCloseable extends AutoCloseable {
+
+ /**
+ * Closes the object and release any system resources it holds.
+ *
+ * <p>Does not throw any checked exceptions.</p>
+ */
+ @Override
+ void close();
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/rs/AllocationCache.java b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/AllocationCache.java
new file mode 100644
index 0000000..e65e819
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/AllocationCache.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright 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 android.hardware.camera2.cts.rs;
+
+import android.hardware.camera2.cts.helpers.UncheckedCloseable;
+import android.renderscript.Allocation;
+import android.renderscript.RenderScript;
+import android.renderscript.Type;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static android.hardware.camera2.cts.helpers.Preconditions.*;
+
+/**
+ * Cache {@link Allocation} objects based on their type and usage.
+ *
+ * <p>This avoids expensive re-allocation of objects when they are used over and over again
+ * by different scripts.</p>
+ */
+public class AllocationCache implements UncheckedCloseable {
+
+ private static final String TAG = "AllocationCache";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private static int sDebugHits = 0;
+ private static int sDebugMisses = 0;
+
+ private final RenderScript mRS;
+ private final HashMap<AllocationKey, List<Allocation>> mAllocationMap =
+ new HashMap<AllocationKey, List<Allocation>>();
+ private boolean mClosed = false;
+
+ /**
+ * Create a new cache with the specified RenderScript context.
+ *
+ * @param rs A non-{@code null} RenderScript context.
+ *
+ * @throws NullPointerException if rs was null
+ */
+ public AllocationCache(RenderScript rs) {
+ mRS = checkNotNull("rs", rs);
+ }
+
+ /**
+ * Returns the {@link RenderScript} context associated with this AllocationCache.
+ *
+ * @return A non-{@code null} RenderScript value.
+ */
+ public RenderScript getRenderScript() {
+ return mRS;
+ }
+
+ /**
+ * Try to lookup a compatible Allocation from the cache, create one if none exist.
+ *
+ * @param type A non-{@code null} RenderScript Type.
+ * @throws NullPointerException if type was null
+ * @throws IllegalStateException if the cache was closed with {@link #close}
+ */
+ public synchronized Allocation getOrCreateTyped(Type type, int usage) {
+ checkNotNull("type", type);
+ checkNotClosed();
+
+ AllocationKey key = new AllocationKey(type, usage);
+ List<Allocation> list = mAllocationMap.get(key);
+
+ Allocation alloc;
+
+ if (list == null || list.isEmpty()) {
+ alloc = Allocation.createTyped(mRS, type, usage);
+
+ if (DEBUG) {
+ sDebugMisses++;
+ Log.d(TAG, String.format(
+ "Cache MISS (%d): type = '%s', usage = '%x'", sDebugMisses, type, usage));
+ }
+ } else {
+ alloc = list.remove(list.size() - 1);
+
+ if (DEBUG) {
+ sDebugHits++;
+ Log.d(TAG, String.format(
+ "Cache HIT (%d): type = '%s', usage = '%x'", sDebugHits, type, usage));
+ }
+ }
+
+ return alloc;
+ }
+
+ /**
+ * Return the Allocation to the cache.
+ *
+ * <p>Future calls to getOrCreateTyped with the same type and usage may
+ * return this allocation.</p>
+ *
+ * <p>Allocations that have usage {@link Allocation#USAGE_IO_INPUT} get their
+ * listeners reset. Those that have {@link Allocation#USAGE_IO_OUTPUT} get their
+ * surfaces reset.</p>
+ *
+ * @param allocation A non-{@code null} RenderScript {@link Allocation}
+ * @throws NullPointerException if allocation was null
+ * @throws IllegalArgumentException if the allocation was already returned previously
+ * @throws IllegalStateException if the cache was closed with {@link #close}
+ */
+ public synchronized void returnToCache(Allocation allocation) {
+ checkNotNull("allocation", allocation);
+ checkNotClosed();
+
+ int usage = allocation.getUsage();
+ AllocationKey key = new AllocationKey(allocation.getType(), usage);
+ List<Allocation> value = mAllocationMap.get(key);
+
+ if (value != null && value.contains(allocation)) {
+ throw new IllegalArgumentException("allocation was already returned to the cache");
+ }
+
+ if ((usage & Allocation.USAGE_IO_INPUT) != 0) {
+ allocation.setOnBufferAvailableListener(null);
+ }
+ if ((usage & Allocation.USAGE_IO_OUTPUT) != 0) {
+ allocation.setSurface(null);
+ }
+
+ if (value == null) {
+ value = new ArrayList<Allocation>(/*capacity*/1);
+ mAllocationMap.put(key, value);
+ }
+
+ value.add(allocation);
+
+ // TODO: Evict existing allocations from cache when we get too many items in it,
+ // to avoid running out of memory
+
+ // TODO: move to using android.util.LruCache under the hood
+ }
+
+ /**
+ * Return the allocation to the cache, if it wasn't {@code null}.
+ *
+ * <p>Future calls to getOrCreateTyped with the same type and usage may
+ * return this allocation.</p>
+ *
+ * <p>Allocations that have usage {@link Allocation#USAGE_IO_INPUT} get their
+ * listeners reset. Those that have {@link Allocation#USAGE_IO_OUTPUT} get their
+ * surfaces reset.</p>
+ *
+ * <p>{@code null} values are a no-op.</p>
+ *
+ * @param allocation A potentially {@code null} RenderScript {@link Allocation}
+ * @throws IllegalArgumentException if the allocation was already returned previously
+ * @throws IllegalStateException if the cache was closed with {@link #close}
+ */
+ public synchronized void returnToCacheIfNotNull(Allocation allocation) {
+ if (allocation != null) {
+ returnToCache(allocation);
+ }
+ }
+
+ /**
+ * Closes the object and destroys any Allocations still in the cache.
+ */
+ @Override
+ public synchronized void close() {
+ if (mClosed) return;
+
+ for (Map.Entry<AllocationKey, List<Allocation>> entry : mAllocationMap.entrySet()) {
+ List<Allocation> value = entry.getValue();
+
+ for (Allocation alloc : value) {
+ alloc.destroy();
+ }
+
+ value.clear();
+ }
+
+ mAllocationMap.clear();
+ mClosed = true;
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ close();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Holder class to check if one allocation is compatible with another.
+ *
+ * <p>An Allocation is considered compatible if both it's Type and usage is equivalent.</p>
+ */
+ private static class AllocationKey {
+ private final Type mType;
+ private final int mUsage;
+
+ public AllocationKey(Type type, int usage) {
+ mType = type;
+ mUsage = usage;
+ }
+
+ @Override
+ public int hashCode() {
+ return mType.hashCode() ^ mUsage;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof AllocationKey){
+ AllocationKey otherKey = (AllocationKey) other;
+
+ return otherKey.mType.equals(mType) && otherKey.mUsage == otherKey.mUsage;
+ }
+
+ return false;
+ }
+ }
+
+ private void checkNotClosed() {
+ if (mClosed == true) {
+ throw new IllegalStateException("AllocationCache has already been closed");
+ }
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/rs/AllocationInfo.java b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/AllocationInfo.java
new file mode 100644
index 0000000..3324783
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/AllocationInfo.java
@@ -0,0 +1,441 @@
+/*
+ * Copyright 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 android.hardware.camera2.cts.rs;
+
+import static android.hardware.camera2.cts.helpers.Preconditions.*;
+
+import android.graphics.ImageFormat;
+import android.graphics.PixelFormat;
+import android.util.Size;
+import android.renderscript.Allocation;
+import android.renderscript.Element;
+import android.renderscript.RenderScript;
+import android.renderscript.Type;
+import android.util.Log;
+
+/**
+ * Abstract the information necessary to create new {@link Allocation allocations} with
+ * their size, element, type, and usage.
+ *
+ * <p>This also includes convenience functions for printing to a string, something RenderScript
+ * lacks at the time of writing.</p>
+ *
+ * <p>Note that when creating a new {@link AllocationInfo} the usage flags <b>always</b> get ORd
+ * to {@link Allocation#USAGE_IO_SCRIPT}.</p>
+ */
+public class AllocationInfo {
+
+ private final RenderScript mRS = RenderScriptSingleton.getRS();
+
+ private final Size mSize;
+ private final Element mElement;
+ private final Type mType;
+ private final int mUsage;
+
+ private static final String TAG = "AllocationInfo";
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+ /**
+ * Create a new {@link AllocationInfo} holding the element, size, and usage
+ * from an existing {@link Allocation}.
+ *
+ * @param allocation {@link Allocation}
+ *
+ * @return A new {@link AllocationInfo}
+ *
+ * @throws NullPointerException if allocation was {@code null}.
+ */
+ public static AllocationInfo newInstance(Allocation allocation) {
+ checkNotNull("allocation", allocation);
+
+ return new AllocationInfo(allocation.getElement(),
+ new Size(allocation.getType().getX(), allocation.getType().getY()),
+ allocation.getUsage());
+ }
+
+ /**
+ * Create a new {@link AllocationInfo} holding the specified format, {@link Size},
+ * and {@link Allocation#USAGE_SCRIPT usage}.
+ *
+ * <p>The usage is always ORd with {@link Allocation#USAGE_SCRIPT}.</p>
+ *
+ * <p>The closest {@link Element} possible is created from the format.</p>
+ *
+ * @param size {@link Size}
+ * @param format An int format
+ * @param usage Usage flags
+ *
+ * @return A new {@link AllocationInfo} holding the given arguments.
+ *
+ * @throws NullPointerException if size was {@code null}.
+ *
+ * @see ImageFormat
+ * @see PixelFormat
+ */
+ public static AllocationInfo newInstance(Size size, int format, int usage) {
+ RenderScript rs = RenderScriptSingleton.getRS();
+
+ Element element;
+ switch (format) {
+ case ImageFormat.YUV_420_888:
+ element = Element.YUV(rs);
+ break;
+ case PixelFormat.RGBA_8888:
+ element = Element.RGBA_8888(rs);
+ break;
+ // TODO: map more formats here
+ default:
+ throw new UnsupportedOperationException("Unsupported format " + format);
+ }
+
+ return new AllocationInfo(element, size, usage);
+ }
+
+
+ /**
+ * Create a new {@link AllocationInfo} holding the specified format, {@link Size},
+ * with the default usage.
+ *
+ * <p>The default usage is always {@link Allocation#USAGE_SCRIPT}.</p>
+ *
+ * <p>The closest {@link Element} possible is created from the format.</p>
+ *
+ * @param size {@link Size}
+ * @param format An int format
+ *
+ * @return A new {@link AllocationInfo} holding the given arguments.
+ *
+ * @throws NullPointerException if size was {@code null}.
+ *
+ * @see ImageFormat
+ * @see PixelFormat
+ */
+ public static AllocationInfo newInstance(Size size, int format) {
+ return newInstance(size, format, Allocation.USAGE_SCRIPT);
+ }
+
+ /**
+ * Create a new {@link AllocationInfo} holding the specified {@link Element}, {@link Size},
+ * with the default usage.
+ *
+ * <p>The default usage is always {@link Allocation#USAGE_SCRIPT}.</p>
+ *
+ * @param element {@link Element}
+ * @param size {@link Size}
+ *
+ * @return A new {@link AllocationInfo} holding the given arguments.
+ *
+ * @throws NullPointerException if size was {@code null}.
+ * @throws NullPointerException if element was {@code null}.
+ */
+ public static AllocationInfo newInstance(Element element, Size size) {
+ return new AllocationInfo(element, size, Allocation.USAGE_SCRIPT);
+ }
+
+ /**
+ * Create a new {@link AllocationInfo} holding the specified {@link Element}, {@link Size},
+ * and {@link Allocation#USAGE_SCRIPT usage}.
+ *
+ * <p>The usage is always ORd with {@link Allocation#USAGE_SCRIPT}.</p>
+ *
+ * @param element {@link Element}
+ * @param size {@link Size}
+ * @param usage usage flags
+ *
+ * @return A new {@link AllocationInfo} holding the given arguments.
+ *
+ * @throws NullPointerException if size was {@code null}.
+ * @throws NullPointerException if element was {@code null}.
+ */
+ public static AllocationInfo newInstance(Element element, Size size, int usage) {
+ return new AllocationInfo(element, size, usage);
+ }
+
+ /**
+ * Create a new {@link AllocationInfo} by copying the existing data but appending
+ * the new usage flags to the old usage flags.
+ *
+ * @param usage usage flags
+ *
+ * @return A new {@link AllocationInfo} with new usage flags ORd to the old ones.
+ */
+ public AllocationInfo addExtraUsage(int usage) {
+ return new AllocationInfo(mElement, mSize, mUsage | usage);
+ }
+
+ /**
+ * Create a new {@link AllocationInfo} by copying the existing data but changing the format,
+ * and appending the new usage flags to the old usage flags.
+ *
+ * @param format Format
+ * @param usage usage flags
+ *
+ * @return A new {@link AllocationInfo} with new format/usage.
+ *
+ * @see ImageFormat
+ * @see PixelFormat
+ */
+ public AllocationInfo changeFormatAndUsage(int format, int usage) {
+ return newInstance(getSize(), format, usage);
+ }
+
+ /**
+ * Create a new {@link AllocationInfo} by copying the existing data but replacing the old
+ * usage with the new usage flags.
+ *
+ * @param usage usage flags
+ *
+ * @return A new {@link AllocationInfo} with new format/usage.
+ *
+ * @see ImageFormat
+ * @see PixelFormat
+ */
+ public AllocationInfo changeElementWithDefaultUsage(Element element) {
+ return newInstance(element, getSize());
+ }
+
+ /**
+ * Create a new {@link AllocationInfo} by copying the existing data but changing the format,
+ * and replacing the old usage flags with default usage flags.
+ *
+ * @param format Format
+ *
+ * @return A new {@link AllocationInfo} with new format/usage.
+ *
+ * @see ImageFormat
+ * @see PixelFormat
+ */
+ public AllocationInfo changeFormatWithDefaultUsage(int format) {
+ return newInstance(getSize(), format, Allocation.USAGE_SCRIPT);
+ }
+
+ private AllocationInfo(Element element, Size size, int usage) {
+ checkNotNull("element", element);
+ checkNotNull("size", size);
+
+ mElement = element;
+ mSize = size;
+ mUsage = usage;
+
+ Type.Builder typeBuilder = typeBuilder(element, size);
+
+ if (element.equals(Element.YUV(mRS))) {
+ typeBuilder.setYuvFormat(ImageFormat.YUV_420_888);
+ }
+
+ mType = typeBuilder.create();
+ }
+
+ /**
+ * Get the {@link Type type} for this info.
+ *
+ * <p>Note that this is the same type that would get used by the {@link Allocation}
+ * created with {@link #createAllocation()}.
+ *
+ * @return The type (never {@code null}).
+ */
+ public Type getType() {
+ return mType;
+ }
+
+ /**
+ * Get the usage.
+ *
+ * <p>The bit for {@link Allocation#USAGE_SCRIPT} will always be set to 1.</p>
+ *
+ * @return usage flags
+ */
+ public int getUsage() {
+ return mUsage;
+ }
+
+ /**
+ * Get the size.
+ *
+ * @return The size (never {@code null}).
+ */
+ public Size getSize() {
+ return mSize;
+ }
+
+ /**
+ * Get the {@link Element}.
+ *
+ * @return The element (never {@code null}).
+ */
+ public Element getElement() {
+ return mElement;
+ }
+
+ /**
+ * Convenience enum to represent commonly-used elements without needing a RenderScript object.
+ */
+ public enum ElementInfo {
+ YUV,
+ RGBA_8888,
+ U8_3,
+ U8_4;
+
+ private static final String TAG = "ElementInfo";
+
+ /**
+ * Create an {@link ElementInfo} by converting it from a {@link Element}.
+ *
+ * @param element The element for which you want to get an enum for.
+ *
+ * @return The element info is a corresponding one exists, or {@code null} otherwise.
+ */
+ public static ElementInfo fromElement(Element element) {
+ checkNotNull("element", element);
+
+ if (element.equals(Element.YUV(RenderScriptSingleton.getRS()))) {
+ return YUV;
+ } else if (element.equals(Element.RGBA_8888(RenderScriptSingleton.getRS()))) {
+ return RGBA_8888;
+ } else if (element.equals(Element.U8_3(RenderScriptSingleton.getRS()))) {
+ return U8_3;
+ } else if (element.equals(Element.U8_4(RenderScriptSingleton.getRS()))) {
+ return U8_4;
+ }
+ // TODO: add more comparisons here as necessary
+
+ Log.w(TAG, "Unknown element of data kind " + element.getDataKind());
+ return null;
+ }
+ }
+
+ /**
+ * Compare the current element against the suggested element (info).
+ *
+ * @param element The other element to compare against.
+ *
+ * @return true if the elements are equal, false otherwise.
+ */
+ public boolean isElementEqualTo(ElementInfo element) {
+ checkNotNull("element", element);
+
+ Element comparison;
+ switch (element) {
+ case YUV:
+ comparison = Element.YUV(mRS);
+ break;
+ case RGBA_8888:
+ comparison = Element.RGBA_8888(mRS);
+ break;
+ case U8_3:
+ comparison = Element.U8_3(mRS);
+ break;
+ case U8_4:
+ comparison = Element.U8_4(mRS);
+ break;
+ default:
+ // TODO: add more comparisons here as necessary
+ comparison = null;
+ }
+
+ return mElement.equals(comparison);
+ }
+
+ /**
+ * Human-readable representation of this info.
+ */
+ @Override
+ public String toString() {
+ return String.format("Size: %s, Element: %s, Usage: %x", mSize,
+ ElementInfo.fromElement(mElement), mUsage);
+ }
+
+ /**
+ * Compare against another object.
+ *
+ * <p>Comparisons against objects that are not instances of {@link AllocationInfo}
+ * always return {@code false}.</p>
+ *
+ * <p>Two {@link AllocationInfo infos} are considered equal only if their elements,
+ * sizes, and usage flags are also equal.</p>
+ *
+ * @param other Another info object
+ *
+ * @return true if this is equal to other
+ */
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof AllocationInfo) {
+ return equals((AllocationInfo)other);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Compare against another object.
+ *
+ * <p>Two {@link AllocationInfo infos} are considered equal only if their elements,
+ * sizes, and usage flags are also equal.</p>
+ *
+ * @param other Another info object
+ *
+ * @return true if this is equal to other
+ */
+ public boolean equals(AllocationInfo other) {
+ if (other == null) {
+ return false;
+ }
+
+ // Element, Size equality is already incorporated into Type equality
+ return mType.equals(other.mType) && mUsage == other.mUsage;
+ }
+
+ /**
+ * Create a new {@link Allocation} using the {@link #getType type} and {@link #getUsage usage}
+ * from this info object.
+ *
+ * <p>The allocation is always created from a {@link AllocationCache cache}. If possible,
+ * return it to the cache once done (although this is not necessary).</p>
+ *
+ * @return a new {@link Allocation}
+ */
+ public Allocation createAllocation() {
+ if (VERBOSE) Log.v(TAG, "createAllocation - for info =" + toString());
+ return RenderScriptSingleton.getCache().getOrCreateTyped(mType, mUsage);
+ }
+
+ /**
+ * Create a new {@link Allocation} using the {@link #getType type} and {@link #getUsage usage}
+ * from this info object; immediately wrap inside a new {@link BlockingInputAllocation}.
+ *
+ * <p>The allocation is always created from a {@link AllocationCache cache}. If possible,
+ * return it to the cache once done (although this is not necessary).</p>
+ *
+ * @return a new {@link Allocation}
+ *
+ * @throws IllegalArgumentException
+ * If the usage did not have one of {@code USAGE_IO_INPUT} or {@code USAGE_IO_OUTPUT}
+ */
+ public BlockingInputAllocation createBlockingInputAllocation() {
+ Allocation alloc = createAllocation();
+ return BlockingInputAllocation.wrap(alloc);
+ }
+
+ private static Type.Builder typeBuilder(Element element, Size size) {
+ Type.Builder builder = (new Type.Builder(RenderScriptSingleton.getRS(), element))
+ .setX(size.getWidth())
+ .setY(size.getHeight());
+
+ return builder;
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/rs/BlockingInputAllocation.java b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/BlockingInputAllocation.java
new file mode 100644
index 0000000..0305540
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/BlockingInputAllocation.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright 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 android.hardware.camera2.cts.rs;
+
+import static android.hardware.camera2.cts.helpers.Preconditions.*;
+
+import android.hardware.camera2.cts.helpers.UncheckedCloseable;
+import android.renderscript.Allocation;
+import android.util.Log;
+
+import com.android.ex.camera2.exceptions.TimeoutRuntimeException;
+
+/**
+ * An {@link Allocation} wrapper that can be used to block until new buffers are available.
+ *
+ * <p>Can only be used only with {@link Allocation#USAGE_IO_INPUT} usage Allocations.</p>
+ *
+ * <p>When used with a {@link android.hardware.camera2.CameraDevice CameraDevice} this
+ * must be used as an output surface.</p>
+ */
+class BlockingInputAllocation implements UncheckedCloseable {
+
+ private static final String TAG = BlockingInputAllocation.class.getSimpleName();
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+ private final Allocation mAllocation;
+ private final OnBufferAvailableListener mListener;
+ private boolean mClosed;
+
+ /**
+ * Wrap an existing Allocation with this {@link BlockingInputAllocation}.
+ *
+ * <p>Doing this will clear any existing associated buffer listeners and replace
+ * it with a new one.</p>
+ *
+ * @param allocation A non-{@code null} {@link Allocation allocation}
+ * @return a new {@link BlockingInputAllocation} instance
+ *
+ * @throws NullPointerException
+ * If {@code allocation} was {@code null}
+ * @throws IllegalArgumentException
+ * If {@code allocation}'s usage did not have one of USAGE_IO_INPUT or USAGE_IO_OUTPUT
+ * @throws IllegalStateException
+ * If this object has already been {@link #close closed}
+ */
+ public static BlockingInputAllocation wrap(Allocation allocation) {
+ checkNotNull("allocation", allocation);
+ checkBitFlags("usage", allocation.getUsage(), "USAGE_IO_INPUT", Allocation.USAGE_IO_INPUT);
+
+ return new BlockingInputAllocation(allocation);
+ }
+
+ /**
+ * Get the Allocation backing this {@link BlockingInputAllocation}.
+ *
+ * @return Allocation instance (non-{@code null}).
+ *
+ * @throws IllegalStateException If this object has already been {@link #close closed}
+ */
+ public Allocation getAllocation() {
+ checkNotClosed();
+
+ return mAllocation;
+ }
+
+ /**
+ * Waits for a buffer to become available, then immediately
+ * {@link Allocation#ioReceive receives} it.
+ *
+ * <p>After calling this, the next script used with this allocation will use the
+ * newer buffer.</p>
+ *
+ * @throws TimeoutRuntimeException If waiting for the buffer has timed out.
+ * @throws IllegalStateException If this object has already been {@link #close closed}
+ */
+ public synchronized void waitForBufferAndReceive() {
+ checkNotClosed();
+
+ if (VERBOSE) Log.v(TAG, "waitForBufferAndReceive - begin");
+
+ mListener.waitForBuffer();
+ mAllocation.ioReceive();
+
+ if (VERBOSE) Log.v(TAG, "waitForBufferAndReceive - Allocation#ioReceive");
+ }
+
+ /**
+ * If there are multiple pending buffers, {@link Allocation#ioReceive receive} the latest one.
+ *
+ * <p>Does not block if there are no currently pending buffers.</p>
+ *
+ * @return {@code true} only if any buffers were received.
+ *
+ * @throws IllegalStateException If this object has already been {@link #close closed}
+ */
+ public synchronized boolean receiveLatestAvailableBuffers() {
+ checkNotClosed();
+
+ int updatedBuffers = 0;
+ while (mListener.isBufferPending()) {
+ mListener.waitForBuffer();
+ mAllocation.ioReceive();
+ updatedBuffers++;
+ }
+
+ if (VERBOSE) Log.v(TAG, "receiveLatestAvailableBuffers - updated = " + updatedBuffers);
+
+ return updatedBuffers > 0;
+ }
+
+ /**
+ * Closes the object and detaches the listener from the {@link Allocation}.
+ *
+ * <p>This has a side effect of calling {@link #receiveLatestAvailableBuffers}
+ *
+ * <p>Does <i>not</i> destroy the underlying {@link Allocation}.</p>
+ */
+ @Override
+ public synchronized void close() {
+ if (mClosed) return;
+
+ receiveLatestAvailableBuffers();
+ mAllocation.setOnBufferAvailableListener(/*callback*/null);
+ mClosed = true;
+ }
+
+ protected void checkNotClosed() {
+ if (mClosed) {
+ throw new IllegalStateException(TAG + " has been closed");
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ close();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ private BlockingInputAllocation(Allocation allocation) {
+ mAllocation = allocation;
+
+ mListener = new OnBufferAvailableListener();
+ mAllocation.setOnBufferAvailableListener(mListener);
+ }
+
+ // TODO: refactor with the ImageReader Listener code to use a LinkedBlockingQueue
+ private static class OnBufferAvailableListener implements Allocation.OnBufferAvailableListener {
+ private int mPendingBuffers = 0;
+ private final Object mBufferSyncObject = new Object();
+ private static final int TIMEOUT_MS = 5000;
+
+ public boolean isBufferPending() {
+ synchronized (mBufferSyncObject) {
+ return (mPendingBuffers > 0);
+ }
+ }
+
+ /**
+ * Waits for a buffer. Caller must call ioReceive exactly once after calling this.
+ *
+ * @throws TimeoutRuntimeException If waiting for the buffer has timed out.
+ */
+ public void waitForBuffer() {
+ synchronized (mBufferSyncObject) {
+ while (mPendingBuffers == 0) {
+ try {
+ if (VERBOSE) Log.v(TAG, "waiting for next buffer");
+ mBufferSyncObject.wait(TIMEOUT_MS);
+ if (mPendingBuffers == 0) {
+ throw new TimeoutRuntimeException("wait for buffer image timed out");
+ }
+ } catch (InterruptedException ie) {
+ throw new AssertionError(ie);
+ }
+ }
+ mPendingBuffers--;
+ }
+ }
+
+ @Override
+ public void onBufferAvailable(Allocation a) {
+ if (VERBOSE) Log.v(TAG, "new buffer in allocation available");
+ synchronized (mBufferSyncObject) {
+ mPendingBuffers++;
+ mBufferSyncObject.notifyAll();
+ }
+ }
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/rs/RenderScriptSingleton.java b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/RenderScriptSingleton.java
new file mode 100644
index 0000000..8e4c8e9
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/RenderScriptSingleton.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 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 android.hardware.camera2.cts.rs;
+
+import android.content.Context;
+import android.renderscript.RenderScript;
+import android.util.Log;
+
+// TODO : Replace with dependency injection
+/**
+ * Singleton to hold {@link RenderScript} and {@link AllocationCache} objects.
+ *
+ * <p>The test method must call {@link #setContext} before attempting to retrieve
+ * the underlying objects.</p> *
+ */
+public class RenderScriptSingleton {
+
+ private static final String TAG = "RenderScriptSingleton";
+
+ private static Context sContext;
+ private static RenderScript sRS;
+ private static AllocationCache sCache;
+
+ /**
+ * Initialize the singletons from the given context; the
+ * {@link RenderScript} and {@link AllocationCache} objects are instantiated.
+ *
+ * @param context a non-{@code null} Context.
+ *
+ * @throws IllegalStateException If this was called repeatedly without {@link #clearContext}
+ */
+ public static synchronized void setContext(Context context) {
+ if (context.equals(sContext)) {
+ return;
+ } else if (sContext != null) {
+ Log.v(TAG,
+ "Trying to set new context " + context +
+ ", before clearing previous "+ sContext);
+ throw new IllegalStateException(
+ "Call #clearContext before trying to set a new context");
+ }
+
+ sRS = RenderScript.create(context);
+ sContext = context;
+ sCache = new AllocationCache(sRS);
+ }
+
+ /**
+ * Clean up the singletons from the given context; the
+ * {@link RenderScript} and {@link AllocationCache} objects are destroyed.
+ *
+ * <p>Safe to call multiple times; subsequent invocations have no effect.</p>
+ */
+ public static synchronized void clearContext() {
+ if (sContext != null) {
+ sCache.close();
+ sCache = null;
+
+ sRS.destroy();
+ sRS = null;
+ sContext = null;
+ }
+ }
+
+ /**
+ * Get the current {@link RenderScript} singleton.
+ *
+ * @return A non-{@code null} {@link RenderScript} object.
+ *
+ * @throws IllegalStateException if {@link #setContext} was not called prior to this
+ */
+ public static synchronized RenderScript getRS() {
+ if (sRS == null) {
+ throw new IllegalStateException("Call #setContext before using #get");
+ }
+
+ return sRS;
+ }
+ /**
+ * Get the current {@link AllocationCache} singleton.
+ *
+ * @return A non-{@code null} {@link AllocationCache} object.
+ *
+ * @throws IllegalStateException if {@link #setContext} was not called prior to this
+ */
+ public static synchronized AllocationCache getCache() {
+ if (sCache == null) {
+ throw new IllegalStateException("Call #setContext before using #getCache");
+ }
+
+ return sCache;
+ }
+
+ // Suppress default constructor for noninstantiability
+ private RenderScriptSingleton() { throw new AssertionError(); }
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/rs/Script.java b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/Script.java
new file mode 100644
index 0000000..92ff1cb
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/Script.java
@@ -0,0 +1,368 @@
+/*
+ * Copyright 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 android.hardware.camera2.cts.rs;
+
+import static android.hardware.camera2.cts.helpers.Preconditions.*;
+
+import android.hardware.camera2.cts.helpers.UncheckedCloseable;
+import android.renderscript.Allocation;
+import android.renderscript.RenderScript;
+import android.util.Log;
+
+import java.util.HashMap;
+
+/**
+ * Base class for all renderscript script abstractions.
+ *
+ * <p>Each script has exactly one input and one output allocation, and is able to execute
+ * one {@link android.renderscript.Script} script file.</p>
+ *
+ * <p>Each script owns it's input allocation, but not the output allocation.</p>
+ *
+ * <p>Subclasses of this class must implement exactly one of two constructors:
+ * <ul>
+ * <li>{@code ScriptSubclass(AllocationInfo inputInfo)}
+ * - if it expects 0 parameters
+ * <li>{@code ScriptSubclass(AllocationInfo inputInfo, ParameterMap<T> parameterMap))}
+ * - if it expects 1 or more parameters
+ * </ul>
+ *
+ * @param <T> A concrete subclass of {@link android.renderscript.Script}
+ */
+public abstract class Script<T extends android.renderscript.Script> implements UncheckedCloseable {
+
+ /**
+ * A type-safe heterogenous parameter map for script parameters.
+ *
+ * @param <ScriptT> A concrete subclass of {@link Script}.
+ */
+ public static class ParameterMap<ScriptT extends Script<?>> {
+ private final HashMap<Script.ScriptParameter<ScriptT, ?>, Object> mParameterMap =
+ new HashMap<Script.ScriptParameter<ScriptT, ?>, Object>();
+
+ /**
+ * Create a new parameter map with 0 parameters.</p>
+ */
+ public ParameterMap() {}
+
+ /**
+ * Get the value associated with the given parameter key.
+ *
+ * @param parameter A type-safe key corresponding to a parameter.
+ *
+ * @return The value, or {@code null} if none was set.
+ *
+ * @param <T> The type of the value
+ *
+ * @throws NullPointerException if parameter was {@code null}
+ */
+ @SuppressWarnings("unchecked")
+ public <T> T get(Script.ScriptParameter<ScriptT, T> parameter) {
+ checkNotNull("parameter", parameter);
+
+ return (T) mParameterMap.get(parameter);
+ }
+
+ /**
+ * Sets the value associated with the given parameter key.
+ *
+ * @param parameter A type-safe key corresponding to a parameter.
+ * @param value The value
+ *
+ * @param <T> The type of the value
+ *
+ * @throws NullPointerException if parameter was {@code null}
+ * @throws NullPointerException if value was {@code null}
+ */
+ public <T> void set(Script.ScriptParameter<ScriptT, T> parameter, T value) {
+ checkNotNull("parameter", parameter);
+ checkNotNull("value", value);
+
+ if (!parameter.getValueClass().isInstance(value)) {
+ throw new IllegalArgumentException(
+ "Runtime type mismatch between " + parameter + " and value " + value);
+ }
+
+ mParameterMap.put(parameter, value);
+ }
+
+ /**
+ * Whether or not at least one parameter has been {@link #set}.
+ *
+ * @return true if there is at least one element in the map
+ */
+ public boolean isEmpty() {
+ return mParameterMap.isEmpty();
+ }
+
+ /**
+ * Check if the parameter has been {@link #set} to a value.
+ *
+ * @param parameter A type-safe key corresponding to a parameter.
+ * @return true if there is a value corresponding to this parameter, false otherwise.
+ */
+ public boolean contains(Script.ScriptParameter<ScriptT, ?> parameter) {
+ checkNotNull("parameter", parameter);
+
+ return mParameterMap.containsKey(parameter);
+ }
+ }
+
+ /**
+ * A type-safe parameter key to be used with {@link ParameterMap}.
+ *
+ * @param <J> A concrete subclass of {@link Script}.
+ * @param <K> The type of the value that the parameter holds.
+ */
+ public static class ScriptParameter<J extends Script<?>, K> {
+ private final Class<J> mScriptClass;
+ private final Class<K> mValueClass;
+
+ ScriptParameter(Class<J> jClass, Class<K> kClass) {
+ checkNotNull("jClass", jClass);
+ checkNotNull("kClass", kClass);
+
+ mScriptClass = jClass;
+ mValueClass = kClass;
+ }
+
+ /**
+ * Get the runtime class associated with the value.
+ */
+ public Class<K> getValueClass() {
+ return mValueClass;
+ }
+
+ /**
+ * Compare with another object.
+ *
+ * <p>Two script parameters are considered equal only if their script class and value
+ * class are both equal.</p>
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof ScriptParameter) {
+ ScriptParameter<J, K> otherParam = (ScriptParameter<J,K>) other;
+
+ return mScriptClass.equals(otherParam.mScriptClass) &&
+ mValueClass.equals(otherParam.mValueClass);
+ }
+
+ return false;
+ }
+
+ /**
+ * Gets the hash code for this object.
+ */
+ @Override
+ public int hashCode() {
+ return mScriptClass.hashCode() ^ mValueClass.hashCode();
+ }
+ }
+
+ private static final String TAG = "Script";
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+ protected final AllocationCache mCache = RenderScriptSingleton.getCache();
+ protected final RenderScript mRS = RenderScriptSingleton.getRS();
+
+ protected final AllocationInfo mInputInfo;
+ protected final AllocationInfo mOutputInfo;
+
+ protected Allocation mOutputAllocation;
+ protected Allocation mInputAllocation;
+
+ protected final T mScript;
+ private boolean mClosed = false;
+
+ /**
+ * Gets the {@link AllocationInfo info} associated with this script's input.
+ *
+ * @return A non-{@code null} {@link AllocationInfo} object.
+ *
+ * @throws IllegalStateException If the script has already been {@link #close closed}.
+ */
+ public AllocationInfo getInputInfo() {
+ checkNotClosed();
+
+ return mInputInfo;
+ }
+ /**
+ * Gets the {@link AllocationInfo info} associated with this script's output.
+ *
+ * @return A non-{@code null} {@link AllocationInfo} object.
+ *
+ * @throws IllegalStateException If the script has already been {@link #close closed}.
+ */
+ public AllocationInfo getOutputInfo() {
+ checkNotClosed();
+
+ return mOutputInfo;
+ }
+
+ /**
+ * Set the input.
+ *
+ * <p>Must be called before executing any scripts.</p>
+ *
+ * @throws IllegalStateException If the script has already been {@link #close closed}.
+ */
+ void setInput(Allocation allocation) {
+ checkNotClosed();
+ checkNotNull("allocation", allocation);
+ checkEquals("allocation info", AllocationInfo.newInstance(allocation),
+ "input info", mInputInfo);
+
+ // Scripts own the input, so return old input to cache if the input changes
+ if (mInputAllocation != allocation) {
+ mCache.returnToCacheIfNotNull(mInputAllocation);
+ }
+
+ mInputAllocation = allocation;
+ updateScriptInput();
+ }
+
+ protected abstract void updateScriptInput();
+
+ /**
+ * Set the output.
+ *
+ * <p>Must be called before executing any scripts.</p>
+ *
+ * @throws IllegalStateException If the script has already been {@link #close closed}.
+ */
+ void setOutput(Allocation allocation) {
+ checkNotClosed();
+ checkNotNull("allocation", allocation);
+ checkEquals("allocation info", AllocationInfo.newInstance(allocation),
+ "output info", mOutputInfo);
+
+ // Scripts do not own the output, simply set a reference to the new one.
+ mOutputAllocation = allocation;
+ }
+
+ protected Script(AllocationInfo inputInfo, AllocationInfo outputInfo, T rsScript) {
+ checkNotNull("inputInfo", inputInfo);
+ checkNotNull("outputInfo", outputInfo);
+ checkNotNull("rsScript", rsScript);
+
+ mInputInfo = inputInfo;
+ mOutputInfo = outputInfo;
+ mScript = rsScript;
+
+ if (VERBOSE) {
+ Log.v(TAG, String.format("%s - inputInfo = %s, outputInfo = %s, rsScript = %s",
+ getName(), inputInfo, outputInfo, rsScript));
+ }
+ }
+
+ /**
+ * Get the {@link Allocation} associated with this script's input.</p>
+ *
+ * @return The input {@link Allocation}, which is never {@code null}.
+ *
+ * @throws IllegalStateException If the script has already been {@link #close closed}.
+ */
+ public Allocation getInput() {
+ checkNotClosed();
+
+ return mInputAllocation;
+ }
+ /**
+ * Get the {@link Allocation} associated with this script's output.</p>
+ *
+ * @return The output {@link Allocation}, which is never {@code null}.
+ *
+ * @throws IllegalStateException If the script has already been {@link #close closed}.
+ */
+ public Allocation getOutput() {
+ checkNotClosed();
+
+ return mOutputAllocation;
+ }
+
+ /**
+ * Execute the script's kernel against the input/output {@link Allocation allocations}.
+ *
+ * <p>Once this is complete, the output will have the new data available (for either
+ * the next script, or to read out with a copy).</p>
+ *
+ * @throws IllegalStateException If the script has already been {@link #close closed}.
+ */
+ public void execute() {
+ checkNotClosed();
+
+ if (mInputAllocation == null || mOutputAllocation == null) {
+ throw new IllegalStateException("Both inputs and outputs must have been set");
+ }
+
+ executeUnchecked();
+ }
+
+ /**
+ * Get the name of this script.
+ *
+ * <p>The name is the short hand name of the concrete class backing this script.</p>
+ *
+ * <p>This method works even if the script has already been {@link #close closed}.</p>
+ *
+ * @return A string representing the script name.
+ */
+ public String getName() {
+ return getClass().getSimpleName();
+ }
+
+ protected abstract void executeUnchecked();
+
+ protected void checkNotClosed() {
+ if (mClosed) {
+ throw new IllegalStateException("Script has been closed");
+ }
+ }
+
+ /**
+ * Destroy the underlying script object and return the input allocation back to the
+ * {@link AllocationCache cache}.
+ *
+ * <p>This method has no effect if called more than once.</p>
+ */
+ @Override
+ public void close() {
+ if (mClosed) return;
+
+ // Scripts own the input allocation. They do NOT own outputs.
+ mCache.returnToCacheIfNotNull(mInputAllocation);
+
+ mScript.destroy();
+
+ mClosed = true;
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ close();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ protected static RenderScript getRS() {
+ return RenderScriptSingleton.getRS();
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/rs/ScriptGraph.java b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/ScriptGraph.java
new file mode 100644
index 0000000..56d8703
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/ScriptGraph.java
@@ -0,0 +1,656 @@
+/*
+ * Copyright 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 android.hardware.camera2.cts.rs;
+
+import static android.hardware.camera2.cts.helpers.Preconditions.*;
+import static junit.framework.Assert.*;
+
+import android.graphics.ImageFormat;
+import android.graphics.PixelFormat;
+import android.util.Size;
+import android.hardware.camera2.cts.helpers.MaybeNull;
+import android.hardware.camera2.cts.helpers.UncheckedCloseable;
+import android.hardware.camera2.cts.rs.Script.ParameterMap;
+import android.renderscript.Allocation;
+import android.util.Log;
+import android.view.Surface;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.List;
+import com.android.ex.camera2.exceptions.TimeoutRuntimeException;
+
+/**
+ * An abstraction to simplify chaining together the execution of multiple RenderScript
+ * {@link android.renderscript.Script scripts} and managing their {@link Allocation allocations}.
+ *
+ * <p>Create a new script graph by using {@link #create}, configure the input with
+ * {@link Builder#configureInput}, then configure one or more scripts with
+ * {@link Builder#configureScript} or {@link Builder#chainScript}. Finally, freeze the graph
+ * with {@link Builder#buildGraph}.</p>
+ *
+ * <p>Once a script graph has been built, all underlying scripts and allocations are instantiated.
+ * Each script may be executed with {@link #execute}. Scripts are executed in the order that they
+ * were configured, with each previous script's output used as the input for the next script.
+ * </p>
+ *
+ * <p>In case the input {@link Allocation} is actually backed by a {@link Surface}, convenience
+ * methods ({@link #advanceInputWaiting} and {@link #advanceInputAndDrop} are provided to
+ * automatically update the {@link Allocation allocation} with the latest buffer available.</p>
+ *
+ * <p>All resources are managed by the {@link ScriptGraph} and {@link #close closing} the graph
+ * will release all underlying resources. See {@link #close} for more details.</p>
+ */
+public class ScriptGraph implements UncheckedCloseable {
+
+ private static final String TAG = "ScriptGraph";
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+ private static final int INPUT_SCRIPT_LOCATION = 0;
+ private final int OUTPUT_SCRIPT_LOCATION; // calculated in constructor
+
+ private final AllocationCache mCache = RenderScriptSingleton.getCache();
+
+ private final Size mSize;
+ private final int mFormat;
+ private final int mUsage;
+ private final List<Script<?>> mScripts;
+
+ private final BlockingInputAllocation mInputBlocker;
+ private final Allocation mOutputAllocation;
+ private boolean mClosed = false;
+
+ /**
+ * Create a new {@link Builder} that will be used to configure the graph's inputs
+ * and scripts (and parameters).
+ *
+ * <p>Once a graph has been fully built, the configuration is immutable.</p>
+ *
+ * @return a {@link Builder} that will be used to configure the graph settings
+ */
+ public static Builder create() {
+ return new Builder();
+ }
+
+ /**
+ * Wait until another buffer is produced into the input {@link Surface}, then
+ * update the backing input {@link Allocation} with the latest buffer with
+ * {@link Allocation#ioReceive ioReceive}.
+ *
+ * @throws IllegalArgumentException
+ * if the graph wasn't configured with
+ * {@link Builder#configureInputWithSurface configureInputWithSurface}
+ * @throws TimeoutRuntimeException
+ * if waiting for the buffer times out
+ */
+ public void advanceInputWaiting() {
+ checkNotClosed();
+ if (!isInputFromSurface()) {
+ throw new IllegalArgumentException("Graph was not configured with USAGE_IO_INPUT");
+ }
+
+ mInputBlocker.waitForBufferAndReceive();
+ }
+
+ /**
+ * Update the backing input {@link Allocation} with the latest buffer with
+ * {@link Allocation#ioReceive ioReceive} repeatedly until no more buffers are pending.
+ *
+ * <p>Does not wait for new buffers to become available if none are currently available
+ * (i.e. {@code false} is returned immediately).</p>
+ *
+ * @return true if any buffers were pending
+ *
+ * @throws IllegalArgumentException
+ * if the graph wasn't configured with
+ * {@link Builder#configureInputWithSurface configureInputWithSurface}
+ * @throws TimeoutRuntimeException
+ * if waiting for the buffer times out
+ */
+ public boolean advanceInputAndDrop() {
+ checkNotClosed();
+ if (!isInputFromSurface()) {
+ throw new IllegalArgumentException("Graph was not configured with USAGE_IO_INPUT");
+ }
+
+ return mInputBlocker.receiveLatestAvailableBuffers();
+ }
+
+ /**
+ * Execute each script in the graph, with each next script's input using the
+ * previous script's output.
+ *
+ * <p>Scripts are executed in the same order that they were configured by the {@link Builder}.
+ * </p>
+ *
+ * @throws IllegalStateException if the graph was already {@link #close closed}
+ */
+ public void execute() {
+ checkNotClosed();
+
+ // TODO: Can we use android.renderscript.ScriptGroup here to make it faster?
+
+ int i = 0;
+ for (Script<?> script : mScripts) {
+ script.execute();
+ i++;
+ }
+
+ if (VERBOSE) Log.v(TAG, "execute - invoked " + i + " scripts");
+ }
+
+ /**
+ * Copies the data from the last script's output {@link Allocation} into a byte array.
+ *
+ * <p>The output allocation must be of an 8 bit integer
+ * {@link android.renderscript.Element Element} type.</p>
+ *
+ * @return A byte[] copy.
+ *
+ * @throws IllegalStateException if the graph was already {@link #close closed}
+ */
+ public byte[] getOutputData() {
+ checkNotClosed();
+
+ Allocation outputAllocation = getOutputAllocation();
+
+ byte[] destination = new byte[outputAllocation.getBytesSize()];
+ outputAllocation.copyTo(destination);
+
+ return destination;
+ }
+
+ /**
+ * Copies the data from the first script's input {@link Allocation} into a byte array.
+ *
+ * <p>The input allocation must be of an 8 bit integer
+ * {@link android.renderscript.Element Element} type.</p>
+ *
+ * @return A byte[] copy.
+ *
+ * @throws IllegalStateException if the graph was already {@link #close closed}
+ */
+ public byte[] getInputData() {
+ checkNotClosed();
+
+ Allocation inputAllocation = getInputAllocation();
+
+ byte[] destination = new byte[inputAllocation.getBytesSize()];
+ inputAllocation.copyTo(destination);
+
+ return destination;
+ }
+
+ /**
+ * Builds a {@link ScriptGraph} by configuring input size/format/usage,
+ * the script classes in the graph, and the parameters passed to the scripts.
+ *
+ * @see ScriptGraph#create
+ */
+ public static class Builder {
+
+ private Size mSize;
+ private int mFormat;
+ private int mUsage;
+
+ private final List<ScriptBuilder<? extends Script<?>>> mChainedScriptBuilders =
+ new ArrayList<ScriptBuilder<? extends Script<?>>>();
+
+ /**
+ * Configure the {@link Allocation} that will be used as the input to the first
+ * script, using the default usage.
+ *
+ * <p>Short hand for calling {@link #configureInput(int, int, int, int)} with a
+ * {@code 0} usage.</p>
+ *
+ * @param width Width in pixels
+ * @param height Height in pixels
+ * @param format Format from {@link ImageFormat} or {@link PixelFormat}
+ *
+ * @return The current builder ({@code this}). Use for chaining method calls.
+ */
+ public Builder configureInput(int width, int height, int format) {
+ return configureInput(new Size(width, height), format, /*usage*/0);
+ }
+
+ /**
+ * Configure the {@link Allocation} that will be used as the input to the first
+ * script.
+ *
+ * <p>The {@code usage} is always ORd together with {@link Allocation#USAGE_SCRIPT}.</p>
+ *
+ * @param width Width in pixels
+ * @param height Height in pixels
+ * @param format Format from {@link ImageFormat} or {@link PixelFormat}
+ * @param usage Usage flags such as {@link Allocation#USAGE_IO_INPUT}
+ *
+ * @return The current builder ({@code this}). Use for chaining method calls.
+ */
+ public Builder configureInput(int width, int height, int format, int usage) {
+ return configureInput(new Size(width, height), format, usage);
+ }
+
+ /**
+ * Configure the {@link Allocation} that will be used as the input to the first
+ * script, using the default usage.
+ *
+ * <p>Short hand for calling {@link #configureInput(Size, int, int)} with a
+ * {@code 0} usage.</p>
+ *
+ * @param size Size (width, height)
+ * @param format Format from {@link ImageFormat} or {@link PixelFormat}
+ *
+ * @return The current builder ({@code this}). Use for chaining method calls.
+ *
+ * @throws NullPointerException if size was {@code null}
+ */
+ public Builder configureInput(Size size, int format) {
+ return configureInput(size, format, /*usage*/0);
+ }
+
+ /**
+ * Configure the {@link Allocation} that will use a {@link Surface} to produce input into
+ * the first script.
+ *
+ * <p>Short hand for calling {@link #configureInput(Size, int, int)} with the
+ * {@link Allocation#USAGE_IO_INPUT} usage.</p>
+ *
+ * <p>The {@code usage} is always ORd together with {@link Allocation#USAGE_SCRIPT}.</p>
+ *
+ * @param size Size (width, height)
+ * @param format Format from {@link ImageFormat} or {@link PixelFormat}
+ *
+ * @return The current builder ({@code this}). Use for chaining method calls.
+ *
+ * @throws NullPointerException if size was {@code null}
+ */
+ public Builder configureInputWithSurface(Size size, int format) {
+ return configureInput(size, format, Allocation.USAGE_IO_INPUT);
+ }
+
+ /**
+ * Configure the {@link Allocation} that will be used as the input to the first
+ * script.
+ *
+ * <p>The {@code usage} is always ORd together with {@link Allocation#USAGE_SCRIPT}.</p>
+ *
+ * @param size Size (width, height)
+ * @param format Format from {@link ImageFormat} or {@link PixelFormat}
+ * @param usage Usage flags such as {@link Allocation#USAGE_IO_INPUT}
+ *
+ * @return The current builder ({@code this}). Use for chaining method calls.
+ *
+ * @throws NullPointerException if size was {@code null}
+ */
+ public Builder configureInput(Size size, int format, int usage) {
+ checkNotNull("size", size);
+
+ mSize = size;
+ mFormat = format;
+ mUsage = usage | Allocation.USAGE_SCRIPT;
+
+ return this;
+ }
+
+ /**
+ * Build a {@link Script} by setting parameters it might require for execution.
+ *
+ * <p>Refer to the documentation for {@code T} to see if there are any
+ * {@link Script.ScriptParameter parameters} in it.
+ * </p>
+ *
+ * @param <T> Concrete type subclassing the {@link Script} class.
+ */
+ public class ScriptBuilder<T extends Script<?>> {
+
+ private final Class<T> mScriptClass;
+
+ private ScriptBuilder(Class<T> scriptClass) {
+ mScriptClass = scriptClass;
+ }
+
+ private final ParameterMap<T> mParameterMap = new ParameterMap<T>();
+
+ /**
+ * Set a script parameter to the specified value.
+ *
+ * @param parameter The {@link Script.ScriptParameter parameter key} in {@code T}
+ * @param value A value of type {@code K} that the script expects.
+ * @param <K> The type of the parameter {@code value}.
+ *
+ * @return The current builder ({@code this}). Use to chain method calls.
+ *
+ * @throws NullPointerException if parameter was {@code null}
+ * @throws NullPointerException if value was {@code null}
+ * @throws IllegalStateException if the parameter was already {@link #set}
+ */
+ public <K> ScriptBuilder<T> set(Script.ScriptParameter<T, K> parameter, K value) {
+ checkNotNull("parameter", parameter);
+ checkNotNull("value", value);
+ checkState("Parameter has already been set", !mParameterMap.contains(parameter));
+
+ mParameterMap.set(parameter, value);
+
+ return this;
+ }
+
+ ParameterMap<T> getParameterMap() {
+ return mParameterMap;
+ }
+
+ Class<T> getScriptClass() {
+ return mScriptClass;
+ }
+
+ /**
+ * Build the script and freeze the parameter list to what was {@link #set}.
+ *
+ * @return
+ * The {@link ScriptGraph#Builder} that was used to configure
+ * {@link this} script.</p>
+ */
+ public Builder buildScript() {
+ mChainedScriptBuilders.add(this);
+
+ return Builder.this;
+ }
+ }
+
+ /**
+ * Configure the script with no parameters.
+ *
+ * <p>Short hand for invoking {@link #configureScript} immediately followed by
+ * {@link ScriptBuilder#buildScript()}.
+ *
+ * @param scriptClass A concrete class that subclasses {@link Script}
+ * @return The current builder ({@code this}). Use to chain method calls.
+ *
+ * @throws NullPointerException if {@code scriptClass} was {@code null}
+ */
+ public <T extends Script<?>> Builder chainScript(Class<T> scriptClass) {
+ checkNotNull("scriptClass", scriptClass);
+
+ return (new ScriptBuilder<T>(scriptClass)).buildScript();
+ }
+
+ /**
+ * Configure the script with parameters.
+ *
+ * <p>Only useful when the {@code scriptClass} has one or more
+ * {@link Script.ScriptParameter script parameters} defined.</p>
+ *
+ * @param scriptClass A concrete class that subclasses {@link Script}
+ * @return A script configuration {@link ScriptBuilder builder}. Use to chain method calls.
+ *
+ * @throws NullPointerException if {@code scriptClass} was {@code null}
+ */
+ public <T extends Script<?>> ScriptBuilder<T> configureScript(Class<T> scriptClass) {
+ checkNotNull("scriptClass", scriptClass);
+
+ return new ScriptBuilder<T>(scriptClass);
+ }
+
+ /**
+ * Finish configuring the graph and freeze the settings, instantiating all
+ * the {@link Script scripts} and {@link Allocation allocations}.
+ *
+ * @return A constructed {@link ScriptGraph}.
+ */
+ public ScriptGraph buildGraph() {
+ return new ScriptGraph(this);
+ }
+
+ private Builder() {}
+ }
+
+ private ScriptGraph(Builder builder) {
+ mSize = builder.mSize;
+ mFormat = builder.mFormat;
+ mUsage = builder.mUsage;
+ List<Builder.ScriptBuilder<? extends Script<?>>> chainedScriptBuilders =
+ builder.mChainedScriptBuilders;
+ mScripts = new ArrayList<Script<?>>(/*capacity*/chainedScriptBuilders.size());
+ OUTPUT_SCRIPT_LOCATION = chainedScriptBuilders.size() - 1;
+
+ if (mSize == null) {
+ throw new IllegalArgumentException("Inputs were not configured");
+ }
+
+ if (chainedScriptBuilders.isEmpty()) {
+ throw new IllegalArgumentException("At least one script should be chained");
+ }
+
+ /*
+ * The first input is special since it could be USAGE_IO_INPUT.
+ */
+ AllocationInfo inputInfo = AllocationInfo.newInstance(mSize, mFormat, mUsage);
+ Allocation inputAllocation;
+
+ // Create an Allocation with a Surface if the input to the graph requires it
+ if (isInputFromSurface()) {
+ mInputBlocker = inputInfo.createBlockingInputAllocation();
+ inputAllocation = mInputBlocker.getAllocation();
+ } else {
+ mInputBlocker = null;
+ inputAllocation = inputInfo.createAllocation();
+ }
+
+ if (VERBOSE) Log.v(TAG, "ScriptGraph() - Instantiating all script classes");
+
+ // Create all scripts.
+ for (Builder.ScriptBuilder<? extends Script<?>> scriptBuilder: chainedScriptBuilders) {
+
+ @SuppressWarnings("unchecked")
+ Class<Script<?>> scriptClass = (Class<Script<?>>) scriptBuilder.getScriptClass();
+
+ @SuppressWarnings("unchecked")
+ ParameterMap<Script<?>> parameters = (ParameterMap<Script<?>>)
+ scriptBuilder.getParameterMap();
+
+ Script<?> script = instantiateScript(scriptClass, inputInfo, parameters);
+ mScripts.add(script);
+
+ // The next script's input info is the current script's output info
+ inputInfo = script.getOutputInfo();
+ }
+
+ if (VERBOSE) Log.v(TAG, "ScriptGraph() - Creating all inputs");
+
+ // Create and wire up all inputs.
+ int i = 0;
+ Script<?> inputScript = mScripts.get(INPUT_SCRIPT_LOCATION);
+ do {
+ if (VERBOSE) {
+ Log.v(TAG, "ScriptGraph() - Setting input for script " + inputScript.getName());
+ }
+
+ inputScript.setInput(inputAllocation);
+
+ i++;
+
+ if (i >= mScripts.size()) {
+ break;
+ }
+
+ // Use the graph input for the first loop iteration
+ inputScript = mScripts.get(i);
+ inputInfo = inputScript.getInputInfo();
+ inputAllocation = inputInfo.createAllocation();
+ } while (true);
+
+ if (VERBOSE) Log.v(TAG, "ScriptGraph() - Creating all outputs");
+
+ // Create and wire up all outputs.
+ Allocation lastOutput = null;
+ for (i = 0; i < mScripts.size(); ++i) {
+ Script<?> script = mScripts.get(i);
+ Script<?> nextScript = (i + 1 < mScripts.size()) ? mScripts.get(i + 1) : null;
+
+ // Each script's output uses the next script's input.
+ // -- Since the last script has no next script, we own its output allocation.
+ lastOutput = (nextScript != null) ? nextScript.getInput()
+ : script.getOutputInfo().createAllocation();
+
+ if (VERBOSE) {
+ Log.v(TAG, "ScriptGraph() - Setting output for script " + script.getName());
+ }
+
+ script.setOutput(lastOutput);
+ }
+ mOutputAllocation = checkNotNull("lastOutput", lastOutput);
+
+ // Done. Safe to execute the graph now.
+
+ if (VERBOSE) Log.v(TAG, "ScriptGraph() - Graph has been built");
+ }
+
+ /**
+ * Construct the script by instantiating it via reflection.
+ *
+ * <p>The {@link Script scriptClass} should have a {@code Script(AllocationInfo inputInfo)}
+ * constructor if it expects an empty parameter map.</p>
+ *
+ * <p>If it expects a non-empty parameter map, it should have a
+ * {@code Script(AllocationInfo inputInfo, ParameterMap<T> parameterMap)} constructor.</p>
+ */
+ private static <T extends Script<?>> T instantiateScript(
+ Class<T> scriptClass, AllocationInfo inputInfo, ParameterMap<T> parameterMap) {
+
+ Constructor<T> ctor;
+ try {
+ // TODO: Would be better if we looked at the script class to see if it expects params
+ if (parameterMap.isEmpty()) {
+ // Script(AllocationInfo inputInfo)
+ ctor = scriptClass.getConstructor(AllocationInfo.class);
+ } else {
+ // Script(AllocationInfo inputInfo, ParameterMap<T> parameterMap)
+ ctor = scriptClass.getConstructor(AllocationInfo.class, ParameterMap.class);
+ }
+ } catch (NoSuchMethodException e) {
+ throw new UnsupportedOperationException(
+ "Script class " + scriptClass + " must have a matching constructor", e);
+ }
+
+ try {
+ if (parameterMap.isEmpty()) {
+ return ctor.newInstance(inputInfo);
+ } else {
+ return ctor.newInstance(inputInfo, parameterMap);
+ }
+ } catch (InstantiationException e) {
+ throw new UnsupportedOperationException(e);
+ } catch (IllegalAccessException e) {
+ throw new UnsupportedOperationException(e);
+ } catch (IllegalArgumentException e) {
+ throw new UnsupportedOperationException(e);
+ } catch (InvocationTargetException e) {
+ throw new UnsupportedOperationException(e);
+ }
+ }
+
+ private boolean isInputFromSurface() {
+ return (mUsage & Allocation.USAGE_IO_INPUT) != 0;
+ }
+
+ /**
+ * Get the input {@link Allocation} that is used by the first script as the input.
+ *
+ * @return An {@link Allocation} (never {@code null}).
+ *
+ * @throws IllegalStateException if the graph was already {@link #close closed}
+ */
+ public Allocation getInputAllocation() {
+ checkNotClosed();
+
+ return mScripts.get(INPUT_SCRIPT_LOCATION).getInput();
+ }
+
+ /**
+ * Get the output {@link Allocation} that is used by the last script as the output.
+ *
+ * @return An {@link Allocation} (never {@code null}).
+ *
+ * @throws IllegalStateException if the graph was already {@link #close closed}
+ */
+ public Allocation getOutputAllocation() {
+ checkNotClosed();
+ Allocation output = mScripts.get(OUTPUT_SCRIPT_LOCATION).getOutput();
+
+ assertEquals("Graph's output should match last script's output", mOutputAllocation, output);
+
+ return output;
+ }
+
+ /**
+ * Get the {@link Surface} that can be used produce buffers into the
+ * {@link #getInputAllocation input allocation}.
+ *
+ * @throws IllegalStateException
+ * if input wasn't configured with {@link Allocation#USAGE_IO_INPUT} {@code usage}.
+ * @throws IllegalStateException
+ * if the graph was already {@link #close closed}
+ *
+ * @return A {@link Surface} (never {@code null}).
+ */
+ public Surface getInputSurface() {
+ checkNotClosed();
+ checkState("This graph was not configured with IO_USAGE_INPUT", isInputFromSurface());
+
+ return getInputAllocation().getSurface();
+ }
+
+ private void checkNotClosed() {
+ checkState("ScriptGraph has been closed", !mClosed);
+ }
+
+ /**
+ * Releases all underlying resources associated with this {@link ScriptGraph}.
+ *
+ * <p>In particular, all underlying {@link Script scripts} and all
+ * {@link Allocation allocations} are also closed.</p>
+ *
+ * <p>All further calls to any other public methods (other than {@link #close}) will throw
+ * an {@link IllegalStateException}.</p>
+ *
+ * <p>This method is idempotent; calling it more than once will
+ * have no further effect.</p>
+ */
+ @Override
+ public synchronized void close() {
+ if (mClosed) return;
+
+ for (Script<?> script : mScripts) {
+ script.close();
+ }
+ mScripts.clear();
+
+ MaybeNull.close(mInputBlocker);
+ mCache.returnToCache(mOutputAllocation);
+
+ mClosed = true;
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ close();
+ } finally {
+ super.finalize();
+ }
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/rs/ScriptYuvCrop.java b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/ScriptYuvCrop.java
new file mode 100644
index 0000000..ae55cc9
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/ScriptYuvCrop.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 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 android.hardware.camera2.cts.rs;
+
+import static android.hardware.camera2.cts.helpers.Preconditions.*;
+
+import android.graphics.ImageFormat;
+import android.graphics.RectF;
+import android.util.Size;
+import android.hardware.camera2.cts.ScriptC_crop_yuvf_420_to_yuvx_444;
+import android.hardware.camera2.cts.rs.AllocationInfo.ElementInfo;
+import android.renderscript.Element;
+import android.util.Log;
+
+/**
+ * Crop {@link ImageFormat#YUV_420_888 flexible-YUV} {@link Allocation allocations} into
+ * a {@link ElementInfo#U8_3 U8_3} {@link Allocation allocation}.
+ *
+ * <p>Users of this script must configure it with the
+ * {@link ScriptYuvCrop#CROP_WINDOW crop window} parameter.</p>
+ *
+ */
+public class ScriptYuvCrop extends Script<ScriptC_crop_yuvf_420_to_yuvx_444> {
+ private static final String TAG = "ScriptYuvCrop";
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+ /**
+ * A rectangle holding the top,left,right,bottom normalized coordinates each within [0,1].
+ *
+ * <p>The output will be a cropped copy of the input to only this crop window.</p>
+ */
+ // TODO: Change this to use Patch
+ public static final Script.ScriptParameter<ScriptYuvCrop, RectF> CROP_WINDOW =
+ new Script.ScriptParameter<ScriptYuvCrop, RectF>(ScriptYuvCrop.class,
+ RectF.class);
+
+ private final RectF mCropWindow;
+
+ private static AllocationInfo createOutputInfo(AllocationInfo inputInfo,
+ ParameterMap<ScriptYuvCrop> parameters) {
+ checkNotNull("inputInfo", inputInfo);
+ checkNotNull("parameters", parameters);
+
+ if (!parameters.contains(CROP_WINDOW)) {
+ throw new IllegalArgumentException("Script's CROP_WINDOW was not set");
+ }
+
+ Size inputSize = inputInfo.getSize();
+ RectF crop = parameters.get(CROP_WINDOW);
+ Size outputSize = new Size(
+ (int)(crop.width() * inputSize.getWidth()),
+ (int)(crop.height() * inputSize.getHeight()));
+
+ if (VERBOSE) Log.v(TAG, String.format("createOutputInfo - outputSize is %s", outputSize));
+
+ /**
+ * Input YUV W x H
+ * Output U8_3 CropW x CropH
+ */
+ return AllocationInfo.newInstance(Element.U8_3(getRS()), outputSize);
+ }
+
+ public ScriptYuvCrop(AllocationInfo inputInfo,
+ ParameterMap<ScriptYuvCrop> parameterMap) {
+ super(inputInfo,
+ createOutputInfo(inputInfo, parameterMap),
+ new ScriptC_crop_yuvf_420_to_yuvx_444(getRS()));
+
+ // YUV_420_888 is the only supported format here
+ if (!inputInfo.isElementEqualTo(ElementInfo.YUV)) {
+ throw new UnsupportedOperationException("Unsupported element "
+ + inputInfo.getElement());
+ }
+
+ mCropWindow = parameterMap.get(CROP_WINDOW);
+ }
+
+ @Override
+ protected void executeUnchecked() {
+ mScript.forEach_crop(mOutputAllocation);
+
+ if (VERBOSE) { Log.v(TAG, "executeUnchecked - forEach_crop done"); }
+ }
+
+ @Override
+ protected void updateScriptInput() {
+ int x = (int)(mCropWindow.left * mInputInfo.getSize().getWidth());
+ int y = (int)(mCropWindow.top * mInputInfo.getSize().getHeight());
+
+ mScript.set_src_x(x);
+ mScript.set_src_y(y);
+
+ mScript.set_mInput(mInputAllocation);
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/rs/ScriptYuvMeans1d.java b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/ScriptYuvMeans1d.java
new file mode 100644
index 0000000..fb76196
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/ScriptYuvMeans1d.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 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 android.hardware.camera2.cts.rs;
+
+import static android.hardware.camera2.cts.helpers.Preconditions.*;
+
+import android.util.Size;
+import android.hardware.camera2.cts.ScriptC_means_yuvx_444_1d_to_single;
+import android.hardware.camera2.cts.rs.AllocationInfo.ElementInfo;
+import android.renderscript.Element;
+
+/**
+ * Average a {@code Hx1} {@link ElementInfo#U8_3 U8_3} {@link Allocation allocation} into a 1x1
+ * {@code U8x3} {@link Allocation allocation}.
+ *
+ * <p>Users of this script should chain {@link ScriptYuvMeans1d} immediately before this
+ * to average the input down to a 1x1 element.</p>
+ */
+public class ScriptYuvMeans1d extends Script<ScriptC_means_yuvx_444_1d_to_single>{
+ private static final String TAG = "ScriptYuvMeans1d";
+
+ private static final Size UNIT_SQUARE = new Size(/*width*/1, /*height*/1);
+
+ private static AllocationInfo createOutputInfo(AllocationInfo inputInfo) {
+ checkNotNull("inputInfo", inputInfo);
+ // (input) Hx1 -> 1x1
+ return AllocationInfo.newInstance(Element.U8_3(getRS()), UNIT_SQUARE);
+ }
+
+ public ScriptYuvMeans1d(AllocationInfo inputInfo) {
+ super(inputInfo,
+ createOutputInfo(inputInfo),
+ new ScriptC_means_yuvx_444_1d_to_single(getRS()));
+
+ // U8x3 is the only supported element here
+ if (!inputInfo.isElementEqualTo(ElementInfo.U8_3)) {
+ throw new UnsupportedOperationException("Unsupported element "
+ + inputInfo.getElement());
+ }
+ }
+
+ @Override
+ protected void executeUnchecked() {
+ mScript.forEach_means_yuvx_444(mOutputAllocation);
+ }
+
+ @Override
+ protected void updateScriptInput() {
+ mScript.set_mInput(mInputAllocation);
+
+ int width = mInputAllocation.getType().getX();
+ mScript.set_width(width);
+ mScript.set_inv_width(1.0f / width);
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/rs/ScriptYuvMeans2dTo1d.java b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/ScriptYuvMeans2dTo1d.java
new file mode 100644
index 0000000..2776f96
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/ScriptYuvMeans2dTo1d.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 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 android.hardware.camera2.cts.rs;
+
+import static android.hardware.camera2.cts.helpers.Preconditions.*;
+
+import android.graphics.ImageFormat;
+import android.graphics.RectF;
+import android.util.Size;
+import android.hardware.camera2.cts.ScriptC_means_yuvx_444_2d_to_1d;
+import android.hardware.camera2.cts.rs.AllocationInfo.ElementInfo;
+import android.util.Log;
+
+/**
+ * Average pixels from a {@link ImageFormat#YUV_420_888 flexible-YUV} or
+ * {@link ElementInfo#U8_3 U8_3} {@link Allocation allocations} into a 1D Hx1
+ * {@link ElementInfo#U8_3 U8_3} {@link Allocation allocation}.
+ *
+ * <p>Users of this script should chain {@link ScriptYuvMeans1d} immediately afterwards
+ * to average the output down to a 1x1 element.</p>
+ */
+public class ScriptYuvMeans2dTo1d extends Script<ScriptC_means_yuvx_444_2d_to_1d> {
+
+ private static final String TAG = "ScriptYuvMeans2dTo1d";
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+ private static AllocationInfo createOutputInfo(AllocationInfo inputInfo) {
+ checkNotNull("inputInfo", inputInfo);
+ // (input) WxH -> (output) Hx1
+ return AllocationInfo.newInstance(inputInfo.getElement(),
+ new Size(inputInfo.getSize().getHeight(), /*height*/1));
+ }
+
+ public ScriptYuvMeans2dTo1d(AllocationInfo inputInfo) {
+ super(inputInfo,
+ createOutputInfo(inputInfo),
+ new ScriptC_means_yuvx_444_2d_to_1d(getRS()));
+
+ // YUV_420_888 and U8_3 is the only supported format here
+ if (!inputInfo.isElementEqualTo(ElementInfo.YUV) &&
+ !inputInfo.isElementEqualTo(ElementInfo.U8_3)) {
+ throw new UnsupportedOperationException("Unsupported element "
+ + inputInfo.getElement());
+ }
+ }
+
+ @Override
+ protected void executeUnchecked() {
+ // TODO: replace with switch statement
+ if (mInputInfo.isElementEqualTo(ElementInfo.YUV)) {
+ mScript.forEach_means_yuvf_420(mOutputAllocation);
+
+ if (VERBOSE) Log.v(TAG, "executeUnchecked - forEach_means_yuvf_420");
+ } else if (mInputInfo.isElementEqualTo(ElementInfo.U8_3)) {
+ mScript.forEach_means_yuvx_444(mOutputAllocation);
+
+ if (VERBOSE) Log.v(TAG, "executeUnchecked - forEach_means_yuvx_444");
+ } else {
+ throw new UnsupportedOperationException("Unsupported element "
+ + mInputInfo.getElement());
+ }
+ }
+
+ @Override
+ protected void updateScriptInput() {
+ mScript.set_mInput(mInputAllocation);
+
+ int width = mInputAllocation.getType().getX();
+ mScript.set_width(width);
+ mScript.set_inv_width(1.0f / width);
+
+ // Do not crop. Those who want to crop should use ScriptYuvCrop.class
+ mScript.set_src_x(0);
+ mScript.set_src_y(0);
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/rs/ScriptYuvToRgb.java b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/ScriptYuvToRgb.java
new file mode 100644
index 0000000..5cdc0a0
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/ScriptYuvToRgb.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 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 android.hardware.camera2.cts.rs;
+
+import static android.hardware.camera2.cts.helpers.Preconditions.*;
+
+import android.graphics.ImageFormat;
+import android.graphics.PixelFormat;
+import android.hardware.camera2.cts.rs.AllocationInfo.ElementInfo;
+import android.renderscript.Element;
+import android.renderscript.ScriptIntrinsicYuvToRGB;
+import android.util.Log;
+
+/**
+ * Convert {@link ImageFormat#YUV_420_888 flexible-YUV} {@link Allocation allocations} into
+ * a {@link ElementInfo#RGBA_8888 RGBA_8888} {@link Allocation allocation}.
+ */
+public class ScriptYuvToRgb extends Script<ScriptIntrinsicYuvToRGB> {
+ private static final String TAG = "ScriptYuvToRgb";
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+ private static AllocationInfo createOutputInfo(AllocationInfo outputInfo) {
+ checkNotNull("outputInfo", outputInfo);
+ return outputInfo.changeFormatWithDefaultUsage(PixelFormat.RGBA_8888);
+ }
+
+ public ScriptYuvToRgb(AllocationInfo inputInfo) {
+ super(inputInfo,
+ createOutputInfo(inputInfo),
+ ScriptIntrinsicYuvToRGB.create(getRS(), Element.YUV(getRS())));
+
+ // YUV_420_888 is the only supported format here
+ // XX: Supports any YUV 4:2:0 such as NV21/YV12 or just YUV_420_888 ?
+ if (!inputInfo.isElementEqualTo(ElementInfo.YUV)) {
+ throw new UnsupportedOperationException("Unsupported element "
+ + inputInfo.getElement());
+ }
+ }
+
+ @Override
+ protected void executeUnchecked() {
+ mScript.forEach(mOutputAllocation);
+
+ if (VERBOSE) { Log.v(TAG, "executeUnchecked - forEach done"); }
+ }
+
+ @Override
+ protected void updateScriptInput() {
+ mScript.setInput(mInputAllocation);
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestCase.java b/tests/tests/hardware/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestCase.java
new file mode 100644
index 0000000..75d91bf
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestCase.java
@@ -0,0 +1,297 @@
+/*
+ * 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 android.hardware.camera2.cts.testcases;
+
+import static android.hardware.camera2.cts.CameraTestUtils.*;
+import static com.android.ex.camera2.blocking.BlockingStateListener.*;
+
+import android.content.Context;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraDevice.CaptureListener;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CaptureRequest;
+import android.util.Size;
+import android.hardware.camera2.cts.CameraTestUtils;
+import android.hardware.camera2.cts.helpers.CameraErrorCollector;
+import android.hardware.camera2.cts.helpers.StaticMetadata;
+import android.hardware.camera2.cts.helpers.StaticMetadata.CheckLevel;
+import android.media.Image;
+import android.media.ImageReader;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.test.AndroidTestCase;
+import android.util.Log;
+import android.view.Surface;
+
+import com.android.ex.camera2.blocking.BlockingStateListener;
+
+import java.util.List;
+
+public class Camera2AndroidTestCase extends AndroidTestCase {
+ private static final String TAG = "Camera2AndroidTestCase";
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+ protected static final String DEBUG_FILE_NAME_BASE =
+ Environment.getExternalStorageDirectory().getPath();
+ // Default capture size: VGA size is required by CDD.
+ protected static final Size DEFAULT_CAPTURE_SIZE = new Size(640, 480);
+ protected static final int CAPTURE_WAIT_TIMEOUT_MS = 5000;
+
+ protected CameraManager mCameraManager;
+ protected CameraDevice mCamera;
+ protected BlockingStateListener mCameraListener;
+ protected String[] mCameraIds;
+ protected ImageReader mReader;
+ protected Surface mReaderSurface;
+ protected Handler mHandler;
+ protected HandlerThread mHandlerThread;
+ protected StaticMetadata mStaticInfo;
+ protected CameraErrorCollector mCollector;
+ protected List<Size> mOrderedPreviewSizes; // In descending order.
+ protected List<Size> mOrderedVideoSizes; // In descending order.
+ protected List<Size> mOrderedStillSizes; // In descending order.
+
+ @Override
+ public void setContext(Context context) {
+ super.setContext(context);
+ mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
+ assertNotNull("Can't connect to camera manager!", mCameraManager);
+ }
+
+ /**
+ * Set up the camera2 test case required environments, including CameraManager,
+ * HandlerThread, Camera IDs, and CameraStateListener etc.
+ */
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mCameraIds = mCameraManager.getCameraIdList();
+ assertNotNull("Camera ids shouldn't be null", mCameraIds);
+ mHandlerThread = new HandlerThread(TAG);
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+ mCameraListener = new BlockingStateListener();
+ mCollector = new CameraErrorCollector();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ mHandlerThread.quitSafely();
+ mHandler = null;
+ closeDefaultImageReader();
+
+ try {
+ mCollector.verify();
+ } catch (Throwable e) {
+ // When new Exception(e) is used, exception info will be printed twice.
+ throw new Exception(e.getMessage());
+ } finally {
+ super.tearDown();
+ }
+ }
+
+ /**
+ * Start capture with given {@link #CaptureRequest}.
+ *
+ * @param request The {@link #CaptureRequest} to be captured.
+ * @param repeating If the capture is single capture or repeating.
+ * @param listener The {@link #CaptureListener} camera device used to notify callbacks.
+ * @param handler The handler camera device used to post callbacks.
+ */
+ protected void startCapture(CaptureRequest request, boolean repeating,
+ CaptureListener listener, Handler handler) throws Exception {
+ if (VERBOSE) Log.v(TAG, "Starting capture");
+
+ if (repeating) {
+ mCamera.setRepeatingRequest(request, listener, handler);
+ } else {
+ mCamera.capture(request, listener, handler);
+ }
+ }
+
+ /**
+ * Stop the current active capture.
+ *
+ * @param fast When it is true, {@link CameraDevice#flush} is called, the stop capture
+ * could be faster.
+ */
+ protected void stopCapture(boolean fast) throws Exception {
+ if (VERBOSE) Log.v(TAG, "Stopping capture");
+
+ if (fast) {
+ /**
+ * Flush is useful for canceling long exposure single capture, it also could help
+ * to make the streaming capture stop sooner.
+ */
+ mCamera.flush();
+ mCameraListener.waitForState(STATE_IDLE, CAMERA_IDLE_TIMEOUT_MS);
+ } else {
+ configureCameraOutputs(mCamera, /*outputSurfaces*/null, mCameraListener);
+ }
+ }
+
+ /**
+ * Open a {@link #CameraDevice camera device} and get the StaticMetadata for a given camera id.
+ * The default mCameraListener is used to wait for states.
+ *
+ * @param cameraId The id of the camera device to be opened.
+ */
+ protected void openDevice(String cameraId) throws Exception {
+ openDevice(cameraId, mCameraListener);
+ }
+
+ /**
+ * Open a {@link #CameraDevice} and get the StaticMetadata for a given camera id and listener.
+ *
+ * @param cameraId The id of the camera device to be opened.
+ * @param listener The {@link #BlockingStateListener} used to wait for states.
+ */
+ protected void openDevice(String cameraId, BlockingStateListener listener) throws Exception {
+ mCamera = CameraTestUtils.openCamera(
+ mCameraManager, cameraId, listener, mHandler);
+ mCollector.setCameraId(cameraId);
+ mStaticInfo = new StaticMetadata(mCameraManager.getCameraCharacteristics(cameraId),
+ CheckLevel.ASSERT, /*collector*/null);
+ mOrderedPreviewSizes = getSupportedPreviewSizes(
+ cameraId, mCameraManager, PREVIEW_SIZE_BOUND);
+ mOrderedVideoSizes = getSupportedVideoSizes(cameraId, mCameraManager, PREVIEW_SIZE_BOUND);
+ mOrderedStillSizes = getSupportedStillSizes(cameraId, mCameraManager, null);
+
+ if (VERBOSE) {
+ Log.v(TAG, "Camera " + cameraId + " is opened");
+ }
+ }
+
+ /**
+ * Close a {@link #CameraDevice camera device} and clear the associated StaticInfo field for a
+ * given camera id. The default mCameraListener is used to wait for states.
+ * <p>
+ * This function must be used along with the {@link #openDevice} for the
+ * same camera id.
+ * </p>
+ *
+ * @param cameraId The id of the {@link #CameraDevice camera device} to be closed.
+ */
+ protected void closeDevice(String cameraId) {
+ closeDevice(cameraId, mCameraListener);
+ }
+
+ /**
+ * Close a {@link #CameraDevice camera device} and clear the associated StaticInfo field for a
+ * given camera id and listener.
+ * <p>
+ * This function must be used along with the {@link #openDevice} for the
+ * same camera id.
+ * </p>
+ *
+ * @param cameraId The id of the camera device to be closed.
+ * @param listener The BlockingStateListener used to wait for states.
+ */
+ protected void closeDevice(String cameraId, BlockingStateListener listener) {
+ if (mCamera != null) {
+ if (!cameraId.equals(mCamera.getId())) {
+ throw new IllegalStateException("Try to close a device that is not opened yet");
+ }
+ mCamera.close();
+ listener.waitForState(STATE_CLOSED, CAMERA_CLOSE_TIMEOUT_MS);
+ mCamera = null;
+ mStaticInfo = null;
+ mOrderedPreviewSizes = null;
+ mOrderedVideoSizes = null;
+ mOrderedStillSizes = null;
+
+ if (VERBOSE) {
+ Log.v(TAG, "Camera " + cameraId + " is closed");
+ }
+ }
+ }
+
+ /**
+ * Create an {@link ImageReader} object and get the surface.
+ * <p>
+ * This function creates {@link ImageReader} object and surface, then assign
+ * to the default {@link mReader} and {@link mReaderSurface}. It closes the
+ * current default active {@link ImageReader} if it exists.
+ * </p>
+ *
+ * @param size The size of this ImageReader to be created.
+ * @param format The format of this ImageReader to be created
+ * @param maxNumImages The max number of images that can be acquired
+ * simultaneously.
+ * @param listener The listener used by this ImageReader to notify
+ * callbacks.
+ */
+ protected void createDefaultImageReader(Size size, int format, int maxNumImages,
+ ImageReader.OnImageAvailableListener listener) throws Exception {
+ closeDefaultImageReader();
+
+ mReader = createImageReader(size, format, maxNumImages, listener);
+ mReaderSurface = mReader.getSurface();
+ if (VERBOSE) Log.v(TAG, "Created ImageReader size " + size.toString());
+ }
+
+ /**
+ * Create an {@link ImageReader} object.
+ *
+ * <p>This function creates image reader object for given format, maxImages, and size.</p>
+ *
+ * @param size The size of this ImageReader to be created.
+ * @param format The format of this ImageReader to be created
+ * @param maxNumImages The max number of images that can be acquired simultaneously.
+ * @param listener The listener used by this ImageReader to notify callbacks.
+ */
+
+ protected ImageReader createImageReader(Size size, int format, int maxNumImages,
+ ImageReader.OnImageAvailableListener listener) throws Exception {
+
+ ImageReader reader = ImageReader.newInstance(size.getWidth(), size.getHeight(),
+ format, maxNumImages);
+ reader.setOnImageAvailableListener(listener, mHandler);
+ if (VERBOSE) Log.v(TAG, "Created ImageReader size " + size.toString());
+ return reader;
+ }
+
+ /**
+ * Close the pending images then close current default {@link ImageReader} object.
+ */
+ protected void closeDefaultImageReader() {
+ closeImageReader(mReader);
+ mReader = null;
+ mReaderSurface = null;
+ }
+
+ /**
+ * Close an image reader instance.
+ *
+ * @param reader
+ */
+ protected void closeImageReader(ImageReader reader) {
+ if (reader != null) {
+ try {
+ // Close all possible pending images first.
+ Image image = reader.acquireLatestImage();
+ if (image != null) {
+ image.close();
+ }
+ } finally {
+ reader.close();
+ reader = null;
+ }
+ }
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/testcases/Camera2SurfaceViewTestCase.java b/tests/tests/hardware/src/android/hardware/camera2/cts/testcases/Camera2SurfaceViewTestCase.java
new file mode 100644
index 0000000..a169f7e
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/testcases/Camera2SurfaceViewTestCase.java
@@ -0,0 +1,547 @@
+/*
+ * 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 android.hardware.camera2.cts.testcases;
+
+import static android.hardware.camera2.cts.CameraTestUtils.*;
+import static com.android.ex.camera2.blocking.BlockingStateListener.STATE_CLOSED;
+
+import android.media.Image;
+import android.media.ImageReader;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.test.ActivityInstrumentationTestCase2;
+import android.util.Log;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.content.Context;
+import android.graphics.ImageFormat;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.util.Size;
+import android.util.Range;
+import android.hardware.camera2.CameraDevice.CaptureListener;
+import android.hardware.camera2.cts.Camera2SurfaceViewStubActivity;
+import android.hardware.camera2.cts.CameraTestUtils;
+import android.hardware.camera2.cts.CameraTestUtils.SimpleCaptureListener;
+import android.hardware.camera2.cts.helpers.CameraErrorCollector;
+import android.hardware.camera2.cts.helpers.StaticMetadata;
+import android.hardware.camera2.cts.helpers.StaticMetadata.CheckLevel;
+
+import com.android.ex.camera2.blocking.BlockingStateListener;
+import com.android.ex.camera2.exceptions.TimeoutRuntimeException;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Camera2 Preview test case base class by using SurfaceView as rendering target.
+ *
+ * <p>This class encapsulates the SurfaceView based preview common functionalities.
+ * The setup and teardown of CameraManager, test HandlerThread, Activity, Camera IDs
+ * and CameraStateListener are handled in this class. Some basic preview related utility
+ * functions are provided to facilitate the derived preview-based test classes.
+ * </p>
+ */
+
+public class Camera2SurfaceViewTestCase extends
+ ActivityInstrumentationTestCase2<Camera2SurfaceViewStubActivity> {
+ private static final String TAG = "SurfaceViewTestCase";
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+ private static final int WAIT_FOR_SURFACE_CHANGE_TIMEOUT_MS = 1000;
+ private static final int MAX_READER_IMAGES = 5;
+
+ private Size mPreviewSize;
+
+ // TODO: Use internal storage for this to make sure the file is only visible to test.
+ protected static final String DEBUG_FILE_NAME_BASE =
+ Environment.getExternalStorageDirectory().getPath();
+ protected static final int WAIT_FOR_RESULT_TIMEOUT_MS = 3000;
+ protected static final float FRAME_DURATION_ERROR_MARGIN = 0.005f; // 0.5 percent error margin.
+ protected static final int NUM_RESULTS_WAIT_TIMEOUT = 100;
+
+ protected Context mContext;
+ protected CameraManager mCameraManager;
+ protected String[] mCameraIds;
+ protected HandlerThread mHandlerThread;
+ protected Handler mHandler;
+ protected BlockingStateListener mCameraListener;
+ protected CameraErrorCollector mCollector;
+ // Per device fields:
+ protected StaticMetadata mStaticInfo;
+ protected CameraDevice mCamera;
+ protected ImageReader mReader;
+ protected Surface mReaderSurface;
+ protected Surface mPreviewSurface;
+ protected List<Size> mOrderedPreviewSizes; // In descending order.
+ protected List<Size> mOrderedVideoSizes; // In descending order.
+ protected List<Size> mOrderedStillSizes; // In descending order.
+ protected HashMap<Size, Long> mMinPreviewFrameDurationMap;
+
+ public Camera2SurfaceViewTestCase() {
+ super(Camera2SurfaceViewStubActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ /**
+ * Set up the camera preview required environments, including activity,
+ * CameraManager, HandlerThread, Camera IDs, and CameraStateListener.
+ */
+ super.setUp();
+ mContext = getActivity();
+ mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
+ assertNotNull("Unable to get CameraManager", mCameraManager);
+ mCameraIds = mCameraManager.getCameraIdList();
+ assertNotNull("Unable to get camera ids", mCameraIds);
+ mHandlerThread = new HandlerThread(TAG);
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+ mCameraListener = new BlockingStateListener();
+ mCollector = new CameraErrorCollector();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ // Teardown the camera preview required environments.
+ mHandlerThread.quitSafely();
+ mHandler = null;
+ mCameraListener = null;
+
+ try {
+ mCollector.verify();
+ } catch (Throwable e) {
+ // When new Exception(e) is used, exception info will be printed twice.
+ throw new Exception(e.getMessage());
+ } finally {
+ super.tearDown();
+ }
+ }
+
+ /**
+ * Start camera preview by using the given request, preview size and capture
+ * listener.
+ * <p>
+ * If preview is already started, calling this function will stop the
+ * current preview stream and start a new preview stream with given
+ * parameters. No need to call stopPreview between two startPreview calls.
+ * </p>
+ *
+ * @param request The request builder used to start the preview.
+ * @param previewSz The size of the camera device output preview stream.
+ * @param listener The callbacks the camera device will notify when preview
+ * capture is available.
+ */
+ protected void startPreview(CaptureRequest.Builder request, Size previewSz,
+ CaptureListener listener) throws Exception {
+ // Update preview size.
+ updatePreviewSurface(previewSz);
+ if (VERBOSE) {
+ Log.v(TAG, "start preview with size " + mPreviewSize.toString());
+ }
+
+ configurePreviewOutput(request);
+
+ mCamera.setRepeatingRequest(request.build(), listener, mHandler);
+ }
+
+ /**
+ * Configure the preview output stream.
+ *
+ * @param request The request to be configured with preview surface
+ */
+ protected void configurePreviewOutput(CaptureRequest.Builder request)
+ throws CameraAccessException {
+ List<Surface> outputSurfaces = new ArrayList<Surface>(/*capacity*/1);
+ outputSurfaces.add(mPreviewSurface);
+ configureCameraOutputs(mCamera, outputSurfaces, mCameraListener);
+
+ request.addTarget(mPreviewSurface);
+ }
+
+ /**
+ * Create a {@link CaptureRequest#Builder} and add the default preview surface.
+ *
+ * @return The {@link CaptureRequest#Builder} to be created
+ * @throws CameraAccessException When create capture request from camera fails
+ */
+ protected CaptureRequest.Builder createRequestForPreview() throws CameraAccessException {
+ if (mPreviewSurface == null) {
+ throw new IllegalStateException(
+ "Preview surface is not set yet, call updatePreviewSurface or startPreview"
+ + "first to set the preview surface properly.");
+ }
+ CaptureRequest.Builder requestBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ requestBuilder.addTarget(mPreviewSurface);
+ return requestBuilder;
+ }
+
+ /**
+ * Stop preview for current camera device.
+ */
+ protected void stopPreview() throws Exception {
+ if (VERBOSE) Log.v(TAG, "Stopping preview and waiting for idle");
+ // Stop repeat, wait for captures to complete, and disconnect from surfaces
+ configureCameraOutputs(mCamera, /*outputSurfaces*/null, mCameraListener);
+ }
+
+ /**
+ * Setup still (JPEG) capture configuration and start preview.
+ * <p>
+ * The default max number of image is set to image reader.
+ * </p>
+ *
+ * @param previewRequest The capture request to be used for preview
+ * @param stillRequest The capture request to be used for still capture
+ * @param previewSz Preview size
+ * @param stillSz The still capture size
+ * @param resultListener Capture result listener
+ * @param imageListener The still capture image listener
+ */
+ protected void prepareStillCaptureAndStartPreview(CaptureRequest.Builder previewRequest,
+ CaptureRequest.Builder stillRequest, Size previewSz, Size stillSz,
+ CaptureListener resultListener,
+ ImageReader.OnImageAvailableListener imageListener) throws Exception {
+ prepareCaptureAndStartPreview(previewRequest, stillRequest, previewSz, stillSz,
+ ImageFormat.JPEG, resultListener, MAX_READER_IMAGES, imageListener);
+ }
+
+ /**
+ * Setup still (JPEG) capture configuration and start preview.
+ *
+ * @param previewRequest The capture request to be used for preview
+ * @param stillRequest The capture request to be used for still capture
+ * @param previewSz Preview size
+ * @param stillSz The still capture size
+ * @param resultListener Capture result listener
+ * @param maxNumImages The max number of images set to the image reader
+ * @param imageListener The still capture image listener
+ */
+ protected void prepareStillCaptureAndStartPreview(CaptureRequest.Builder previewRequest,
+ CaptureRequest.Builder stillRequest, Size previewSz, Size stillSz,
+ CaptureListener resultListener, int maxNumImages,
+ ImageReader.OnImageAvailableListener imageListener) throws Exception {
+ prepareCaptureAndStartPreview(previewRequest, stillRequest, previewSz, stillSz,
+ ImageFormat.JPEG, resultListener, maxNumImages, imageListener);
+ }
+
+ /**
+ * Setup raw capture configuration and start preview.
+ *
+ * <p>
+ * The default max number of image is set to image reader.
+ * </p>
+ *
+ * @param previewRequest The capture request to be used for preview
+ * @param rawRequest The capture request to be used for raw capture
+ * @param previewSz Preview size
+ * @param rawSz The raw capture size
+ * @param resultListener Capture result listener
+ * @param imageListener The raw capture image listener
+ */
+ protected void prepareRawCaptureAndStartPreview(CaptureRequest.Builder previewRequest,
+ CaptureRequest.Builder rawRequest, Size previewSz, Size rawSz,
+ CaptureListener resultListener,
+ ImageReader.OnImageAvailableListener imageListener) throws Exception {
+ prepareCaptureAndStartPreview(previewRequest, rawRequest, previewSz, rawSz,
+ ImageFormat.RAW_SENSOR, resultListener, MAX_READER_IMAGES, imageListener);
+ }
+
+ /**
+ * Wait for expected result key value available in a certain number of results.
+ *
+ * <p>
+ * Check the result immediately if numFramesWait is 0.
+ * </p>
+ *
+ * @param listener The capture listener to get capture result
+ * @param resultKey The capture result key associated with the result value
+ * @param expectedValue The result value need to be waited for
+ * @param numResultsWait Number of frame to wait before times out
+ * @throws TimeoutRuntimeException If more than numResultsWait results are
+ * seen before the result matching myRequest arrives, or each individual wait
+ * for result times out after {@value #WAIT_FOR_RESULT_TIMEOUT_MS}ms.
+ */
+ protected static <T> void waitForResultValue(SimpleCaptureListener listener,
+ CaptureResult.Key<T> resultKey,
+ T expectedValue, int numResultsWait) {
+ List<T> expectedValues = new ArrayList<T>();
+ expectedValues.add(expectedValue);
+ waitForAnyResultValue(listener, resultKey, expectedValues, numResultsWait);
+ }
+
+ /**
+ * Wait for any expected result key values available in a certain number of results.
+ *
+ * <p>
+ * Check the result immediately if numFramesWait is 0.
+ * </p>
+ *
+ * @param listener The capture listener to get capture result.
+ * @param resultKey The capture result key associated with the result value.
+ * @param expectedValues The list of result value need to be waited for,
+ * return immediately if the list is empty.
+ * @param numResultsWait Number of frame to wait before times out.
+ * @throws TimeoutRuntimeException If more than numResultsWait results are.
+ * seen before the result matching myRequest arrives, or each individual wait
+ * for result times out after {@value #WAIT_FOR_RESULT_TIMEOUT_MS}ms.
+ */
+ protected static <T> void waitForAnyResultValue(SimpleCaptureListener listener,
+ CaptureResult.Key<T> resultKey,
+ List<T> expectedValues, int numResultsWait) {
+ if (numResultsWait < 0 || listener == null || expectedValues == null) {
+ throw new IllegalArgumentException(
+ "Input must be non-negative number and listener/expectedValues "
+ + "must be non-null");
+ }
+
+ int i = 0;
+ CaptureResult result;
+ do {
+ result = listener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
+ T value = result.get(resultKey);
+ for ( T expectedValue : expectedValues) {
+ if (VERBOSE) {
+ Log.v(TAG, "Current result value for key " + resultKey.getName() + " is: "
+ + value.toString());
+ }
+ if (value.equals(expectedValue)) {
+ return;
+ }
+ }
+ } while (i++ < numResultsWait);
+
+ throw new TimeoutRuntimeException(
+ "Unable to get the expected result value " + expectedValues + " for key " +
+ resultKey.getName() + " after waiting for " + numResultsWait + " results");
+ }
+
+ /**
+ * Wait for numResultWait frames
+ *
+ * @param resultListener The capture listener to get capture result back.
+ * @param numResultsWait Number of frame to wait
+ */
+ protected static void waitForNumResults(SimpleCaptureListener resultListener,
+ int numResultsWait) {
+ if (numResultsWait <= 0 || resultListener == null) {
+ throw new IllegalArgumentException(
+ "Input must be positive number and listener must be non-null");
+ }
+
+ CaptureResult result;
+ for (int i = 0; i < numResultsWait; i++) {
+ result = resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
+ }
+ return;
+ }
+
+ /**
+ * Wait for AE to be stabilized before capture: CONVERGED or FLASH_REQUIRED.
+ *
+ * @param resultListener The capture listener to get capture result back.
+ */
+ protected static void waitForAeStable(SimpleCaptureListener resultListener) {
+ List<Integer> expectedAeStates = new ArrayList<Integer>();
+ expectedAeStates.add(new Integer(CaptureResult.CONTROL_AE_STATE_CONVERGED));
+ expectedAeStates.add(new Integer(CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED));
+ waitForAnyResultValue(resultListener, CaptureResult.CONTROL_AE_STATE, expectedAeStates,
+ NUM_RESULTS_WAIT_TIMEOUT);
+ }
+
+ /**
+ * Wait for AE to be: LOCKED
+ *
+ * @param resultListener The capture listener to get capture result back.
+ */
+ protected static void waitForAeLocked(SimpleCaptureListener resultListener) {
+ List<Integer> expectedAeStates = new ArrayList<Integer>();
+ expectedAeStates.add(new Integer(CaptureResult.CONTROL_AE_STATE_LOCKED));
+ waitForAnyResultValue(resultListener, CaptureResult.CONTROL_AE_STATE, expectedAeStates,
+ NUM_RESULTS_WAIT_TIMEOUT);
+ }
+
+ /**
+ * Create an {@link ImageReader} object and get the surface.
+ *
+ * @param size The size of this ImageReader to be created.
+ * @param format The format of this ImageReader to be created
+ * @param maxNumImages The max number of images that can be acquired simultaneously.
+ * @param listener The listener used by this ImageReader to notify callbacks.
+ */
+ protected void createImageReader(Size size, int format, int maxNumImages,
+ ImageReader.OnImageAvailableListener listener) throws Exception {
+ closeImageReader();
+
+ mReader = ImageReader.newInstance(size.getWidth(), size.getHeight(), format, maxNumImages);
+ mReaderSurface = mReader.getSurface();
+ mReader.setOnImageAvailableListener(listener, mHandler);
+ if (VERBOSE) Log.v(TAG, "Created ImageReader size " + size.toString());
+ }
+
+ /**
+ * Close the pending images then close current active {@link ImageReader} object.
+ */
+ protected void closeImageReader() {
+ if (mReader != null) {
+ try {
+ // Close all possible pending images first.
+ Image image = mReader.acquireLatestImage();
+ if (image != null) {
+ image.close();
+ }
+ } finally {
+ mReader.close();
+ mReader = null;
+ }
+ }
+ }
+
+ /**
+ * Open a camera device and get the StaticMetadata for a given camera id.
+ *
+ * @param cameraId The id of the camera device to be opened.
+ */
+ protected void openDevice(String cameraId) throws Exception {
+ mCamera = CameraTestUtils.openCamera(
+ mCameraManager, cameraId, mCameraListener, mHandler);
+ mCollector.setCameraId(cameraId);
+ mStaticInfo = new StaticMetadata(mCameraManager.getCameraCharacteristics(cameraId),
+ CheckLevel.ASSERT, /*collector*/null);
+ mOrderedPreviewSizes = getSupportedPreviewSizes(cameraId, mCameraManager, PREVIEW_SIZE_BOUND);
+ mOrderedVideoSizes = getSupportedVideoSizes(cameraId, mCameraManager, PREVIEW_SIZE_BOUND);
+ mOrderedStillSizes = getSupportedStillSizes(cameraId, mCameraManager, null);
+ // Use ImageFormat.YUV_420_888 for now. TODO: need figure out what's format for preview
+ // in public API side.
+ mMinPreviewFrameDurationMap =
+ mStaticInfo.getAvailableMinFrameDurationsForFormatChecked(ImageFormat.YUV_420_888);
+ }
+
+ /**
+ * Close the current actively used camera device.
+ */
+ protected void closeDevice() {
+ if (mCamera != null) {
+ mCamera.close();
+ mCameraListener.waitForState(STATE_CLOSED, CAMERA_CLOSE_TIMEOUT_MS);
+ mCamera = null;
+ mStaticInfo = null;
+ mOrderedPreviewSizes = null;
+ mOrderedVideoSizes = null;
+ mOrderedStillSizes = null;
+ }
+ }
+
+ protected void updatePreviewSurface(Size size) {
+ if (size.equals(mPreviewSize)) {
+ return;
+ }
+
+ mPreviewSize = size;
+ Camera2SurfaceViewStubActivity stubActivity = getActivity();
+ final SurfaceHolder holder = stubActivity.getSurfaceView().getHolder();
+ Handler handler = new Handler(Looper.getMainLooper());
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ holder.setFixedSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
+ }
+ });
+
+ boolean res = stubActivity.waitForSurfaceSizeChanged(
+ WAIT_FOR_SURFACE_CHANGE_TIMEOUT_MS, mPreviewSize.getWidth(),
+ mPreviewSize.getHeight());
+ assertTrue("wait for surface change to " + mPreviewSize.toString() + " timed out", res);
+ mPreviewSurface = holder.getSurface();
+ assertTrue("Preview surface is invalid", mPreviewSurface.isValid());
+ }
+
+ /**
+ * Setup single capture configuration and start preview.
+ *
+ * @param previewRequest The capture request to be used for preview
+ * @param stillRequest The capture request to be used for still capture
+ * @param previewSz Preview size
+ * @param captureSz Still capture size
+ * @param format The single capture image format
+ * @param resultListener Capture result listener
+ * @param maxNumImages The max number of images set to the image reader
+ * @param imageListener The single capture capture image listener
+ */
+ private void prepareCaptureAndStartPreview(CaptureRequest.Builder previewRequest,
+ CaptureRequest.Builder stillRequest, Size previewSz, Size captureSz, int format,
+ CaptureListener resultListener, int maxNumImages,
+ ImageReader.OnImageAvailableListener imageListener) throws Exception {
+ if (VERBOSE) {
+ Log.v(TAG, String.format("Prepare single capture (%s) and preview (%s)",
+ captureSz.toString(), previewSz.toString()));
+ }
+
+ // Update preview size.
+ updatePreviewSurface(previewSz);
+
+ // Create ImageReader.
+ createImageReader(captureSz, format, maxNumImages, imageListener);
+
+ // Configure output streams with preview and jpeg streams.
+ List<Surface> outputSurfaces = new ArrayList<Surface>();
+ outputSurfaces.add(mPreviewSurface);
+ outputSurfaces.add(mReaderSurface);
+ configureCameraOutputs(mCamera, outputSurfaces, mCameraListener);
+
+ // Configure the requests.
+ previewRequest.addTarget(mPreviewSurface);
+ stillRequest.addTarget(mPreviewSurface);
+ stillRequest.addTarget(mReaderSurface);
+
+ // Start preview.
+ mCamera.setRepeatingRequest(previewRequest.build(), resultListener, mHandler);
+ }
+
+ /**
+ * Get the max preview size that supports the given fpsRange.
+ *
+ * @param fpsRange The fps range the returned size must support.
+ * @return max size that support the given fps range.
+ */
+ protected Size getMaxPreviewSizeForFpsRange(Range<Integer> fpsRange) {
+ if (fpsRange == null || fpsRange.getLower() <= 0 || fpsRange.getUpper() <= 0) {
+ throw new IllegalArgumentException("Invalid fps range argument");
+ }
+ if (mOrderedPreviewSizes == null || mMinPreviewFrameDurationMap == null) {
+ throw new IllegalStateException("mOrderedPreviewSizes and mMinPreviewFrameDurationMap"
+ + " must be initialized");
+ }
+
+ long[] frameDurationRange =
+ new long[]{(long) (1e9 / fpsRange.getUpper()), (long) (1e9 / fpsRange.getLower())};
+ for (Size size : mOrderedPreviewSizes) {
+ long minDuration = mMinPreviewFrameDurationMap.get(size);
+ if (minDuration <= frameDurationRange[0]) {
+ return size;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/cts/CameraTest.java b/tests/tests/hardware/src/android/hardware/cts/CameraTest.java
index c6e28f7..863325f 100644
--- a/tests/tests/hardware/src/android/hardware/cts/CameraTest.java
+++ b/tests/tests/hardware/src/android/hardware/cts/CameraTest.java
@@ -33,6 +33,7 @@
import android.media.CamcorderProfile;
import android.media.ExifInterface;
import android.media.MediaRecorder;
+import android.os.Build;
import android.os.ConditionVariable;
import android.os.Environment;
import android.os.Looper;
@@ -47,12 +48,14 @@
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
+import java.util.TimeZone;
import junit.framework.AssertionFailedError;
@@ -63,7 +66,7 @@
public class CameraTest extends ActivityInstrumentationTestCase2<CameraStubActivity> {
private static String TAG = "CameraTest";
private static final String PACKAGE = "com.android.cts.stub";
- private static final boolean LOGV = false;
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
private final String JPEG_PATH = Environment.getExternalStorageDirectory().getPath() +
"/test.jpg";
private byte[] mJpegData;
@@ -91,6 +94,13 @@
private static final int AUTOEXPOSURE_LOCK = 0;
private static final int AUTOWHITEBALANCE_LOCK = 1;
+ // Some exif tags that are not defined by ExifInterface but supported.
+ private static final String TAG_DATETIME_DIGITIZED = "DateTimeDigitized";
+ private static final String TAG_SUBSEC_TIME = "SubSecTime";
+ private static final String TAG_SUBSEC_TIME_ORIG = "SubSecTimeOriginal";
+ private static final String TAG_SUBSEC_TIME_DIG = "SubSecTimeDigitized";
+
+
private PreviewCallback mPreviewCallback = new PreviewCallback();
private TestShutterCallback mShutterCallback = new TestShutterCallback();
private RawPictureCallback mRawPictureCallback = new RawPictureCallback();
@@ -108,7 +118,7 @@
public CameraTest() {
super(PACKAGE, CameraStubActivity.class);
- if (LOGV) Log.v(TAG, "Camera Constructor");
+ if (VERBOSE) Log.v(TAG, "Camera Constructor");
}
@Override
@@ -151,7 +161,7 @@
Log.v(TAG, "camera is opened");
startDone.open();
Looper.loop(); // Blocks forever until Looper.quit() is called.
- if (LOGV) Log.v(TAG, "initializeMessageLooper: quit.");
+ if (VERBOSE) Log.v(TAG, "initializeMessageLooper: quit.");
}
}.start();
@@ -187,7 +197,7 @@
private static int calculateBufferSize(int width, int height,
int format, int bpp) {
- if (LOGV) {
+ if (VERBOSE) {
Log.v(TAG, "calculateBufferSize: w=" + width + ",h=" + height
+ ",f=" + format + ",bpp=" + bpp);
}
@@ -204,7 +214,7 @@
int c_size = c_stride * height/2;
int size = y_size + c_size * 2;
- if (LOGV) {
+ if (VERBOSE) {
Log.v(TAG, "calculateBufferSize: YV12 size= " + size);
}
@@ -238,9 +248,9 @@
}
mPreviewCallbackResult = PREVIEW_CALLBACK_RECEIVED;
mCamera.stopPreview();
- if (LOGV) Log.v(TAG, "notify the preview callback");
+ if (VERBOSE) Log.v(TAG, "notify the preview callback");
mPreviewDone.open();
- if (LOGV) Log.v(TAG, "Preview callback stop");
+ if (VERBOSE) Log.v(TAG, "Preview callback stop");
}
}
@@ -248,7 +258,7 @@
private final class TestShutterCallback implements ShutterCallback {
public void onShutter() {
mShutterCallbackResult = true;
- if (LOGV) Log.v(TAG, "onShutter called");
+ if (VERBOSE) Log.v(TAG, "onShutter called");
}
}
@@ -256,7 +266,7 @@
private final class RawPictureCallback implements PictureCallback {
public void onPictureTaken(byte [] rawData, Camera camera) {
mRawPictureCallbackResult = true;
- if (LOGV) Log.v(TAG, "RawPictureCallback callback");
+ if (VERBOSE) Log.v(TAG, "RawPictureCallback callback");
}
}
@@ -273,14 +283,14 @@
outStream.close();
mJpegPictureCallbackResult = true;
- if (LOGV) {
+ if (VERBOSE) {
Log.v(TAG, "JpegPictureCallback rawDataLength = " + rawData.length);
}
} else {
mJpegPictureCallbackResult = false;
}
mSnapshotDone.open();
- if (LOGV) Log.v(TAG, "Jpeg Picture callback");
+ if (VERBOSE) Log.v(TAG, "Jpeg Picture callback");
} catch (IOException e) {
// no need to fail here; callback worked fine
Log.w(TAG, "Error writing picture to sd card.");
@@ -313,7 +323,7 @@
}
private void waitForPreviewDone() {
- if (LOGV) Log.v(TAG, "Wait for preview callback");
+ if (VERBOSE) Log.v(TAG, "Wait for preview callback");
if (!mPreviewDone.block(WAIT_FOR_COMMAND_TO_COMPLETE)) {
// timeout could be expected or unexpected. The caller will decide.
Log.v(TAG, "waitForPreviewDone: timeout");
@@ -340,7 +350,18 @@
}
private void checkPreviewCallback() throws Exception {
- if (LOGV) Log.v(TAG, "check preview callback");
+ if (VERBOSE) Log.v(TAG, "check preview callback");
+ mCamera.startPreview();
+ waitForPreviewDone();
+ mCamera.setPreviewCallback(null);
+ }
+
+ /**
+ * Start preview and wait for the first preview callback, which indicates the
+ * preview becomes active.
+ */
+ private void blockingStartPreview() {
+ mCamera.setPreviewCallback(new SimplePreviewStreamCb(/*Id*/0));
mCamera.startPreview();
waitForPreviewDone();
mCamera.setPreviewCallback(null);
@@ -385,7 +406,7 @@
assertEquals(pictureSize.height, bmpOptions.outHeight);
} else {
int realArea = bmpOptions.outWidth * bmpOptions.outHeight;
- if (LOGV) Log.v(TAG, "Video snapshot is " +
+ if (VERBOSE) Log.v(TAG, "Video snapshot is " +
bmpOptions.outWidth + " x " + bmpOptions.outHeight +
", video size is " + videoWidth + " x " + videoHeight);
assertTrue ("Video snapshot too small! Expected at least " +
@@ -810,6 +831,21 @@
assertTrue(mJpegPictureCallbackResult);
exif = new ExifInterface(JPEG_PATH);
assertFalse(exif.hasThumbnail());
+ // Primary image should still be valid for no thumbnail capture.
+ BitmapFactory.decodeFile(JPEG_PATH, bmpOptions);
+ if (!recording) {
+ assertTrue("Jpeg primary image size should match requested size",
+ bmpOptions.outWidth == pictureSize.width &&
+ bmpOptions.outHeight == pictureSize.height);
+ } else {
+ assertTrue(bmpOptions.outWidth >= recordingWidth ||
+ bmpOptions.outWidth == size.width);
+ assertTrue(bmpOptions.outHeight >= recordingHeight ||
+ bmpOptions.outHeight == size.height);
+ }
+
+ assertNotNull("Jpeg primary image data should be decodable",
+ BitmapFactory.decodeFile(JPEG_PATH));
}
@UiThreadTest
@@ -835,8 +871,10 @@
// Test various exif tags.
ExifInterface exif = new ExifInterface(JPEG_PATH);
- assertNotNull(exif.getAttribute(ExifInterface.TAG_MAKE));
- assertNotNull(exif.getAttribute(ExifInterface.TAG_MODEL));
+ StringBuffer failedCause = new StringBuffer("Jpeg exif test failed:\n");
+ boolean extraExiftestPassed = checkExtraExifTagsSucceeds(failedCause, exif);
+
+ if (VERBOSE) Log.v(TAG, "Testing exif tag TAG_DATETIME");
String datetime = exif.getAttribute(ExifInterface.TAG_DATETIME);
assertNotNull(datetime);
// Datetime should be local time.
@@ -849,6 +887,7 @@
assertBitmapAndJpegSizeEqual(mJpegData, exif);
// Test gps exif tags.
+ if (VERBOSE) Log.v(TAG, "Testing exif GPS tags");
testGpsExifValues(parameters, 37.736071, -122.441983, 21, 1199145600,
"GPS NETWORK HYBRID ARE ALL FINE.");
testGpsExifValues(parameters, 0.736071, 0.441983, 1, 1199145601, "GPS");
@@ -856,6 +895,7 @@
// Test gps tags do not exist after calling removeGpsData. Also check if
// image width and height exif match the jpeg when jpeg rotation is set.
+ if (VERBOSE) Log.v(TAG, "Testing exif GPS tag removal");
if (!recording) mCamera.startPreview();
parameters.removeGpsData();
parameters.setRotation(90); // For testing image width and height exif.
@@ -865,11 +905,164 @@
exif = new ExifInterface(JPEG_PATH);
checkGpsDataNull(exif);
assertBitmapAndJpegSizeEqual(mJpegData, exif);
+ assertTrue(failedCause.toString(), extraExiftestPassed);
// Reset the rotation to prevent from affecting other tests.
parameters.setRotation(0);
mCamera.setParameters(parameters);
}
+ /**
+ * Sanity check of some extra exif tags.
+ * <p>
+ * Sanity check some extra exif tags without asserting the check failures
+ * immediately. When a failure is detected, the failure cause is logged,
+ * the rest of the tests are still executed. The caller can assert with the
+ * failure cause based on the returned test status.
+ * </p>
+ *
+ * @param logBuf Log failure cause to this StringBuffer if there is
+ * any failure.
+ * @param exif The exif data associated with a jpeg image being tested.
+ * @return true if no test failure is found, false if there is any failure.
+ */
+ private boolean checkExtraExifTagsSucceeds(StringBuffer logBuf, ExifInterface exif) {
+ if (logBuf == null || exif == null) {
+ throw new IllegalArgumentException("failureCause and exif shouldn't be null");
+ }
+
+ if (VERBOSE) Log.v(TAG, "Testing extra exif tags");
+ boolean allTestsPassed = true;
+ boolean passedSoFar = true;
+ String failureMsg;
+
+ // TAG_EXPOSURE_TIME
+ // ExifInterface API gives exposure time value in the form of float instead of rational
+ String exposureTime = exif.getAttribute(ExifInterface.TAG_EXPOSURE_TIME);
+ passedSoFar = expectNotNull("Exif TAG_EXPOSURE_TIME is null!", logBuf, exposureTime);
+ if (passedSoFar) {
+ double exposureTimeValue = Double.parseDouble(exposureTime);
+ failureMsg = "Exif exposure time " + exposureTime + " should be a positive value";
+ passedSoFar = expectTrue(failureMsg, logBuf, exposureTimeValue > 0);
+ }
+ allTestsPassed = allTestsPassed && passedSoFar;
+
+ // TAG_APERTURE
+ // ExifInterface API gives aperture value in the form of float instead of rational
+ String aperture = exif.getAttribute(ExifInterface.TAG_APERTURE);
+ passedSoFar = expectNotNull("Exif TAG_APERTURE is null!", logBuf, aperture);
+ if (passedSoFar) {
+ double apertureValue = Double.parseDouble(aperture);
+ passedSoFar = expectTrue("Exif TAG_APERTURE value " + aperture + " should be positive!",
+ logBuf, apertureValue > 0);
+ }
+ allTestsPassed = allTestsPassed && passedSoFar;
+
+ // TAG_FLASH
+ String flash = exif.getAttribute(ExifInterface.TAG_FLASH);
+ passedSoFar = expectNotNull("Exif TAG_FLASH is null!", logBuf, flash);
+ allTestsPassed = allTestsPassed && passedSoFar;
+
+ // TAG_WHITE_BALANCE
+ String whiteBalance = exif.getAttribute(ExifInterface.TAG_WHITE_BALANCE);
+ passedSoFar = expectNotNull("Exif TAG_WHITE_BALANCE is null!", logBuf, whiteBalance);
+ allTestsPassed = allTestsPassed && passedSoFar;
+
+ // TAG_MAKE
+ String make = exif.getAttribute(ExifInterface.TAG_MAKE);
+ passedSoFar = expectNotNull("Exif TAG_MAKE is null!", logBuf, make);
+ if (passedSoFar) {
+ passedSoFar = expectTrue("Exif TAG_MODEL value: " + make
+ + " should match build manufacturer: " + Build.MANUFACTURER, logBuf,
+ make.equals(Build.MANUFACTURER));
+ }
+ allTestsPassed = allTestsPassed && passedSoFar;
+
+ // TAG_MODEL
+ String model = exif.getAttribute(ExifInterface.TAG_MODEL);
+ passedSoFar = expectNotNull("Exif TAG_MODEL is null!", logBuf, model);
+ if (passedSoFar) {
+ passedSoFar = expectTrue("Exif TAG_MODEL value: " + model
+ + " should match build manufacturer: " + Build.MODEL, logBuf,
+ model.equals(Build.MODEL));
+ }
+ allTestsPassed = allTestsPassed && passedSoFar;
+
+ // TAG_ISO
+ int iso = exif.getAttributeInt(ExifInterface.TAG_ISO, -1);
+ passedSoFar = expectTrue("Exif ISO value " + iso + " is invalid", logBuf, iso > 0);
+ allTestsPassed = allTestsPassed && passedSoFar;
+
+ // TAG_DATETIME_DIGITIZED (a.k.a Create time for digital cameras).
+ String digitizedTime = exif.getAttribute(TAG_DATETIME_DIGITIZED);
+ passedSoFar = expectNotNull("Exif TAG_DATETIME_DIGITIZED is null!", logBuf, digitizedTime);
+ if (passedSoFar) {
+ String datetime = exif.getAttribute(ExifInterface.TAG_DATETIME);
+ passedSoFar = expectNotNull("Exif TAG_DATETIME is null!", logBuf, datetime);
+ if (passedSoFar) {
+ passedSoFar = expectTrue("dataTime should match digitizedTime", logBuf,
+ digitizedTime.equals(datetime));
+ }
+ }
+ allTestsPassed = allTestsPassed && passedSoFar;
+
+ /**
+ * TAG_SUBSEC_TIME. Since the sub second tag strings are truncated to at
+ * most 9 digits in ExifInterface implementation, use getAttributeInt to
+ * sanitize it. When the default value -1 is returned, it means that
+ * this exif tag either doesn't exist or is a non-numerical invalid
+ * string. Same rule applies to the rest of sub second tags.
+ */
+ int subSecTime = exif.getAttributeInt(TAG_SUBSEC_TIME, -1);
+ passedSoFar = expectTrue(
+ "Exif TAG_SUBSEC_TIME value is null or invalid!", logBuf, subSecTime > 0);
+ allTestsPassed = allTestsPassed && passedSoFar;
+
+ // TAG_SUBSEC_TIME_ORIG
+ int subSecTimeOrig = exif.getAttributeInt(TAG_SUBSEC_TIME_ORIG, -1);
+ passedSoFar = expectTrue(
+ "Exif TAG_SUBSEC_TIME_ORIG value is null or invalid!", logBuf, subSecTimeOrig > 0);
+ allTestsPassed = allTestsPassed && passedSoFar;
+
+ // TAG_SUBSEC_TIME_DIG
+ int subSecTimeDig = exif.getAttributeInt(TAG_SUBSEC_TIME_DIG, -1);
+ passedSoFar = expectTrue(
+ "Exif TAG_SUBSEC_TIME_DIG value is null or invalid!", logBuf, subSecTimeDig > 0);
+ allTestsPassed = allTestsPassed && passedSoFar;
+
+ return allTestsPassed;
+ }
+
+ /**
+ * Check if object is null and log failure msg.
+ *
+ * @param msg Failure msg.
+ * @param logBuffer StringBuffer to log the failure msg.
+ * @param obj Object to test.
+ * @return true if object is not null, otherwise return false.
+ */
+ private boolean expectNotNull(String msg, StringBuffer logBuffer, Object obj)
+ {
+ if (obj == null) {
+ logBuffer.append(msg + "\n");
+ }
+ return (obj != null);
+ }
+
+ /**
+ * Check if condition is false and log failure msg.
+ *
+ * @param msg Failure msg.
+ * @param logBuffer StringBuffer to log the failure msg.
+ * @param condition Condition to test.
+ * @return The value of the condition.
+ */
+ private boolean expectTrue(String msg, StringBuffer logBuffer, boolean condition) {
+ if (!condition) {
+ logBuffer.append(msg + "\n");
+ }
+ return condition;
+ }
+
private void assertBitmapAndJpegSizeEqual(byte[] jpegData, ExifInterface exif) {
int exifWidth = exif.getAttributeInt(ExifInterface.TAG_IMAGE_WIDTH, 0);
int exifHeight = exif.getAttributeInt(ExifInterface.TAG_IMAGE_LENGTH, 0);
@@ -906,7 +1099,26 @@
assertEquals((float)latitude, latLong[0], 0.0001f);
assertEquals((float)longitude, latLong[1], 0.0001f);
assertEquals(altitude, exif.getAltitude(-1), 1);
- assertEquals(timestamp, exif.getGpsDateTime() / 1000);
+ assertEquals(timestamp, getGpsDateTimeFromJpeg(exif) / 1000);
+ }
+
+ private long getGpsDateTimeFromJpeg(ExifInterface exif) {
+ String date = exif.getAttribute(ExifInterface.TAG_GPS_DATESTAMP);
+ String time = exif.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP);
+ if (date == null || time == null) return -1;
+
+ String dateTimeString = date + ' ' + time;
+ ParsePosition pos = new ParsePosition(0);
+ try {
+ SimpleDateFormat formatter = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
+ formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
+
+ Date datetime = formatter.parse(dateTimeString, pos);
+ if (datetime == null) return -1;
+ return datetime.getTime();
+ } catch (IllegalArgumentException ex) {
+ return -1;
+ }
}
private void checkGpsDataNull(ExifInterface exif) {
@@ -1153,8 +1365,7 @@
for (int i = 0; i < ratios.size() - 1; i++) {
assertTrue(ratios.get(i) < ratios.get(i + 1));
}
- mCamera.startPreview();
- waitForPreviewDone();
+ blockingStartPreview();
// Test each zoom step.
for (int i = 0; i <= maxZoom; i++) {
@@ -1317,8 +1528,8 @@
private void testFocusDistancesByCamera(int cameraId) throws Exception {
initializeMessageLooper(cameraId);
- mCamera.startPreview();
- waitForPreviewDone();
+ blockingStartPreview();
+
Parameters parameters = mCamera.getParameters();
// Test every supported focus mode.
@@ -1786,7 +1997,7 @@
double intervalMargin = 0.9;
long lastArrivalTime = mFrames.get(mFrames.size() - 1);
double interval = arrivalTime - lastArrivalTime;
- if (LOGV) Log.v(TAG, "Frame interval=" + interval);
+ if (VERBOSE) Log.v(TAG, "Frame interval=" + interval);
try {
assertTrue("Frame interval (" + interval + "ms) is too " +
"large. mMaxFrameInterval=" +
@@ -1891,8 +2102,7 @@
// Make sure scene mode settings are consistent before preview and
// after preview.
- mCamera.startPreview();
- waitForPreviewDone();
+ blockingStartPreview();
for (int i = 0; i < supportedSceneModes.size(); i++) {
String sceneMode = supportedSceneModes.get(i);
parameters.setSceneMode(sceneMode);
@@ -2065,7 +2275,7 @@
@UiThreadTest
public void testMultiCameraRelease() throws Exception {
// Verify that multiple cameras exist, and that they can be opened at the same time
- if (LOGV) Log.v(TAG, "testMultiCameraRelease: Checking pre-conditions.");
+ if (VERBOSE) Log.v(TAG, "testMultiCameraRelease: Checking pre-conditions.");
int nCameras = Camera.getNumberOfCameras();
if (nCameras < 2) {
Log.i(TAG, "Test multi-camera release: Skipping test because only 1 camera available");
@@ -2087,11 +2297,11 @@
testCamera1.release();
// Start first camera
- if (LOGV) Log.v(TAG, "testMultiCameraRelease: Opening camera 0");
+ if (VERBOSE) Log.v(TAG, "testMultiCameraRelease: Opening camera 0");
initializeMessageLooper(0);
SimplePreviewStreamCb callback0 = new SimplePreviewStreamCb(0);
mCamera.setPreviewCallback(callback0);
- if (LOGV) Log.v(TAG, "testMultiCameraRelease: Starting preview on camera 0");
+ if (VERBOSE) Log.v(TAG, "testMultiCameraRelease: Starting preview on camera 0");
mCamera.startPreview();
// Run preview for a bit
for (int f = 0; f < 100; f++) {
@@ -2099,7 +2309,7 @@
assertTrue("testMultiCameraRelease: First camera preview timed out on frame " + f + "!",
mPreviewDone.block( WAIT_FOR_COMMAND_TO_COMPLETE));
}
- if (LOGV) Log.v(TAG, "testMultiCameraRelease: Stopping preview on camera 0");
+ if (VERBOSE) Log.v(TAG, "testMultiCameraRelease: Stopping preview on camera 0");
mCamera.stopPreview();
// Save message looper and camera to deterministically release them, instead
// of letting GC do it at some point.
@@ -2111,11 +2321,11 @@
// Start second camera without releasing the first one (will
// set mCamera and mLooper to new objects)
- if (LOGV) Log.v(TAG, "testMultiCameraRelease: Opening camera 1");
+ if (VERBOSE) Log.v(TAG, "testMultiCameraRelease: Opening camera 1");
initializeMessageLooper(1);
SimplePreviewStreamCb callback1 = new SimplePreviewStreamCb(1);
mCamera.setPreviewCallback(callback1);
- if (LOGV) Log.v(TAG, "testMultiCameraRelease: Starting preview on camera 1");
+ if (VERBOSE) Log.v(TAG, "testMultiCameraRelease: Starting preview on camera 1");
mCamera.startPreview();
// Run preview for a bit - GC of first camera instance should not impact the second's
// operation.
@@ -2125,11 +2335,11 @@
mPreviewDone.block( WAIT_FOR_COMMAND_TO_COMPLETE));
if (f == 50) {
// Release first camera mid-preview, should cause no problems
- if (LOGV) Log.v(TAG, "testMultiCameraRelease: Releasing camera 0");
+ if (VERBOSE) Log.v(TAG, "testMultiCameraRelease: Releasing camera 0");
firstCamera.release();
}
}
- if (LOGV) Log.v(TAG, "testMultiCameraRelease: Stopping preview on camera 0");
+ if (VERBOSE) Log.v(TAG, "testMultiCameraRelease: Stopping preview on camera 0");
mCamera.stopPreview();
firstLooper.quit();
@@ -2145,7 +2355,7 @@
mId = id;
}
public void onPreviewFrame(byte[] data, android.hardware.Camera camera) {
- if (LOGV) Log.v(TAG, "Preview frame callback, id " + mId + ".");
+ if (VERBOSE) Log.v(TAG, "Preview frame callback, id " + mId + ".");
mPreviewDone.open();
}
}
diff --git a/tests/tests/holo/Android.mk b/tests/tests/holo/Android.mk
index afc4e17..e21b7bd 100644
--- a/tests/tests/holo/Android.mk
+++ b/tests/tests/holo/Android.mk
@@ -20,8 +20,6 @@
# and when built explicitly put it in the data partition
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/holo/AndroidManifest.xml b/tests/tests/holo/AndroidManifest.xml
index 1c59630..34f8e72 100644
--- a/tests/tests/holo/AndroidManifest.xml
+++ b/tests/tests/holo/AndroidManifest.xml
@@ -59,8 +59,11 @@
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.holo"
- android:label="CTS tests for the Holo theme" />
+ android:label="CTS tests for the Holo theme" >
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/tests/jni/Android.mk b/tests/tests/jni/Android.mk
index 4f44e15..8b3edd2 100644
--- a/tests/tests/jni/Android.mk
+++ b/tests/tests/jni/Android.mk
@@ -18,16 +18,12 @@
LOCAL_PACKAGE_NAME := CtsJniTestCases
-
# Don't include this package in any target.
LOCAL_MODULE_TAGS := optional
# When built, explicitly put it in the data partition.
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-# All tests should include android.test.runner.
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
LOCAL_JNI_SHARED_LIBRARIES := libjnitest
diff --git a/tests/tests/jni/AndroidManifest.xml b/tests/tests/jni/AndroidManifest.xml
index c3407d1..843b322 100644
--- a/tests/tests/jni/AndroidManifest.xml
+++ b/tests/tests/jni/AndroidManifest.xml
@@ -23,9 +23,12 @@
</application>
<!-- This is a self-instrumenting test package. -->
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.jni"
- android:label="CTS tests of calling native code via JNI"/>
+ android:label="CTS tests of calling native code via JNI">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/tests/keystore/Android.mk b/tests/tests/keystore/Android.mk
index f2dae38..0f2cd03 100644
--- a/tests/tests/keystore/Android.mk
+++ b/tests/tests/keystore/Android.mk
@@ -18,8 +18,6 @@
LOCAL_MODULE_TAGS := optional
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner core-tests-support
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/keystore/AndroidManifest.xml b/tests/tests/keystore/AndroidManifest.xml
index 0ce9f09..106a0dc 100644
--- a/tests/tests/keystore/AndroidManifest.xml
+++ b/tests/tests/keystore/AndroidManifest.xml
@@ -24,9 +24,12 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.keystore"
- android:label="CTS tests of com.android.cts.keystore"/>
+ android:label="CTS tests of com.android.cts.keystore">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/tests/location/Android.mk b/tests/tests/location/Android.mk
index b76672c..2503fc7 100644
--- a/tests/tests/location/Android.mk
+++ b/tests/tests/location/Android.mk
@@ -21,8 +21,6 @@
# and when built explicitly put it in the data partition
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
LOCAL_SRC_FILES := $(call all-java-files-under, src)
@@ -31,7 +29,6 @@
LOCAL_INSTRUMENTATION_FOR := CtsTestStubs
-# uncomment when dalvik.annotation.Test* are removed or part of SDK
-#LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := current
include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/location/AndroidManifest.xml b/tests/tests/location/AndroidManifest.xml
index 147f0ba..5016f49 100644
--- a/tests/tests/location/AndroidManifest.xml
+++ b/tests/tests/location/AndroidManifest.xml
@@ -27,8 +27,11 @@
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"/>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.location"
- android:label="CTS tests of android.location"/>
+ android:label="CTS tests of android.location">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/tests/location/src/android/location/cts/LocationManagerTest.java b/tests/tests/location/src/android/location/cts/LocationManagerTest.java
index 64b15dc..ab11b3c 100644
--- a/tests/tests/location/src/android/location/cts/LocationManagerTest.java
+++ b/tests/tests/location/src/android/location/cts/LocationManagerTest.java
@@ -81,7 +81,7 @@
// remove test provider if left over from an aborted run
LocationProvider lp = mManager.getProvider(TEST_MOCK_PROVIDER_NAME);
if (lp != null) {
- removeTestProvider(TEST_MOCK_PROVIDER_NAME);
+ mManager.removeTestProvider(TEST_MOCK_PROVIDER_NAME);
}
addTestProvider(TEST_MOCK_PROVIDER_NAME);
@@ -107,7 +107,7 @@
protected void tearDown() throws Exception {
LocationProvider provider = mManager.getProvider(TEST_MOCK_PROVIDER_NAME);
if (provider != null) {
- removeTestProvider(TEST_MOCK_PROVIDER_NAME);
+ mManager.removeTestProvider(TEST_MOCK_PROVIDER_NAME);
}
if (mPendingIntent != null) {
mManager.removeProximityAlert(mPendingIntent);
@@ -138,12 +138,12 @@
// expected
}
- removeTestProvider(TEST_MOCK_PROVIDER_NAME);
+ mManager.removeTestProvider(TEST_MOCK_PROVIDER_NAME);
provider = mManager.getProvider(TEST_MOCK_PROVIDER_NAME);
assertNull(provider);
try {
- removeTestProvider(UNKNOWN_PROVIDER_NAME);
+ mManager.removeTestProvider(UNKNOWN_PROVIDER_NAME);
fail("Should throw IllegalArgumentException when no provider exists!");
} catch (IllegalArgumentException e) {
// expected
@@ -177,7 +177,7 @@
assertEquals(oldSizeAllProviders, providers.size());
assertTrue(hasTestProvider(providers));
- removeTestProvider(TEST_MOCK_PROVIDER_NAME);
+ mManager.removeTestProvider(TEST_MOCK_PROVIDER_NAME);
providers = mManager.getAllProviders();
assertEquals(oldSizeAllProviders - 1, providers.size());
assertFalse(hasTestProvider(providers));
@@ -277,8 +277,8 @@
// Find out location manager's opinion on the matter, making sure we dont' get spurious
// results from test versions of the two providers.
- forceClearTestProvider(LocationManager.GPS_PROVIDER);
- forceClearTestProvider(LocationManager.NETWORK_PROVIDER);
+ forceRemoveTestProvider(LocationManager.GPS_PROVIDER);
+ forceRemoveTestProvider(LocationManager.NETWORK_PROVIDER);
boolean lmGps = mManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
boolean lmNlp = mManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
@@ -330,20 +330,12 @@
}
/**
- * Clears the test provider. Works around b/11446702 by temporarily adding the test provider
- * so we are allowed to clear it.
+ * Ensures the test provider is removed. {@link LocationManager#removeTestProvider(String)}
+ * throws an {@link java.lang.IllegalArgumentException} if there is no such test provider,
+ * so we have to add it before we clear it.
*/
- private void forceClearTestProvider(String provider) {
+ private void forceRemoveTestProvider(String provider) {
addTestProvider(provider);
- mManager.clearTestProviderEnabled(provider);
- removeTestProvider(provider);
- }
-
- /**
- * Work around b/11446702 by clearing the test provider before removing it
- */
- private void removeTestProvider(String provider) {
- mManager.clearTestProviderEnabled(provider);
mManager.removeTestProvider(provider);
}
diff --git a/tests/tests/location2/Android.mk b/tests/tests/location2/Android.mk
index e89204d..6daca72 100644
--- a/tests/tests/location2/Android.mk
+++ b/tests/tests/location2/Android.mk
@@ -21,8 +21,6 @@
# and when built explicitly put it in the data partition
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
LOCAL_SRC_FILES := $(call all-java-files-under, src)
@@ -31,7 +29,7 @@
LOCAL_INSTRUMENTATION_FOR := CtsTestStubs
-# uncomment when dalvik.annotation.Test* are removed or part of SDK
-#LOCAL_SDK_VERSION := current
+# uncomment when Location.EXTRA_NO_GPS_LOCATION is removed
+#LOCAL_SDK_VERSION := curren
include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/location2/AndroidManifest.xml b/tests/tests/location2/AndroidManifest.xml
index 118278b..ad77b7e 100644
--- a/tests/tests/location2/AndroidManifest.xml
+++ b/tests/tests/location2/AndroidManifest.xml
@@ -27,8 +27,11 @@
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"/>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.location2"
- android:label="CTS tests of android.location"/>
+ android:label="CTS tests of android.location">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/tests/media/Android.mk b/tests/tests/media/Android.mk
index 4474b4a..936a35e 100644
--- a/tests/tests/media/Android.mk
+++ b/tests/tests/media/Android.mk
@@ -21,14 +21,18 @@
# and when built explicitly put it in the data partition
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_JAVA_LIBRARIES := android.test.runner
LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil ctstestserver ctstestrunner
+LOCAL_JNI_SHARED_LIBRARIES := libctsmediacodec_jni
+
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := CtsMediaTestCases
-# uncomment when dalvik.annotation.Test* are removed or part of SDK
+# uncomment when b/13249737 is fixed
#LOCAL_SDK_VERSION := current
+LOCAL_JAVA_LIBRARIES += android.test.runner
include $(BUILD_CTS_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/media/AndroidManifest.xml b/tests/tests/media/AndroidManifest.xml
index 90024c4..d53b2c6 100644
--- a/tests/tests/media/AndroidManifest.xml
+++ b/tests/tests/media/AndroidManifest.xml
@@ -66,9 +66,12 @@
</service>
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.media"
- android:label="CTS tests of android.media"/>
+ android:label="CTS tests of android.media">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/tests/media/libmediandkjni/Android.mk b/tests/tests/media/libmediandkjni/Android.mk
new file mode 100644
index 0000000..2d2033f
--- /dev/null
+++ b/tests/tests/media/libmediandkjni/Android.mk
@@ -0,0 +1,31 @@
+# Copyright (C) 2012 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libctsmediacodec_jni
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := native-media-jni.cpp
+
+LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
+
+LOCAL_C_INCLUDES += $(call include-path-for, mediandk)
+
+LOCAL_SHARED_LIBRARIES := libandroid libnativehelper liblog libmediandk
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/tests/tests/media/libmediandkjni/native-media-jni.cpp b/tests/tests/media/libmediandkjni/native-media-jni.cpp
new file mode 100644
index 0000000..51d2cf2
--- /dev/null
+++ b/tests/tests/media/libmediandkjni/native-media-jni.cpp
@@ -0,0 +1,662 @@
+/*
+ * 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.
+ */
+
+/* Original code copied from NDK Native-media sample code */
+
+#undef NDEBUG
+#include <assert.h>
+#include <jni.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <semaphore.h>
+
+#include <android/native_window_jni.h>
+
+// for __android_log_print(ANDROID_LOG_INFO, "YourApp", "formatted message");
+#include <android/log.h>
+#define TAG "NativeMedia"
+#define ALOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, TAG, __VA_ARGS__)
+#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
+#define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
+
+#include "ndk/NdkMediaExtractor.h"
+#include "ndk/NdkMediaCodec.h"
+#include "ndk/NdkMediaCrypto.h"
+#include "ndk/NdkMediaFormat.h"
+#include "ndk/NdkMediaMuxer.h"
+
+template <class T>
+class simplevector {
+ T *storage;
+ int capacity;
+ int numfilled;
+public:
+ simplevector() {
+ capacity = 16;
+ numfilled = 0;
+ storage = new T[capacity];
+ }
+ ~simplevector() {
+ delete[] storage;
+ }
+
+ void add(T item) {
+ if (numfilled == capacity) {
+ T *old = storage;
+ capacity *= 2;
+ storage = new T[capacity];
+ for (int i = 0; i < numfilled; i++) {
+ storage[i] = old[i];
+ }
+ delete[] old;
+ }
+ storage[numfilled] = item;
+ numfilled++;
+ }
+
+ int size() {
+ return numfilled;
+ }
+
+ T* data() {
+ return storage;
+ }
+};
+
+
+
+jobject testExtractor(AMediaExtractor *ex, JNIEnv *env) {
+
+ simplevector<int> sizes;
+ int numtracks = AMediaExtractor_getTrackCount(ex);
+ sizes.add(numtracks);
+ for (int i = 0; i < numtracks; i++) {
+ AMediaFormat *format = AMediaExtractor_getTrackFormat(ex, i);
+ const char *s = AMediaFormat_toString(format);
+ ALOGI("track %d format: %s", i, s);
+ const char *mime;
+ if (!AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &mime)) {
+ ALOGE("no mime type");
+ return NULL;
+ } else if (!strncmp(mime, "audio/", 6)) {
+ sizes.add(0);
+ int32_t val32;
+ int64_t val64;
+ AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_SAMPLE_RATE, &val32);
+ sizes.add(val32);
+ AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_CHANNEL_COUNT, &val32);
+ sizes.add(val32);
+ AMediaFormat_getInt64(format, AMEDIAFORMAT_KEY_DURATION, &val64);
+ sizes.add(val64);
+ } else if (!strncmp(mime, "video/", 6)) {
+ sizes.add(1);
+ int32_t val32;
+ int64_t val64;
+ AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_WIDTH, &val32);
+ sizes.add(val32);
+ AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_HEIGHT, &val32);
+ sizes.add(val32);
+ AMediaFormat_getInt64(format, AMEDIAFORMAT_KEY_DURATION, &val64);
+ sizes.add(val64);
+ } else {
+ ALOGE("expected audio or video mime type, got %s", mime);
+ }
+ AMediaFormat_delete(format);
+ AMediaExtractor_selectTrack(ex, i);
+ }
+ int bufsize = 1024*1024;
+ uint8_t *buf = new uint8_t[bufsize];
+ while(true) {
+ int n = AMediaExtractor_readSampleData(ex, buf, bufsize);
+ if (n < 0) {
+ break;
+ }
+ sizes.add(n);
+ sizes.add(AMediaExtractor_getSampleTrackIndex(ex));
+ sizes.add(AMediaExtractor_getSampleFlags(ex));
+ sizes.add(AMediaExtractor_getSampleTime(ex));
+ AMediaExtractor_advance(ex);
+ }
+
+ // allocate java int array for result and return it
+ int *data = sizes.data();
+ int numsamples = sizes.size();
+ jintArray ret = env->NewIntArray(numsamples);
+ jboolean isCopy;
+ jint *dst = env->GetIntArrayElements(ret, &isCopy);
+ for (int i = 0; i < numsamples; ++i) {
+ dst[i] = data[i];
+ }
+ env->ReleaseIntArrayElements(ret, dst, 0);
+
+ delete[] buf;
+ AMediaExtractor_delete(ex);
+ return ret;
+}
+
+
+// get the sample sizes for the file
+extern "C" jobject Java_android_media_cts_NativeDecoderTest_getSampleSizesNative(JNIEnv *env,
+ jclass /*clazz*/, int fd, jlong offset, jlong size)
+{
+ AMediaExtractor *ex = AMediaExtractor_new();
+ int err = AMediaExtractor_setDataSourceFd(ex, fd, offset, size);
+ if (err != 0) {
+ ALOGE("setDataSource error: %d", err);
+ return NULL;
+ }
+ return testExtractor(ex, env);
+}
+
+// get the sample sizes for the path
+extern "C" jobject Java_android_media_cts_NativeDecoderTest_getSampleSizesNativePath(JNIEnv *env,
+ jclass /*clazz*/, jstring jpath)
+{
+ AMediaExtractor *ex = AMediaExtractor_new();
+
+ const char *tmp = env->GetStringUTFChars(jpath, NULL);
+ if (tmp == NULL) { // Out of memory
+ return NULL;
+ }
+
+ int err = AMediaExtractor_setDataSource(ex, tmp);
+
+ env->ReleaseStringUTFChars(jpath, tmp);
+
+ if (err != 0) {
+ ALOGE("setDataSource error: %d", err);
+ return NULL;
+ }
+ return testExtractor(ex, env);
+}
+
+static int adler32(const uint8_t *input, int len) {
+
+ int a = 1;
+ int b = 0;
+ for (int i = 0; i < len; i++) {
+ a += input[i];
+ b += a;
+ }
+ a = a % 65521;
+ b = b % 65521;
+ int ret = b * 65536 + a;
+ ALOGV("adler %d/%d", len, ret);
+ return ret;
+}
+
+static int checksum(const uint8_t *in, int len, AMediaFormat *format) {
+ int width, stride, height;
+ if (!AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_WIDTH, &width)) {
+ width = len;
+ }
+ if (!AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_STRIDE, &stride)) {
+ stride = width;
+ }
+ if (!AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_HEIGHT, &height)) {
+ height = 1;
+ }
+ uint8_t *bb = new uint8_t[width * height];
+ for (int i = 0; i < height; i++) {
+ memcpy(bb + i * width, in + i * stride, width);
+ }
+ // bb is filled with data
+ int sum = adler32(bb, width * height);
+ delete[] bb;
+ return sum;
+}
+
+extern "C" jobject Java_android_media_cts_NativeDecoderTest_getDecodedDataNative(JNIEnv *env,
+ jclass /*clazz*/, int fd, jlong offset, jlong size) {
+ ALOGV("getDecodedDataNative");
+
+ AMediaExtractor *ex = AMediaExtractor_new();
+ int err = AMediaExtractor_setDataSourceFd(ex, fd, offset, size);
+ if (err != 0) {
+ ALOGE("setDataSource error: %d", err);
+ return NULL;
+ }
+
+ int numtracks = AMediaExtractor_getTrackCount(ex);
+
+ AMediaCodec **codec = new AMediaCodec*[numtracks];
+ AMediaFormat **format = new AMediaFormat*[numtracks];
+ bool *sawInputEOS = new bool[numtracks];
+ bool *sawOutputEOS = new bool[numtracks];
+ simplevector<int> sizes[numtracks];
+
+ ALOGV("input has %d tracks", numtracks);
+ for (int i = 0; i < numtracks; i++) {
+ AMediaFormat *format = AMediaExtractor_getTrackFormat(ex, i);
+ const char *s = AMediaFormat_toString(format);
+ ALOGI("track %d format: %s", i, s);
+ const char *mime;
+ if (!AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &mime)) {
+ ALOGE("no mime type");
+ return NULL;
+ } else if (!strncmp(mime, "audio/", 6) || !strncmp(mime, "video/", 6)) {
+ codec[i] = AMediaCodec_createDecoderByType(mime);
+ AMediaCodec_configure(codec[i], format, NULL /* surface */, NULL /* crypto */, 0);
+ AMediaCodec_start(codec[i]);
+ sawInputEOS[i] = false;
+ sawOutputEOS[i] = false;
+ } else {
+ ALOGE("expected audio or video mime type, got %s", mime);
+ return NULL;
+ }
+ AMediaFormat_delete(format);
+ AMediaExtractor_selectTrack(ex, i);
+ }
+ int eosCount = 0;
+ while(eosCount < numtracks) {
+ int t = AMediaExtractor_getSampleTrackIndex(ex);
+ if (t >=0) {
+ ssize_t bufidx = AMediaCodec_dequeueInputBuffer(codec[t], 5000);
+ ALOGV("track %d, input buffer %d", t, bufidx);
+ if (bufidx >= 0) {
+ size_t bufsize;
+ uint8_t *buf = AMediaCodec_getInputBuffer(codec[t], bufidx, &bufsize);
+ int sampleSize = AMediaExtractor_readSampleData(ex, buf, bufsize);
+ ALOGV("read %d", sampleSize);
+ if (sampleSize < 0) {
+ sampleSize = 0;
+ sawInputEOS[t] = true;
+ ALOGV("EOS");
+ //break;
+ }
+ int64_t presentationTimeUs = AMediaExtractor_getSampleTime(ex);
+
+ AMediaCodec_queueInputBuffer(codec[t], bufidx, 0, sampleSize, presentationTimeUs,
+ sawInputEOS[t] ? AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM : 0);
+ AMediaExtractor_advance(ex);
+ }
+ } else {
+ ALOGV("@@@@ no more input samples");
+ for (int tt = 0; tt < numtracks; tt++) {
+ if (!sawInputEOS[tt]) {
+ // we ran out of samples without ever signaling EOS to the codec,
+ // so do that now
+ int bufidx = AMediaCodec_dequeueInputBuffer(codec[tt], 5000);
+ if (bufidx >= 0) {
+ AMediaCodec_queueInputBuffer(codec[tt], bufidx, 0, 0, 0,
+ AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM);
+ sawInputEOS[tt] = true;
+ }
+ }
+ }
+ }
+
+ // check all codecs for available data
+ AMediaCodecBufferInfo info;
+ for (int tt = 0; tt < numtracks; tt++) {
+ if (!sawOutputEOS[tt]) {
+ int status = AMediaCodec_dequeueOutputBuffer(codec[tt], &info, 1);
+ ALOGV("dequeueoutput on track %d: %d", tt, status);
+ if (status >= 0) {
+ if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) {
+ ALOGV("EOS on track %d", tt);
+ sawOutputEOS[tt] = true;
+ eosCount++;
+ }
+ ALOGV("got decoded buffer for track %d, size %d", tt, info.size);
+ if (info.size > 0) {
+ size_t bufsize;
+ uint8_t *buf = AMediaCodec_getOutputBuffer(codec[tt], status, &bufsize);
+ int adler = checksum(buf, info.size, format[tt]);
+ sizes[tt].add(adler);
+ }
+ AMediaCodec_releaseOutputBuffer(codec[tt], status, false);
+ } else if (status == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {
+ ALOGV("output buffers changed for track %d", tt);
+ } else if (status == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
+ format[tt] = AMediaCodec_getOutputFormat(codec[tt]);
+ ALOGV("format changed for track %d: %s", tt, AMediaFormat_toString(format[tt]));
+ } else if (status == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
+ ALOGV("no output buffer right now for track %d", tt);
+ } else {
+ ALOGV("unexpected info code for track %d : %d", tt, status);
+ }
+ } else {
+ ALOGV("already at EOS on track %d", tt);
+ }
+ }
+ }
+ ALOGV("decoding loop done");
+
+ // allocate java int array for result and return it
+ int numsamples = 0;
+ for (int i = 0; i < numtracks; i++) {
+ numsamples += sizes[i].size();
+ }
+ ALOGV("checksums: %d", numsamples);
+ jintArray ret = env->NewIntArray(numsamples);
+ jboolean isCopy;
+ jint *org = env->GetIntArrayElements(ret, &isCopy);
+ jint *dst = org;
+ for (int i = 0; i < numtracks; i++) {
+ int *data = sizes[i].data();
+ int len = sizes[i].size();
+ ALOGV("copying %d", len);
+ for (int j = 0; j < len; j++) {
+ *dst++ = data[j];
+ }
+ }
+ env->ReleaseIntArrayElements(ret, org, 0);
+
+ delete[] sawInputEOS;
+ for (int i = 0; i < numtracks; i++) {
+ AMediaFormat_delete(format[i]);
+ AMediaCodec_stop(codec[i]);
+ AMediaCodec_delete(codec[i]);
+ }
+ delete[] format;
+ delete[] codec;
+ AMediaExtractor_delete(ex);
+ return ret;
+}
+
+extern "C" jboolean Java_android_media_cts_NativeDecoderTest_testPlaybackNative(JNIEnv *env,
+ jclass /*clazz*/, jobject surface, int fd, jlong offset, jlong size) {
+
+ ANativeWindow *window = ANativeWindow_fromSurface(env, surface);
+ ALOGI("@@@@ native window: %p", window);
+
+ AMediaExtractor *ex = AMediaExtractor_new();
+ int err = AMediaExtractor_setDataSourceFd(ex, fd, offset, size);
+ if (err != 0) {
+ ALOGE("setDataSource error: %d", err);
+ return false;
+ }
+
+ int numtracks = AMediaExtractor_getTrackCount(ex);
+
+ AMediaCodec *codec = NULL;
+ AMediaFormat *format = NULL;
+ bool sawInputEOS = false;
+ bool sawOutputEOS = false;
+
+ ALOGV("input has %d tracks", numtracks);
+ for (int i = 0; i < numtracks; i++) {
+ AMediaFormat *format = AMediaExtractor_getTrackFormat(ex, i);
+ const char *s = AMediaFormat_toString(format);
+ ALOGI("track %d format: %s", i, s);
+ const char *mime;
+ if (!AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &mime)) {
+ ALOGE("no mime type");
+ return false;
+ } else if (!strncmp(mime, "video/", 6)) {
+ codec = AMediaCodec_createDecoderByType(mime);
+ AMediaCodec_configure(codec, format, window, NULL, 0);
+ AMediaCodec_start(codec);
+ AMediaExtractor_selectTrack(ex, i);
+ }
+ AMediaFormat_delete(format);
+ }
+
+ while (!sawOutputEOS) {
+ ssize_t bufidx = AMediaCodec_dequeueInputBuffer(codec, 5000);
+ ALOGV("input buffer %d", bufidx);
+ if (bufidx >= 0) {
+ size_t bufsize;
+ uint8_t *buf = AMediaCodec_getInputBuffer(codec, bufidx, &bufsize);
+ int sampleSize = AMediaExtractor_readSampleData(ex, buf, bufsize);
+ ALOGV("read %d", sampleSize);
+ if (sampleSize < 0) {
+ sampleSize = 0;
+ sawInputEOS = true;
+ ALOGV("EOS");
+ }
+ int64_t presentationTimeUs = AMediaExtractor_getSampleTime(ex);
+
+ AMediaCodec_queueInputBuffer(codec, bufidx, 0, sampleSize, presentationTimeUs,
+ sawInputEOS ? AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM : 0);
+ AMediaExtractor_advance(ex);
+ }
+
+ AMediaCodecBufferInfo info;
+ int status = AMediaCodec_dequeueOutputBuffer(codec, &info, 1);
+ ALOGV("dequeueoutput returned: %d", status);
+ if (status >= 0) {
+ if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) {
+ ALOGV("output EOS");
+ sawOutputEOS = true;
+ }
+ ALOGV("got decoded buffer size %d", info.size);
+ AMediaCodec_releaseOutputBuffer(codec, status, true);
+ usleep(20000);
+ } else if (status == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {
+ ALOGV("output buffers changed");
+ } else if (status == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
+ format = AMediaCodec_getOutputFormat(codec);
+ ALOGV("format changed to: %s", AMediaFormat_toString(format));
+ } else if (status == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
+ ALOGV("no output buffer right now");
+ } else {
+ ALOGV("unexpected info code: %d", status);
+ }
+ }
+
+ AMediaCodec_stop(codec);
+ AMediaCodec_delete(codec);
+ AMediaExtractor_delete(ex);
+ return true;
+}
+
+extern "C" jboolean Java_android_media_cts_NativeDecoderTest_testMuxerNative(JNIEnv */*env*/,
+ jclass /*clazz*/, int infd, jlong inoffset, jlong insize, int outfd, jboolean webm) {
+
+
+ AMediaMuxer *muxer = AMediaMuxer_new(outfd,
+ webm ? AMEDIAMUXER_OUTPUT_FORMAT_WEBM : AMEDIAMUXER_OUTPUT_FORMAT_MPEG_4);
+
+ AMediaExtractor *ex = AMediaExtractor_new();
+ int err = AMediaExtractor_setDataSourceFd(ex, infd, inoffset, insize);
+ if (err != 0) {
+ ALOGE("setDataSource error: %d", err);
+ return false;
+ }
+
+ int numtracks = AMediaExtractor_getTrackCount(ex);
+ ALOGI("input tracks: %d", numtracks);
+ for (int i = 0; i < numtracks; i++) {
+ AMediaFormat *format = AMediaExtractor_getTrackFormat(ex, i);
+ const char *s = AMediaFormat_toString(format);
+ ALOGI("track %d format: %s", i, s);
+ const char *mime;
+ if (!AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &mime)) {
+ ALOGE("no mime type");
+ return false;
+ } else if (!strncmp(mime, "audio/", 6) || !strncmp(mime, "video/", 6)) {
+ ssize_t tidx = AMediaMuxer_addTrack(muxer, format);
+ ALOGI("track %d -> %d format %s", i, tidx, s);
+ AMediaExtractor_selectTrack(ex, i);
+ } else {
+ ALOGE("expected audio or video mime type, got %s", mime);
+ return false;
+ }
+ AMediaFormat_delete(format);
+ AMediaExtractor_selectTrack(ex, i);
+ }
+ AMediaMuxer_start(muxer);
+
+ int bufsize = 1024*1024;
+ uint8_t *buf = new uint8_t[bufsize];
+ AMediaCodecBufferInfo info;
+ while(true) {
+ int n = AMediaExtractor_readSampleData(ex, buf, bufsize);
+ if (n < 0) {
+ break;
+ }
+ info.offset = 0;
+ info.size = n;
+ info.presentationTimeUs = AMediaExtractor_getSampleTime(ex);
+ info.flags = AMediaExtractor_getSampleFlags(ex);
+
+ size_t idx = (size_t) AMediaExtractor_getSampleTrackIndex(ex);
+ AMediaMuxer_writeSampleData(muxer, idx, buf, &info);
+
+ AMediaExtractor_advance(ex);
+ }
+
+ AMediaExtractor_delete(ex);
+ AMediaMuxer_stop(muxer);
+ AMediaMuxer_delete(muxer);
+ return true;
+
+}
+
+extern "C" jboolean Java_android_media_cts_NativeDecoderTest_testFormatNative(JNIEnv * /*env*/,
+ jclass /*clazz*/) {
+ AMediaFormat* format = AMediaFormat_new();
+ if (!format) {
+ return false;
+ }
+
+ AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_BIT_RATE, 8000);
+ int32_t bitrate = 0;
+ if (!AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_BIT_RATE, &bitrate) || bitrate != 8000) {
+ ALOGE("AMediaFormat_getInt32 fail: %d", bitrate);
+ return false;
+ }
+
+ AMediaFormat_setInt64(format, AMEDIAFORMAT_KEY_DURATION, 123456789123456789ll);
+ int64_t duration = 0;
+ if (!AMediaFormat_getInt64(format, AMEDIAFORMAT_KEY_DURATION, &duration)
+ || duration != 123456789123456789ll) {
+ ALOGE("AMediaFormat_getInt64 fail: %lld", duration);
+ return false;
+ }
+
+ AMediaFormat_setFloat(format, AMEDIAFORMAT_KEY_FRAME_RATE, 25.0f);
+ float framerate = 0.0f;
+ if (!AMediaFormat_getFloat(format, AMEDIAFORMAT_KEY_FRAME_RATE, &framerate)
+ || framerate != 25.0f) {
+ ALOGE("AMediaFormat_getFloat fail: %f", framerate);
+ return false;
+ }
+
+ const char* value = "audio/mpeg";
+ AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, value);
+ const char* readback = NULL;
+ if (!AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &readback)
+ || strcmp(value, readback) || value == readback) {
+ ALOGE("AMediaFormat_getString fail");
+ return false;
+ }
+
+ uint32_t foo = 0xdeadbeef;
+ AMediaFormat_setBuffer(format, "csd-0", &foo, sizeof(foo));
+ foo = 0xabadcafe;
+ void *bytes;
+ size_t bytesize = 0;
+ if(!AMediaFormat_getBuffer(format, "csd-0", &bytes, &bytesize)
+ || bytesize != sizeof(foo) || *((uint32_t*)bytes) != 0xdeadbeef) {
+ ALOGE("AMediaFormat_getBuffer fail");
+ return false;
+ }
+
+ return true;
+}
+
+
+extern "C" jboolean Java_android_media_cts_NativeDecoderTest_testPsshNative(JNIEnv * /*env*/,
+ jclass /*clazz*/, int fd, jlong offset, jlong size) {
+
+ AMediaExtractor *ex = AMediaExtractor_new();
+ int err = AMediaExtractor_setDataSourceFd(ex, fd, offset, size);
+ if (err != 0) {
+ ALOGE("setDataSource error: %d", err);
+ return false;
+ }
+
+ PsshInfo* info = AMediaExtractor_getPsshInfo(ex);
+ if (info == NULL) {
+ ALOGI("null pssh");
+ return false;
+ }
+
+ ALOGI("pssh has %u entries", info->numentries);
+ if (info->numentries != 2) {
+ return false;
+ }
+
+ for (size_t i = 0; i < info->numentries; i++) {
+ PsshEntry *entry = &info->entries[i];
+ ALOGI("entry uuid %02x%02x..%02x%02x, data size %u",
+ entry->uuid[0],
+ entry->uuid[1],
+ entry->uuid[14],
+ entry->uuid[15],
+ entry->datalen);
+
+ AMediaCrypto *crypto = AMediaCrypto_new(entry->uuid, entry->data, entry->datalen);
+ if (crypto) {
+ ALOGI("got crypto");
+ AMediaCrypto_delete(crypto);
+ } else {
+ ALOGI("no crypto");
+ }
+ }
+ return true;
+}
+
+extern "C" jboolean Java_android_media_cts_NativeDecoderTest_testCryptoInfoNative(JNIEnv * /*env*/,
+ jclass /*clazz*/) {
+
+ size_t numsubsamples = 4;
+ uint8_t key[16] = { 1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4 };
+ uint8_t iv[16] = { 4,3,2,1,4,3,2,1,4,3,2,1,4,3,2,1 };
+ size_t clearbytes[4] = { 5, 6, 7, 8 };
+ size_t encryptedbytes[4] = { 8, 7, 6, 5 };
+
+ AMediaCodecCryptoInfo *ci =
+ AMediaCodecCryptoInfo_new(numsubsamples, key, iv, AMEDIACODECRYPTOINFO_MODE_CLEAR, clearbytes, encryptedbytes);
+
+ if (AMediaCodecCryptoInfo_getNumSubSamples(ci) != 4) {
+ ALOGE("numsubsamples mismatch");
+ return false;
+ }
+ uint8_t bytes[16];
+ AMediaCodecCryptoInfo_getKey(ci, bytes);
+ if (memcmp(key, bytes, 16) != 0) {
+ ALOGE("key mismatch");
+ return false;
+ }
+ AMediaCodecCryptoInfo_getIV(ci, bytes);
+ if (memcmp(iv, bytes, 16) != 0) {
+ ALOGE("IV mismatch");
+ return false;
+ }
+ if (AMediaCodecCryptoInfo_getMode(ci) != AMEDIACODECRYPTOINFO_MODE_CLEAR) {
+ ALOGE("mode mismatch");
+ return false;
+ }
+ size_t sizes[numsubsamples];
+ AMediaCodecCryptoInfo_getClearBytes(ci, sizes);
+ if (memcmp(clearbytes, sizes, sizeof(size_t) * numsubsamples)) {
+ ALOGE("clear size mismatch");
+ return false;
+ }
+ AMediaCodecCryptoInfo_getEncryptedBytes(ci, sizes);
+ if (memcmp(encryptedbytes, sizes, sizeof(size_t) * numsubsamples)) {
+ ALOGE("encrypted size mismatch");
+ return false;
+ }
+ return true;
+}
+
diff --git a/tests/tests/media/res/raw/big5_1.mp3 b/tests/tests/media/res/raw/big5_1.mp3
new file mode 100644
index 0000000..faa3eb4
--- /dev/null
+++ b/tests/tests/media/res/raw/big5_1.mp3
Binary files differ
diff --git a/tests/tests/media/res/raw/big5_2.mp3 b/tests/tests/media/res/raw/big5_2.mp3
new file mode 100644
index 0000000..a69da4f
--- /dev/null
+++ b/tests/tests/media/res/raw/big5_2.mp3
Binary files differ
diff --git a/tests/tests/media/res/raw/cp1251_3.mp3 b/tests/tests/media/res/raw/cp1251_3.mp3
new file mode 100644
index 0000000..179a1a5
--- /dev/null
+++ b/tests/tests/media/res/raw/cp1251_3.mp3
Binary files differ
diff --git a/tests/tests/media/res/raw/cp1251_4.mp3 b/tests/tests/media/res/raw/cp1251_4.mp3
new file mode 100644
index 0000000..3df1d32
--- /dev/null
+++ b/tests/tests/media/res/raw/cp1251_4.mp3
Binary files differ
diff --git a/tests/tests/media/res/raw/cp1251_5.mp3 b/tests/tests/media/res/raw/cp1251_5.mp3
new file mode 100644
index 0000000..46df442
--- /dev/null
+++ b/tests/tests/media/res/raw/cp1251_5.mp3
Binary files differ
diff --git a/tests/tests/media/res/raw/cp1251_6.mp3 b/tests/tests/media/res/raw/cp1251_6.mp3
new file mode 100644
index 0000000..545834d
--- /dev/null
+++ b/tests/tests/media/res/raw/cp1251_6.mp3
Binary files differ
diff --git a/tests/tests/media/res/raw/cp1251_7.mp3 b/tests/tests/media/res/raw/cp1251_7.mp3
new file mode 100644
index 0000000..d1c492b
--- /dev/null
+++ b/tests/tests/media/res/raw/cp1251_7.mp3
Binary files differ
diff --git a/tests/tests/media/res/raw/cp1251_8.mp3 b/tests/tests/media/res/raw/cp1251_8.mp3
new file mode 100644
index 0000000..17f7e31
--- /dev/null
+++ b/tests/tests/media/res/raw/cp1251_8.mp3
Binary files differ
diff --git a/tests/tests/media/res/raw/cp1251_v1.mp3 b/tests/tests/media/res/raw/cp1251_v1.mp3
new file mode 100644
index 0000000..173d970
--- /dev/null
+++ b/tests/tests/media/res/raw/cp1251_v1.mp3
Binary files differ
diff --git a/tests/tests/media/res/raw/cp1251_v1v2.mp3 b/tests/tests/media/res/raw/cp1251_v1v2.mp3
new file mode 100644
index 0000000..abffa92
--- /dev/null
+++ b/tests/tests/media/res/raw/cp1251_v1v2.mp3
Binary files differ
diff --git a/tests/tests/media/res/raw/football_qvga.yuv b/tests/tests/media/res/raw/football_qvga.yuv
new file mode 100644
index 0000000..f18f676
--- /dev/null
+++ b/tests/tests/media/res/raw/football_qvga.yuv
Binary files differ
diff --git a/tests/tests/media/res/raw/football_qvga_desc.txt b/tests/tests/media/res/raw/football_qvga_desc.txt
new file mode 100644
index 0000000..f6b44b2
--- /dev/null
+++ b/tests/tests/media/res/raw/football_qvga_desc.txt
@@ -0,0 +1,2 @@
+Football_qvga.yuv contains 3 seconds of raw 320x240 yuv420 video @ 30 fps.
+Extracted from http://media.xiph.org/video/derf/y4m/football_cif.y4m.
\ No newline at end of file
diff --git a/tests/tests/media/res/raw/gb18030_1.mp3 b/tests/tests/media/res/raw/gb18030_1.mp3
new file mode 100644
index 0000000..dc63de5
--- /dev/null
+++ b/tests/tests/media/res/raw/gb18030_1.mp3
Binary files differ
diff --git a/tests/tests/media/res/raw/gb18030_2.mp3 b/tests/tests/media/res/raw/gb18030_2.mp3
new file mode 100644
index 0000000..6109c97
--- /dev/null
+++ b/tests/tests/media/res/raw/gb18030_2.mp3
Binary files differ
diff --git a/tests/tests/media/res/raw/gb18030_3.mp3 b/tests/tests/media/res/raw/gb18030_3.mp3
new file mode 100644
index 0000000..4fcb22f
--- /dev/null
+++ b/tests/tests/media/res/raw/gb18030_3.mp3
Binary files differ
diff --git a/tests/tests/media/res/raw/gb18030_4.mp3 b/tests/tests/media/res/raw/gb18030_4.mp3
new file mode 100644
index 0000000..fedffd7
--- /dev/null
+++ b/tests/tests/media/res/raw/gb18030_4.mp3
Binary files differ
diff --git a/tests/tests/media/res/raw/gb18030_5.mp3 b/tests/tests/media/res/raw/gb18030_5.mp3
new file mode 100644
index 0000000..70f76ce
--- /dev/null
+++ b/tests/tests/media/res/raw/gb18030_5.mp3
Binary files differ
diff --git a/tests/tests/media/res/raw/gb18030_6.mp3 b/tests/tests/media/res/raw/gb18030_6.mp3
new file mode 100644
index 0000000..b4817b2
--- /dev/null
+++ b/tests/tests/media/res/raw/gb18030_6.mp3
Binary files differ
diff --git a/tests/tests/media/res/raw/gb18030_7.mp3 b/tests/tests/media/res/raw/gb18030_7.mp3
new file mode 100644
index 0000000..7932596
--- /dev/null
+++ b/tests/tests/media/res/raw/gb18030_7.mp3
Binary files differ
diff --git a/tests/tests/media/res/raw/gb18030_8.mp3 b/tests/tests/media/res/raw/gb18030_8.mp3
new file mode 100644
index 0000000..f5f54de
--- /dev/null
+++ b/tests/tests/media/res/raw/gb18030_8.mp3
Binary files differ
diff --git a/tests/tests/media/res/raw/hebrew.mp3 b/tests/tests/media/res/raw/hebrew.mp3
new file mode 100644
index 0000000..59d76d8
--- /dev/null
+++ b/tests/tests/media/res/raw/hebrew.mp3
Binary files differ
diff --git a/tests/tests/media/res/raw/hebrew2.mp3 b/tests/tests/media/res/raw/hebrew2.mp3
new file mode 100644
index 0000000..d48cad2
--- /dev/null
+++ b/tests/tests/media/res/raw/hebrew2.mp3
Binary files differ
diff --git a/tests/tests/media/res/raw/iso88591_1.ogg b/tests/tests/media/res/raw/iso88591_1.ogg
new file mode 100644
index 0000000..c20bf34
--- /dev/null
+++ b/tests/tests/media/res/raw/iso88591_1.ogg
Binary files differ
diff --git a/tests/tests/media/res/raw/iso88591_2.mp3 b/tests/tests/media/res/raw/iso88591_2.mp3
new file mode 100644
index 0000000..bcfdaad
--- /dev/null
+++ b/tests/tests/media/res/raw/iso88591_2.mp3
Binary files differ
diff --git a/tests/tests/media/res/raw/psshtest.mp4 b/tests/tests/media/res/raw/psshtest.mp4
new file mode 100644
index 0000000..98ffeb0
--- /dev/null
+++ b/tests/tests/media/res/raw/psshtest.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/shiftjis1.mp3 b/tests/tests/media/res/raw/shiftjis1.mp3
new file mode 100644
index 0000000..1c50c76
--- /dev/null
+++ b/tests/tests/media/res/raw/shiftjis1.mp3
Binary files differ
diff --git a/tests/tests/media/res/raw/shiftjis2.mp3 b/tests/tests/media/res/raw/shiftjis2.mp3
new file mode 100644
index 0000000..808c597
--- /dev/null
+++ b/tests/tests/media/res/raw/shiftjis2.mp3
Binary files differ
diff --git a/tests/tests/media/res/raw/shiftjis3.mp3 b/tests/tests/media/res/raw/shiftjis3.mp3
new file mode 100644
index 0000000..820631b
--- /dev/null
+++ b/tests/tests/media/res/raw/shiftjis3.mp3
Binary files differ
diff --git a/tests/tests/media/res/raw/shiftjis4.mp3 b/tests/tests/media/res/raw/shiftjis4.mp3
new file mode 100644
index 0000000..3fbc25e
--- /dev/null
+++ b/tests/tests/media/res/raw/shiftjis4.mp3
Binary files differ
diff --git a/tests/tests/media/res/raw/shiftjis5.mp3 b/tests/tests/media/res/raw/shiftjis5.mp3
new file mode 100644
index 0000000..90520f8
--- /dev/null
+++ b/tests/tests/media/res/raw/shiftjis5.mp3
Binary files differ
diff --git a/tests/tests/media/res/raw/shiftjis6.mp3 b/tests/tests/media/res/raw/shiftjis6.mp3
new file mode 100644
index 0000000..5310936
--- /dev/null
+++ b/tests/tests/media/res/raw/shiftjis6.mp3
Binary files differ
diff --git a/tests/tests/media/res/raw/shiftjis7.mp3 b/tests/tests/media/res/raw/shiftjis7.mp3
new file mode 100644
index 0000000..6143126
--- /dev/null
+++ b/tests/tests/media/res/raw/shiftjis7.mp3
Binary files differ
diff --git a/tests/tests/media/res/raw/shiftjis8.mp3 b/tests/tests/media/res/raw/shiftjis8.mp3
new file mode 100644
index 0000000..c45c130
--- /dev/null
+++ b/tests/tests/media/res/raw/shiftjis8.mp3
Binary files differ
diff --git a/tests/tests/media/res/raw/testmp3_3.raw b/tests/tests/media/res/raw/testmp3_3.raw
new file mode 100644
index 0000000..1d5ee19
--- /dev/null
+++ b/tests/tests/media/res/raw/testmp3_3.raw
@@ -0,0 +1 @@
+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
diff --git a/tests/tests/media/res/raw/video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz_dash.mp4 b/tests/tests/media/res/raw/video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz_dash.mp4
new file mode 100644
index 0000000..19c4e06
--- /dev/null
+++ b/tests/tests/media/res/raw/video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz_dash.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz_fragmented.mp4 b/tests/tests/media/res/raw/video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz_fragmented.mp4
new file mode 100644
index 0000000..c321586
--- /dev/null
+++ b/tests/tests/media/res/raw/video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz_fragmented.mp4
Binary files differ
diff --git a/tests/tests/media/src/android/media/cts/AdaptivePlaybackTest.java b/tests/tests/media/src/android/media/cts/AdaptivePlaybackTest.java
index 13852ae..1747afa 100644
--- a/tests/tests/media/src/android/media/cts/AdaptivePlaybackTest.java
+++ b/tests/tests/media/src/android/media/cts/AdaptivePlaybackTest.java
@@ -1275,13 +1275,11 @@
/* test if the explicitly named codec is present on the system */
if (explicitCodecName != null) {
- try {
- MediaCodec codec = MediaCodec.createByCodecName(explicitCodecName);
- if (codec != null) {
- codec.release();
- add(new Codec(explicitCodecName, null, mediaList));
- }
- } catch (Exception e) {}
+ MediaCodec codec = MediaCodec.createByCodecName(explicitCodecName);
+ if (codec != null) {
+ codec.release();
+ add(new Codec(explicitCodecName, null, mediaList));
+ }
}
} catch (Throwable t) {
Log.wtf("Constructor failed", t);
diff --git a/tests/tests/media/src/android/media/cts/AudioTrackTest.java b/tests/tests/media/src/android/media/cts/AudioTrackTest.java
index 090cde8..b076e6b 100644
--- a/tests/tests/media/src/android/media/cts/AudioTrackTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioTrackTest.java
@@ -447,10 +447,9 @@
// Playback properties
// ----------------------------------
- // Test case 1: setStereoVolume() with max volume returns SUCCESS
- public void testSetStereoVolumeMax() throws Exception {
+ // Common code for the testSetStereoVolume* and testSetVolume* tests
+ private void testSetVolumeCommon(String testName, float vol, boolean isStereo) throws Exception {
// constants for test
- final String TEST_NAME = "testSetStereoVolumeMax";
final int TEST_SR = 22050;
final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_STEREO;
final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
@@ -466,61 +465,35 @@
track.write(data, OFFSET_DEFAULT, data.length);
track.write(data, OFFSET_DEFAULT, data.length);
track.play();
- float maxVol = AudioTrack.getMaxVolume();
- assertTrue(TEST_NAME, track.setStereoVolume(maxVol, maxVol) == AudioTrack.SUCCESS);
+ if (isStereo) {
+ // TODO to really test this, do a pan instead of using same value for left and right
+ assertTrue(testName, track.setStereoVolume(vol, vol) == AudioTrack.SUCCESS);
+ } else {
+ assertTrue(testName, track.setVolume(vol) == AudioTrack.SUCCESS);
+ }
// -------- tear down --------------
track.release();
}
+ // Test case 1: setStereoVolume() with max volume returns SUCCESS
+ public void testSetStereoVolumeMax() throws Exception {
+ final String TEST_NAME = "testSetStereoVolumeMax";
+ float maxVol = AudioTrack.getMaxVolume();
+ testSetVolumeCommon(TEST_NAME, maxVol, true /*isStereo*/);
+ }
+
// Test case 2: setStereoVolume() with min volume returns SUCCESS
public void testSetStereoVolumeMin() throws Exception {
- // constants for test
final String TEST_NAME = "testSetStereoVolumeMin";
- final int TEST_SR = 22050;
- final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_STEREO;
- final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
- final int TEST_MODE = AudioTrack.MODE_STREAM;
- final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-
- // -------- initialization --------------
- int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
- AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
- 2 * minBuffSize, TEST_MODE);
- byte data[] = new byte[minBuffSize];
- // -------- test --------------
- track.write(data, OFFSET_DEFAULT, data.length);
- track.write(data, OFFSET_DEFAULT, data.length);
- track.play();
float minVol = AudioTrack.getMinVolume();
- assertTrue(TEST_NAME, track.setStereoVolume(minVol, minVol) == AudioTrack.SUCCESS);
- // -------- tear down --------------
- track.release();
+ testSetVolumeCommon(TEST_NAME, minVol, true /*isStereo*/);
}
// Test case 3: setStereoVolume() with mid volume returns SUCCESS
public void testSetStereoVolumeMid() throws Exception {
- // constants for test
final String TEST_NAME = "testSetStereoVolumeMid";
- final int TEST_SR = 22050;
- final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_STEREO;
- final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
- final int TEST_MODE = AudioTrack.MODE_STREAM;
- final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-
- // -------- initialization --------------
- int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
- AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
- 2 * minBuffSize, TEST_MODE);
- byte data[] = new byte[minBuffSize];
- // -------- test --------------
-
- track.write(data, OFFSET_DEFAULT, data.length);
- track.write(data, OFFSET_DEFAULT, data.length);
- track.play();
float midVol = (AudioTrack.getMaxVolume() - AudioTrack.getMinVolume()) / 2;
- assertTrue(TEST_NAME, track.setStereoVolume(midVol, midVol) == AudioTrack.SUCCESS);
- // -------- tear down --------------
- track.release();
+ testSetVolumeCommon(TEST_NAME, midVol, true /*isStereo*/);
}
// Test case 4: setPlaybackRate() with half the content rate returns SUCCESS
@@ -645,6 +618,27 @@
track.release();
}
+ // Test case 9: setVolume() with max volume returns SUCCESS
+ public void testSetVolumeMax() throws Exception {
+ final String TEST_NAME = "testSetVolumeMax";
+ float maxVol = AudioTrack.getMaxVolume();
+ testSetVolumeCommon(TEST_NAME, maxVol, false /*isStereo*/);
+ }
+
+ // Test case 10: setVolume() with min volume returns SUCCESS
+ public void testSetVolumeMin() throws Exception {
+ final String TEST_NAME = "testSetVolumeMin";
+ float minVol = AudioTrack.getMinVolume();
+ testSetVolumeCommon(TEST_NAME, minVol, false /*isStereo*/);
+ }
+
+ // Test case 11: setVolume() with mid volume returns SUCCESS
+ public void testSetVolumeMid() throws Exception {
+ final String TEST_NAME = "testSetVolumeMid";
+ float midVol = (AudioTrack.getMaxVolume() - AudioTrack.getMinVolume()) / 2;
+ testSetVolumeCommon(TEST_NAME, midVol, false /*isStereo*/);
+ }
+
// -----------------------------------------------------------------
// Playback progress
// ----------------------------------
diff --git a/tests/tests/media/src/android/media/cts/DecodeEditEncodeTest.java b/tests/tests/media/src/android/media/cts/DecodeEditEncodeTest.java
index 0d83647..bb430e3 100644
--- a/tests/tests/media/src/android/media/cts/DecodeEditEncodeTest.java
+++ b/tests/tests/media/src/android/media/cts/DecodeEditEncodeTest.java
@@ -149,7 +149,8 @@
/**
* Tests editing of a video file with GL.
*/
- private void videoEditTest() {
+ private void videoEditTest()
+ throws IOException {
VideoChunks sourceChunks = new VideoChunks();
if (!generateVideoFile(sourceChunks)) {
@@ -182,7 +183,8 @@
*
* @return true on success, false on "soft" failure
*/
- private boolean generateVideoFile(VideoChunks output) {
+ private boolean generateVideoFile(VideoChunks output)
+ throws IOException {
if (VERBOSE) Log.d(TAG, "generateVideoFile " + mWidth + "x" + mHeight);
MediaCodec encoder = null;
InputSurface inputSurface = null;
@@ -315,7 +317,7 @@
encoderOutputBuffers = encoder.getOutputBuffers();
if (VERBOSE) Log.d(TAG, "encoder output buffers changed");
} else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
- // not expected for an encoder
+ // expected on API 18+
MediaFormat newFormat = encoder.getOutputFormat();
if (VERBOSE) Log.d(TAG, "encoder output format changed: " + newFormat);
} else if (encoderStatus < 0) {
@@ -393,7 +395,8 @@
* for output and a Surface for input, we can avoid issues with obscure formats and can
* use a fragment shader to do transformations.
*/
- private VideoChunks editVideoFile(VideoChunks inputData) {
+ private VideoChunks editVideoFile(VideoChunks inputData)
+ throws IOException {
if (VERBOSE) Log.d(TAG, "editVideoFile " + mWidth + "x" + mHeight);
VideoChunks outputData = new VideoChunks();
MediaCodec decoder = null;
@@ -613,7 +616,8 @@
* Checks the video file to see if the contents match our expectations. We decode the
* video to a Surface and check the pixels with GL.
*/
- private void checkVideoFile(VideoChunks inputData) {
+ private void checkVideoFile(VideoChunks inputData)
+ throws IOException {
OutputSurface surface = null;
MediaCodec decoder = null;
diff --git a/tests/tests/media/src/android/media/cts/DecoderTest.java b/tests/tests/media/src/android/media/cts/DecoderTest.java
index f250c3d..0b6e0d7 100644
--- a/tests/tests/media/src/android/media/cts/DecoderTest.java
+++ b/tests/tests/media/src/android/media/cts/DecoderTest.java
@@ -33,6 +33,7 @@
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
import java.util.zip.CRC32;
public class DecoderTest extends MediaPlayerTestBase {
@@ -41,6 +42,12 @@
private static final int RESET_MODE_NONE = 0;
private static final int RESET_MODE_RECONFIGURE = 1;
private static final int RESET_MODE_FLUSH = 2;
+ private static final int RESET_MODE_EOS_FLUSH = 3;
+
+ private static final String[] CSD_KEYS = new String[] { "csd-0", "csd-1" };
+
+ private static final int CONFIG_MODE_NONE = 0;
+ private static final int CONFIG_MODE_QUEUE = 1;
private Resources mResources;
short[] mMasterBuffer;
@@ -69,6 +76,20 @@
masterFd.close();
}
+ // TODO: add similar tests for other audio and video formats
+ public void testBug11696552() throws Exception {
+ MediaCodec mMediaCodec = MediaCodec.createDecoderByType("audio/mp4a-latm");
+ MediaFormat mFormat = MediaFormat.createAudioFormat("audio/mp4a-latm", 48000, 2);
+ mFormat.setByteBuffer("csd-0", ByteBuffer.wrap( new byte [] {0x13, 0x10} ));
+ mFormat.setInteger(MediaFormat.KEY_IS_ADTS, 1);
+ mMediaCodec.configure(mFormat, null, null, 0);
+ mMediaCodec.start();
+ int index = mMediaCodec.dequeueInputBuffer(250000);
+ mMediaCodec.queueInputBuffer(index, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM );
+ MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+ mMediaCodec.dequeueOutputBuffer(info, 250000);
+ }
+
// The allowed errors in the following tests are the actual maximum measured
// errors with the standard decoders, plus 10%.
// This should allow for some variation in decoders, while still detecting
@@ -104,8 +125,257 @@
monoTest(R.raw.monotestogg);
}
+ public void testTrackSelection() throws Exception {
+ testTrackSelection(R.raw.video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz);
+ testTrackSelection(
+ R.raw.video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz_fragmented);
+ testTrackSelection(
+ R.raw.video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz_dash);
+ }
+
+ private void testTrackSelection(int resid) throws Exception {
+ AssetFileDescriptor fd1 = null;
+ try {
+ fd1 = mResources.openRawResourceFd(resid);
+ MediaExtractor ex1 = new MediaExtractor();
+ ex1.setDataSource(fd1.getFileDescriptor(), fd1.getStartOffset(), fd1.getLength());
+
+ ByteBuffer buf1 = ByteBuffer.allocate(1024*1024);
+ ArrayList<Integer> vid = new ArrayList<Integer>();
+ ArrayList<Integer> aud = new ArrayList<Integer>();
+
+ // scan the file once and build lists of audio and video samples
+ ex1.selectTrack(0);
+ ex1.selectTrack(1);
+ while(true) {
+ int n1 = ex1.readSampleData(buf1, 0);
+ if (n1 < 0) {
+ break;
+ }
+ int idx = ex1.getSampleTrackIndex();
+ if (idx == 0) {
+ vid.add(n1);
+ } else if (idx == 1) {
+ aud.add(n1);
+ } else {
+ fail("unexpected track index: " + idx);
+ }
+ ex1.advance();
+ }
+
+ // read the video track once, then rewind and do it again, and
+ // verify we get the right samples
+ ex1.release();
+ ex1 = new MediaExtractor();
+ ex1.setDataSource(fd1.getFileDescriptor(), fd1.getStartOffset(), fd1.getLength());
+ ex1.selectTrack(0);
+ for (int i = 0; i < 2; i++) {
+ ex1.seekTo(0, MediaExtractor.SEEK_TO_NEXT_SYNC);
+ int idx = 0;
+ while(true) {
+ int n1 = ex1.readSampleData(buf1, 0);
+ if (n1 < 0) {
+ assertEquals(vid.size(), idx);
+ break;
+ }
+ assertEquals(vid.get(idx++).intValue(), n1);
+ ex1.advance();
+ }
+ }
+
+ // read the audio track once, then rewind and do it again, and
+ // verify we get the right samples
+ ex1.release();
+ ex1 = new MediaExtractor();
+ ex1.setDataSource(fd1.getFileDescriptor(), fd1.getStartOffset(), fd1.getLength());
+ ex1.selectTrack(1);
+ for (int i = 0; i < 2; i++) {
+ ex1.seekTo(0, MediaExtractor.SEEK_TO_NEXT_SYNC);
+ int idx = 0;
+ while(true) {
+ int n1 = ex1.readSampleData(buf1, 0);
+ if (n1 < 0) {
+ assertEquals(aud.size(), idx);
+ break;
+ }
+ assertEquals(aud.get(idx++).intValue(), n1);
+ ex1.advance();
+ }
+ }
+
+ // read the video track first, then rewind and get the audio track instead, and
+ // verify we get the right samples
+ ex1.release();
+ ex1 = new MediaExtractor();
+ ex1.setDataSource(fd1.getFileDescriptor(), fd1.getStartOffset(), fd1.getLength());
+ for (int i = 0; i < 2; i++) {
+ ex1.selectTrack(i);
+ ex1.seekTo(0, MediaExtractor.SEEK_TO_NEXT_SYNC);
+ int idx = 0;
+ while(true) {
+ int n1 = ex1.readSampleData(buf1, 0);
+ if (i == 0) {
+ if (n1 < 0) {
+ assertEquals(vid.size(), idx);
+ break;
+ }
+ assertEquals(vid.get(idx++).intValue(), n1);
+ } else if (i == 1) {
+ if (n1 < 0) {
+ assertEquals(aud.size(), idx);
+ break;
+ }
+ assertEquals(aud.get(idx++).intValue(), n1);
+ } else {
+ fail("unexpected track index: " + idx);
+ }
+ ex1.advance();
+ }
+ ex1.unselectTrack(i);
+ }
+
+ // read the video track first, then rewind, enable the audio track in addition
+ // to the video track, and verify we get the right samples
+ ex1.release();
+ ex1 = new MediaExtractor();
+ ex1.setDataSource(fd1.getFileDescriptor(), fd1.getStartOffset(), fd1.getLength());
+ for (int i = 0; i < 2; i++) {
+ ex1.selectTrack(i);
+ ex1.seekTo(0, MediaExtractor.SEEK_TO_NEXT_SYNC);
+ int vididx = 0;
+ int audidx = 0;
+ while(true) {
+ int n1 = ex1.readSampleData(buf1, 0);
+ if (n1 < 0) {
+ // we should have read all audio and all video samples at this point
+ assertEquals(vid.size(), vididx);
+ if (i == 1) {
+ assertEquals(aud.size(), audidx);
+ }
+ break;
+ }
+ int trackidx = ex1.getSampleTrackIndex();
+ if (trackidx == 0) {
+ assertEquals(vid.get(vididx++).intValue(), n1);
+ } else if (trackidx == 1) {
+ assertEquals(aud.get(audidx++).intValue(), n1);
+ } else {
+ fail("unexpected track index: " + trackidx);
+ }
+ ex1.advance();
+ }
+ }
+
+ // read both tracks from the start, then rewind and verify we get the right
+ // samples both times
+ ex1.release();
+ ex1 = new MediaExtractor();
+ ex1.setDataSource(fd1.getFileDescriptor(), fd1.getStartOffset(), fd1.getLength());
+ for (int i = 0; i < 2; i++) {
+ ex1.selectTrack(0);
+ ex1.selectTrack(1);
+ ex1.seekTo(0, MediaExtractor.SEEK_TO_NEXT_SYNC);
+ int vididx = 0;
+ int audidx = 0;
+ while(true) {
+ int n1 = ex1.readSampleData(buf1, 0);
+ if (n1 < 0) {
+ // we should have read all audio and all video samples at this point
+ assertEquals(vid.size(), vididx);
+ assertEquals(aud.size(), audidx);
+ break;
+ }
+ int trackidx = ex1.getSampleTrackIndex();
+ if (trackidx == 0) {
+ assertEquals(vid.get(vididx++).intValue(), n1);
+ } else if (trackidx == 1) {
+ assertEquals(aud.get(audidx++).intValue(), n1);
+ } else {
+ fail("unexpected track index: " + trackidx);
+ }
+ ex1.advance();
+ }
+ }
+
+ } finally {
+ if (fd1 != null) {
+ fd1.close();
+ }
+ }
+ }
+
+ public void testDecodeFragmented() throws Exception {
+ testDecodeFragmented(R.raw.video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz,
+ R.raw.video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz_fragmented);
+ testDecodeFragmented(R.raw.video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz,
+ R.raw.video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz_dash);
+ }
+
+ private void testDecodeFragmented(int reference, int teststream) throws Exception {
+ AssetFileDescriptor fd1 = null;
+ AssetFileDescriptor fd2 = null;
+ try {
+ fd1 = mResources.openRawResourceFd(reference);
+ MediaExtractor ex1 = new MediaExtractor();
+ ex1.setDataSource(fd1.getFileDescriptor(), fd1.getStartOffset(), fd1.getLength());
+
+ fd2 = mResources.openRawResourceFd(teststream);
+ MediaExtractor ex2 = new MediaExtractor();
+ ex2.setDataSource(fd2.getFileDescriptor(), fd2.getStartOffset(), fd2.getLength());
+
+ assertEquals("different track count", ex1.getTrackCount(), ex2.getTrackCount());
+
+ ByteBuffer buf1 = ByteBuffer.allocate(1024*1024);
+ ByteBuffer buf2 = ByteBuffer.allocate(1024*1024);
+
+ for (int i = 0; i < ex1.getTrackCount(); i++) {
+ // note: this assumes the tracks are reported in the order in which they appear
+ // in the file.
+ ex1.seekTo(0, MediaExtractor.SEEK_TO_NEXT_SYNC);
+ ex1.selectTrack(i);
+ ex2.seekTo(0, MediaExtractor.SEEK_TO_NEXT_SYNC);
+ ex2.selectTrack(i);
+
+ while(true) {
+ int n1 = ex1.readSampleData(buf1, 0);
+ int n2 = ex2.readSampleData(buf2, 0);
+ assertEquals("different buffer size on track " + i, n1, n2);
+
+ if (n1 < 0) {
+ break;
+ }
+ // see bug 13008204
+ buf1.limit(n1);
+ buf2.limit(n2);
+ buf1.rewind();
+ buf2.rewind();
+
+ assertEquals("limit does not match return value on track " + i,
+ n1, buf1.limit());
+ assertEquals("limit does not match return value on track " + i,
+ n2, buf2.limit());
+
+ assertEquals("buffer data did not match on track " + i, buf1, buf2);
+
+ ex1.advance();
+ ex2.advance();
+ }
+ ex1.unselectTrack(i);
+ ex2.unselectTrack(i);
+ }
+ } finally {
+ if (fd1 != null) {
+ fd1.close();
+ }
+ if (fd2 != null) {
+ fd2.close();
+ }
+ }
+ }
+
+
private void monoTest(int res) throws Exception {
- short [] mono = decodeToMemory(res, RESET_MODE_NONE);
+ short [] mono = decodeToMemory(res, RESET_MODE_NONE, CONFIG_MODE_NONE, -1, null);
if (mono.length == 44100) {
// expected
} else if (mono.length == 88200) {
@@ -118,14 +388,11 @@
fail("wrong number of samples: " + mono.length);
}
- // we should get the same data when reconfiguring the codec
- short [] mono2 = decodeToMemory(res, RESET_MODE_RECONFIGURE);
+ short [] mono2 = decodeToMemory(res, RESET_MODE_RECONFIGURE, CONFIG_MODE_NONE, -1, null);
assertTrue(Arrays.equals(mono, mono2));
- // NOTE: coming soon
- // and when flushing it
-// short [] mono3 = decodeToMemory(res, RESET_MODE_FLUSH);
-// assertTrue(Arrays.equals(mono, mono3));
+ short [] mono3 = decodeToMemory(res, RESET_MODE_FLUSH, CONFIG_MODE_NONE, -1, null);
+ assertTrue(Arrays.equals(mono, mono3));
}
/**
@@ -135,7 +402,7 @@
*/
private void decode(int testinput, float maxerror) throws IOException {
- short [] decoded = decodeToMemory(testinput, RESET_MODE_NONE);
+ short[] decoded = decodeToMemory(testinput, RESET_MODE_NONE, CONFIG_MODE_NONE, -1, null);
assertEquals("wrong data size", mMasterBuffer.length, decoded.length);
@@ -152,22 +419,54 @@
double rmse = Math.sqrt(avgErrorSquared);
assertTrue("decoding error too big: " + rmse, rmse <= maxerror);
- short [] decoded2 = decodeToMemory(testinput, RESET_MODE_RECONFIGURE);
- assertEquals("count different with reconfigure", decoded.length, decoded2.length);
- for (int i = 0; i < decoded.length; i++) {
- assertEquals("samples don't match", decoded[i], decoded2[i]);
- }
+ int[] resetModes = new int[] { RESET_MODE_NONE, RESET_MODE_RECONFIGURE,
+ RESET_MODE_FLUSH, RESET_MODE_EOS_FLUSH };
+ int[] configModes = new int[] { CONFIG_MODE_NONE, CONFIG_MODE_QUEUE };
- // NOTE: coming soon
-// short [] decoded3 = decodeToMemory(testinput, RESET_MODE_FLUSH);
-// assertEquals("count different with flush", decoded.length, decoded3.length);
-// for (int i = 0; i < decoded.length; i++) {
-// assertEquals("samples don't match", decoded[i], decoded3[i]);
-// }
+ for (int conf : configModes) {
+ for (int reset : resetModes) {
+ if (conf == CONFIG_MODE_NONE && reset == RESET_MODE_NONE) {
+ // default case done outside of loop
+ continue;
+ }
+ if (conf == CONFIG_MODE_QUEUE && !hasAudioCsd(testinput)) {
+ continue;
+ }
+
+ String params = String.format("(using reset: %d, config: %s)", reset, conf);
+ short[] decoded2 = decodeToMemory(testinput, reset, conf, -1, null);
+ assertEquals("count different with reconfigure" + params,
+ decoded.length, decoded2.length);
+ for (int i = 0; i < decoded.length; i++) {
+ assertEquals("samples don't match" + params, decoded[i], decoded2[i]);
+ }
+ }
+ }
}
- private short[] decodeToMemory(int testinput, int resetMode) throws IOException {
+ private boolean hasAudioCsd(int testinput) throws IOException {
+ AssetFileDescriptor fd = null;
+ try {
+ fd = mResources.openRawResourceFd(testinput);
+ MediaExtractor extractor = new MediaExtractor();
+ extractor.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
+ MediaFormat format = extractor.getTrackFormat(0);
+
+ return format.containsKey(CSD_KEYS[0]);
+
+ } finally {
+ if (fd != null) {
+ fd.close();
+ }
+ }
+ }
+
+ private short[] decodeToMemory(int testinput, int resetMode, int configMode,
+ int eossample, List<Long> timestamps) throws IOException {
+
+ String localTag = TAG + "#decodeToMemory";
+ Log.v(localTag, String.format("reset = %d; config: %s", resetMode, configMode));
short [] decoded = new short[0];
int decodedIdx = 0;
@@ -188,15 +487,32 @@
String mime = format.getString(MediaFormat.KEY_MIME);
assertTrue("not an audio file", mime.startsWith("audio/"));
+ MediaFormat configFormat = format;
codec = MediaCodec.createDecoderByType(mime);
- codec.configure(format, null /* surface */, null /* crypto */, 0 /* flags */);
+ if (configMode == CONFIG_MODE_QUEUE && format.containsKey(CSD_KEYS[0])) {
+ configFormat = MediaFormat.createAudioFormat(mime,
+ format.getInteger(MediaFormat.KEY_SAMPLE_RATE),
+ format.getInteger(MediaFormat.KEY_CHANNEL_COUNT));
+
+ configFormat.setLong(MediaFormat.KEY_DURATION,
+ format.getLong(MediaFormat.KEY_DURATION));
+ String[] keys = new String[] { "max-input-size", "encoder-delay", "encoder-padding" };
+ for (String k : keys) {
+ if (format.containsKey(k)) {
+ configFormat.setInteger(k, format.getInteger(k));
+ }
+ }
+ }
+ Log.v(localTag, "configuring with " + configFormat);
+ codec.configure(configFormat, null /* surface */, null /* crypto */, 0 /* flags */);
+
codec.start();
codecInputBuffers = codec.getInputBuffers();
codecOutputBuffers = codec.getOutputBuffers();
if (resetMode == RESET_MODE_RECONFIGURE) {
codec.stop();
- codec.configure(format, null /* surface */, null /* crypto */, 0 /* flags */);
+ codec.configure(configFormat, null /* surface */, null /* crypto */, 0 /* flags */);
codec.start();
codecInputBuffers = codec.getInputBuffers();
codecOutputBuffers = codec.getOutputBuffers();
@@ -206,12 +522,17 @@
extractor.selectTrack(0);
+ if (configMode == CONFIG_MODE_QUEUE) {
+ queueConfig(codec, format);
+ }
+
// start decoding
final long kTimeOutUs = 5000;
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
boolean sawInputEOS = false;
boolean sawOutputEOS = false;
int noOutputCounter = 0;
+ int samplecounter = 0;
while (!sawOutputEOS && noOutputCounter < 50) {
noOutputCounter++;
if (!sawInputEOS) {
@@ -225,14 +546,20 @@
long presentationTimeUs = 0;
+ if (sampleSize < 0 && eossample > 0) {
+ fail("test is broken: never reached eos sample");
+ }
if (sampleSize < 0) {
Log.d(TAG, "saw input EOS.");
sawInputEOS = true;
sampleSize = 0;
} else {
+ if (samplecounter == eossample) {
+ sawInputEOS = true;
+ }
+ samplecounter++;
presentationTimeUs = extractor.getSampleTime();
}
-
codec.queueInputBuffer(
inputBufIndex,
0 /* offset */,
@@ -253,22 +580,33 @@
if (info.size > 0) {
noOutputCounter = 0;
+ if (timestamps != null) {
+ timestamps.add(info.presentationTimeUs);
+ }
}
- if (info.size > 0 && resetMode != RESET_MODE_NONE) {
+ if (info.size > 0 &&
+ resetMode != RESET_MODE_NONE && resetMode != RESET_MODE_EOS_FLUSH) {
// once we've gotten some data out of the decoder, reset and start again
if (resetMode == RESET_MODE_RECONFIGURE) {
codec.stop();
- codec.configure(format, null /* surface */, null /* crypto */,
+ codec.configure(configFormat, null /* surface */, null /* crypto */,
0 /* flags */);
codec.start();
codecInputBuffers = codec.getInputBuffers();
codecOutputBuffers = codec.getOutputBuffers();
+ if (configMode == CONFIG_MODE_QUEUE) {
+ queueConfig(codec, format);
+ }
} else /* resetMode == RESET_MODE_FLUSH */ {
codec.flush();
}
resetMode = RESET_MODE_NONE;
extractor.seekTo(0, MediaExtractor.SEEK_TO_NEXT_SYNC);
sawInputEOS = false;
+ samplecounter = 0;
+ if (timestamps != null) {
+ timestamps.clear();
+ }
continue;
}
@@ -279,15 +617,29 @@
decoded = Arrays.copyOf(decoded, decodedIdx + (info.size / 2));
}
+ buf.position(info.offset);
for (int i = 0; i < info.size; i += 2) {
- decoded[decodedIdx++] = buf.getShort(i);
+ decoded[decodedIdx++] = buf.getShort();
}
codec.releaseOutputBuffer(outputBufIndex, false /* render */);
if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
Log.d(TAG, "saw output EOS.");
- sawOutputEOS = true;
+ if (resetMode == RESET_MODE_EOS_FLUSH) {
+ resetMode = RESET_MODE_NONE;
+ codec.flush();
+ extractor.seekTo(0, MediaExtractor.SEEK_TO_NEXT_SYNC);
+ sawInputEOS = false;
+ samplecounter = 0;
+ decoded = new short[0];
+ decodedIdx = 0;
+ if (timestamps != null) {
+ timestamps.clear();
+ }
+ } else {
+ sawOutputEOS = true;
+ }
}
} else if (res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
codecOutputBuffers = codec.getOutputBuffers();
@@ -301,12 +653,106 @@
Log.d(TAG, "dequeueOutputBuffer returned " + res);
}
}
+ if (noOutputCounter >= 50) {
+ fail("decoder stopped outputing data");
+ }
codec.stop();
codec.release();
return decoded;
}
+ private void queueConfig(MediaCodec codec, MediaFormat format) {
+ for (String csdKey : CSD_KEYS) {
+ if (!format.containsKey(csdKey)) {
+ continue;
+ }
+ ByteBuffer[] codecInputBuffers = codec.getInputBuffers();
+ int inputBufIndex = codec.dequeueInputBuffer(-1);
+ if (inputBufIndex < 0) {
+ fail("failed to queue configuration buffer " + csdKey);
+ } else {
+ ByteBuffer csd = (ByteBuffer) format.getByteBuffer(csdKey).rewind();
+ Log.v(TAG + "#queueConfig", String.format("queueing %s:%s", csdKey, csd));
+ codecInputBuffers[inputBufIndex].put(csd);
+ codec.queueInputBuffer(
+ inputBufIndex,
+ 0 /* offset */,
+ csd.limit(),
+ 0 /* presentation time (us) */,
+ MediaCodec.BUFFER_FLAG_CODEC_CONFIG);
+ }
+ }
+ }
+
+ public void testDecodeWithEOSOnLastBuffer() throws Exception {
+ testDecodeWithEOSOnLastBuffer(R.raw.sinesweepm4a);
+ testDecodeWithEOSOnLastBuffer(R.raw.sinesweepmp3lame);
+ testDecodeWithEOSOnLastBuffer(R.raw.sinesweepmp3smpb);
+ testDecodeWithEOSOnLastBuffer(R.raw.sinesweepwav);
+ testDecodeWithEOSOnLastBuffer(R.raw.sinesweepflac);
+ testDecodeWithEOSOnLastBuffer(R.raw.sinesweepogg);
+ }
+
+ /* setting EOS on the last full input buffer should be equivalent to setting EOS on an empty
+ * input buffer after all the full ones. */
+ private void testDecodeWithEOSOnLastBuffer(int res) throws Exception {
+ int numsamples = countSamples(res);
+ assertTrue(numsamples != 0);
+
+ List<Long> timestamps1 = new ArrayList<Long>();
+ short[] decode1 = decodeToMemory(res, RESET_MODE_NONE, CONFIG_MODE_NONE, -1, timestamps1);
+
+ List<Long> timestamps2 = new ArrayList<Long>();
+ short[] decode2 = decodeToMemory(res, RESET_MODE_NONE, CONFIG_MODE_NONE, numsamples - 1,
+ timestamps2);
+
+ // check that the data and the timestamps are the same for EOS-on-last and EOS-after-last
+ assertEquals(decode1.length, decode2.length);
+ assertTrue(Arrays.equals(decode1, decode2));
+ assertEquals(timestamps1.size(), timestamps2.size());
+ assertTrue(timestamps1.equals(timestamps2));
+
+ // ... and that this is also true when reconfiguring the codec
+ timestamps2.clear();
+ decode2 = decodeToMemory(res, RESET_MODE_RECONFIGURE, CONFIG_MODE_NONE, -1, timestamps2);
+ assertTrue(Arrays.equals(decode1, decode2));
+ assertTrue(timestamps1.equals(timestamps2));
+ timestamps2.clear();
+ decode2 = decodeToMemory(res, RESET_MODE_RECONFIGURE, CONFIG_MODE_NONE, numsamples - 1,
+ timestamps2);
+ assertEquals(decode1.length, decode2.length);
+ assertTrue(Arrays.equals(decode1, decode2));
+ assertTrue(timestamps1.equals(timestamps2));
+
+ // ... and that this is also true when flushing the codec
+ timestamps2.clear();
+ decode2 = decodeToMemory(res, RESET_MODE_FLUSH, CONFIG_MODE_NONE, -1, timestamps2);
+ assertTrue(Arrays.equals(decode1, decode2));
+ assertTrue(timestamps1.equals(timestamps2));
+ timestamps2.clear();
+ decode2 = decodeToMemory(res, RESET_MODE_FLUSH, CONFIG_MODE_NONE, numsamples - 1,
+ timestamps2);
+ assertEquals(decode1.length, decode2.length);
+ assertTrue(Arrays.equals(decode1, decode2));
+ assertTrue(timestamps1.equals(timestamps2));
+ }
+
+ private int countSamples(int res) throws IOException {
+ AssetFileDescriptor testFd = mResources.openRawResourceFd(res);
+
+ MediaExtractor extractor = new MediaExtractor();
+ extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(),
+ testFd.getLength());
+ testFd.close();
+ extractor.selectTrack(0);
+ int numsamples = 0;
+ while (extractor.advance()) {
+ numsamples++;
+ }
+ return numsamples;
+ }
+
public void testCodecBasicH264() throws Exception {
Surface s = getActivity().getSurfaceHolder().getSurface();
int frames1 = countFrames(
@@ -472,21 +918,21 @@
// }
public void testCodecResetsMp3() throws Exception {
- testCodecReconfig(R.raw.sinesweepmp3lame, null);
+ testCodecReconfig(R.raw.sinesweepmp3lame);
// NOTE: replacing testCodecReconfig call soon
// testCodecResets(R.raw.sinesweepmp3lame, null);
}
public void testCodecResetsM4a() throws Exception {
- testCodecReconfig(R.raw.sinesweepm4a, null);
+ testCodecReconfig(R.raw.sinesweepm4a);
// NOTE: replacing testCodecReconfig call soon
// testCodecResets(R.raw.sinesweepm4a, null);
}
- private void testCodecReconfig(int video, Surface s) throws Exception {
- int frames1 = countFrames(video, RESET_MODE_NONE, -1 /* eosframe */, s);
- int frames2 = countFrames(video, RESET_MODE_RECONFIGURE, -1 /* eosframe */, s);
- assertEquals("different number of frames when using reconfigured codec", frames1, frames2);
+ private void testCodecReconfig(int audio) throws Exception {
+ int size1 = countSize(audio, RESET_MODE_NONE, -1 /* eosframe */);
+ int size2 = countSize(audio, RESET_MODE_RECONFIGURE, -1 /* eosframe */);
+ assertEquals("different output size when using reconfigured codec", size1, size2);
}
private void testCodecResets(int video, Surface s) throws Exception {
@@ -497,55 +943,146 @@
assertEquals("different number of frames when using flushed codec", frames1, frames3);
}
- private MediaCodec createDecoder(String mime) {
- if (false) {
- // change to force testing software codecs
- 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.vp8.decoder");
- } else if (mime.contains("vp9")) {
- return MediaCodec.createByCodecName("OMX.google.vp9.decoder");
+ private static MediaCodec createDecoder(String mime) {
+ try {
+ if (false) {
+ // change to force testing software codecs
+ 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.vp8.decoder");
+ } else if (mime.contains("vp9")) {
+ return MediaCodec.createByCodecName("OMX.google.vp9.decoder");
+ }
}
+ return MediaCodec.createDecoderByType(mime);
+ } catch (Exception e) {
+ return null;
}
- return MediaCodec.createDecoderByType(mime);
}
+ // for video
private int countFrames(int video, int resetMode, int eosframe, Surface s)
throws Exception {
- int numframes = 0;
-
AssetFileDescriptor testFd = mResources.openRawResourceFd(video);
+ MediaExtractor extractor = new MediaExtractor();
+ extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(),
+ testFd.getLength());
+ extractor.selectTrack(0);
- MediaExtractor extractor;
- MediaCodec codec = null;
+ int numframes = decodeWithChecks(extractor, CHECKFLAG_RETURN_OUTPUTFRAMES
+ | CHECKFLAG_COMPAREINPUTOUTPUTPTSMATCH, resetMode, s,
+ eosframe, null, null);
+
+ extractor.release();
+ testFd.close();
+ return numframes;
+ }
+
+ // for audio
+ private int countSize(int audio, int resetMode, int eosframe)
+ throws Exception {
+ AssetFileDescriptor testFd = mResources.openRawResourceFd(audio);
+ MediaExtractor extractor = new MediaExtractor();
+ extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(),
+ testFd.getLength());
+ extractor.selectTrack(0);
+
+ // fails CHECKFLAG_COMPAREINPUTOUTPUTPTSMATCH
+ int outputSize = decodeWithChecks(extractor, CHECKFLAG_RETURN_OUTPUTSIZE, resetMode, null,
+ eosframe, null, null);
+
+ extractor.release();
+ testFd.close();
+ return outputSize;
+ }
+
+ private void testEOSBehavior(int movie, int stopatsample) throws Exception {
+ testEOSBehavior(movie, new int[] {stopatsample});
+ }
+
+ private void testEOSBehavior(int movie, int[] stopAtSample) throws Exception {
+ Surface s = null;
+ AssetFileDescriptor testFd = mResources.openRawResourceFd(movie);
+ MediaExtractor extractor = new MediaExtractor();
+ extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(),
+ testFd.getLength());
+ extractor.selectTrack(0); // consider variable looping on track
+ List<Long> outputChecksums = new ArrayList<Long>();
+ List<Long> outputTimestamps = new ArrayList<Long>();
+ Arrays.sort(stopAtSample);
+ int last = stopAtSample.length - 1;
+
+ // decode reference (longest sequence to stop at + 100) and
+ // store checksums/pts in outputChecksums and outputTimestamps
+ // (will fail CHECKFLAG_COMPAREINPUTOUTPUTSAMPLEMATCH)
+ decodeWithChecks(extractor,
+ CHECKFLAG_SETCHECKSUM | CHECKFLAG_SETPTS | CHECKFLAG_COMPAREINPUTOUTPUTPTSMATCH,
+ RESET_MODE_NONE, s,
+ stopAtSample[last] + 100, outputChecksums, outputTimestamps);
+
+ // decode stopAtSample requests in reverse order (longest to
+ // shortest) and compare to reference checksums/pts in
+ // outputChecksums and outputTimestamps
+ for (int i = last; i >= 0; --i) {
+ if (true) { // reposition extractor
+ extractor.seekTo(0, MediaExtractor.SEEK_TO_NEXT_SYNC);
+ } else { // create new extractor
+ extractor.release();
+ extractor = new MediaExtractor();
+ extractor.setDataSource(testFd.getFileDescriptor(),
+ testFd.getStartOffset(), testFd.getLength());
+ extractor.selectTrack(0); // consider variable looping on track
+ }
+ decodeWithChecks(extractor,
+ CHECKFLAG_COMPARECHECKSUM | CHECKFLAG_COMPAREPTS
+ | CHECKFLAG_COMPAREINPUTOUTPUTSAMPLEMATCH
+ | CHECKFLAG_COMPAREINPUTOUTPUTPTSMATCH,
+ RESET_MODE_NONE, s,
+ stopAtSample[i], outputChecksums, outputTimestamps);
+ }
+ extractor.release();
+ testFd.close();
+ }
+
+ private static final int CHECKFLAG_SETCHECKSUM = 1 << 0;
+ private static final int CHECKFLAG_COMPARECHECKSUM = 1 << 1;
+ private static final int CHECKFLAG_SETPTS = 1 << 2;
+ private static final int CHECKFLAG_COMPAREPTS = 1 << 3;
+ private static final int CHECKFLAG_COMPAREINPUTOUTPUTSAMPLEMATCH = 1 << 4;
+ private static final int CHECKFLAG_COMPAREINPUTOUTPUTPTSMATCH = 1 << 5;
+ private static final int CHECKFLAG_RETURN_OUTPUTFRAMES = 1 << 6;
+ private static final int CHECKFLAG_RETURN_OUTPUTSIZE = 1 << 7;
+
+ /**
+ * Decodes frames with parameterized checks and return values.
+ * The integer return can be selected through the checkFlags variable.
+ */
+ private static int decodeWithChecks(MediaExtractor extractor, int checkFlags, int resetMode,
+ Surface surface, int stopAtSample,
+ List<Long> outputChecksums, List<Long> outputTimestamps)
+ throws Exception {
+ int trackIndex = extractor.getSampleTrackIndex();
+ MediaFormat format = extractor.getTrackFormat(trackIndex);
+ String mime = format.getString(MediaFormat.KEY_MIME);
+ boolean isAudio = mime.startsWith("audio/");
ByteBuffer[] codecInputBuffers;
ByteBuffer[] codecOutputBuffers;
- extractor = new MediaExtractor();
- extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(),
- testFd.getLength());
-
- MediaFormat format = extractor.getTrackFormat(0);
- String mime = format.getString(MediaFormat.KEY_MIME);
- boolean isAudio = mime.startsWith("audio/");
-
- codec = createDecoder(mime);
-
- assertNotNull("couldn't find codec", codec);
+ MediaCodec codec = createDecoder(mime);
Log.i("@@@@", "using codec: " + codec.getName());
- codec.configure(format, s /* surface */, null /* crypto */, 0 /* flags */);
+ codec.configure(format, surface, null /* crypto */, 0 /* flags */);
codec.start();
codecInputBuffers = codec.getInputBuffers();
codecOutputBuffers = codec.getOutputBuffers();
if (resetMode == RESET_MODE_RECONFIGURE) {
codec.stop();
- codec.configure(format, s /* surface */, null /* crypto */, 0 /* flags */);
+ codec.configure(format, surface, null /* crypto */, 0 /* flags */);
codec.start();
codecInputBuffers = codec.getInputBuffers();
codecOutputBuffers = codec.getOutputBuffers();
@@ -553,19 +1090,28 @@
codec.flush();
}
- Log.i("@@@@", "format: " + format);
-
- extractor.selectTrack(0);
-
- // start decoding
- final long kTimeOutUs = 5000;
+ // start decode loop
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+
+ final long kTimeOutUs = 5000; // 5ms timeout
boolean sawInputEOS = false;
boolean sawOutputEOS = false;
int deadDecoderCounter = 0;
- int samplecounter = 0;
+ int samplenum = 0;
+ int numframes = 0;
+ int outputSize = 0;
+ int width = 0;
+ int height = 0;
+ boolean dochecksum = false;
ArrayList<Long> timestamps = new ArrayList<Long>();
+ if ((checkFlags & CHECKFLAG_SETPTS) != 0) {
+ outputTimestamps.clear();
+ }
+ if ((checkFlags & CHECKFLAG_SETCHECKSUM) != 0) {
+ outputChecksums.clear();
+ }
while (!sawOutputEOS && deadDecoderCounter < 100) {
+ // handle input
if (!sawInputEOS) {
int inputBufIndex = codec.dequeueInputBuffer(kTimeOutUs);
@@ -573,418 +1119,286 @@
ByteBuffer dstBuf = codecInputBuffers[inputBufIndex];
int sampleSize =
- extractor.readSampleData(dstBuf, 0 /* offset */);
-
- long presentationTimeUs = 0;
+ extractor.readSampleData(dstBuf, 0 /* offset */);
+ long presentationTimeUs = extractor.getSampleTime();
+ boolean advanceDone = extractor.advance();
+ // int flags = extractor.getSampleFlags();
+ // Log.i("@@@@", "read sample " + samplenum + ":" +
+ // extractor.getSampleFlags()
+ // + " @ " + extractor.getSampleTime() + " size " +
+ // sampleSize);
+ assertEquals("extractor.advance() should match end of stream", sampleSize >= 0,
+ advanceDone);
if (sampleSize < 0) {
Log.d(TAG, "saw input EOS.");
sawInputEOS = true;
- sampleSize = 0;
+ assertEquals("extractor.readSampleData() must return -1 at end of stream",
+ -1, sampleSize);
+ assertEquals("extractor.getSampleTime() must return -1 at end of stream",
+ -1, presentationTimeUs);
+ sampleSize = 0; // required otherwise queueInputBuffer
+ // returns invalid.
} else {
- presentationTimeUs = extractor.getSampleTime();
- samplecounter++;
- if (samplecounter == eosframe) {
- sawInputEOS = true;
+ timestamps.add(presentationTimeUs);
+ samplenum++; // increment before comparing with stopAtSample
+ if (samplenum == stopAtSample) {
+ Log.d(TAG, "saw input EOS (stop at sample).");
+ sawInputEOS = true; // tag this sample as EOS
}
}
-
- timestamps.add(presentationTimeUs);
-
- int flags = extractor.getSampleFlags();
-
codec.queueInputBuffer(
inputBufIndex,
0 /* offset */,
sampleSize,
presentationTimeUs,
sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
-
- if (!sawInputEOS) {
- extractor.advance();
- }
+ } else {
+ assertEquals(
+ "codec.dequeueInputBuffer() unrecognized return value: " + inputBufIndex,
+ MediaCodec.INFO_TRY_AGAIN_LATER, inputBufIndex);
}
}
- int res = codec.dequeueOutputBuffer(info, kTimeOutUs);
+ // handle output
+ int outputBufIndex = codec.dequeueOutputBuffer(info, kTimeOutUs);
deadDecoderCounter++;
- if (res >= 0) {
- //Log.d(TAG, "got frame, size " + info.size + "/" + info.presentationTimeUs);
-
- // Some decoders output a 0-sized buffer at the end. Disregard those.
- if (info.size > 0) {
+ if (outputBufIndex >= 0) {
+ if (info.size > 0) { // Disregard 0-sized buffers at the end.
deadDecoderCounter = 0;
if (resetMode != RESET_MODE_NONE) {
- // once we've gotten some data out of the decoder, reset and start again
+ // once we've gotten some data out of the decoder, reset
+ // and start again
if (resetMode == RESET_MODE_RECONFIGURE) {
codec.stop();
- codec.configure(format, s /* surface */, null /* crypto */,
+ codec.configure(format, surface /* surface */, null /* crypto */,
0 /* flags */);
codec.start();
codecInputBuffers = codec.getInputBuffers();
codecOutputBuffers = codec.getOutputBuffers();
- } else /* resetMode == RESET_MODE_FLUSH */ {
+ } else if (resetMode == RESET_MODE_FLUSH) {
codec.flush();
+ } else {
+ fail("unknown resetMode: " + resetMode);
}
+ // restart at beginning, clear resetMode
resetMode = RESET_MODE_NONE;
extractor.seekTo(0, MediaExtractor.SEEK_TO_NEXT_SYNC);
sawInputEOS = false;
numframes = 0;
timestamps.clear();
+ if ((checkFlags & CHECKFLAG_SETPTS) != 0) {
+ outputTimestamps.clear();
+ }
+ if ((checkFlags & CHECKFLAG_SETCHECKSUM) != 0) {
+ outputChecksums.clear();
+ }
continue;
}
-
- if (isAudio) {
- // for audio, count the number of bytes that were decoded, not the number
- // of access units
- numframes += info.size;
- } else {
- // for video, count the number of video frames and check the timestamp
- numframes++;
- assertTrue("invalid timestamp", timestamps.remove(info.presentationTimeUs));
+ if ((checkFlags & CHECKFLAG_COMPAREPTS) != 0) {
+ assertTrue("number of frames (" + numframes
+ + ") exceeds number of reference timestamps",
+ numframes < outputTimestamps.size());
+ assertEquals("frame ts mismatch at frame " + numframes,
+ (long) outputTimestamps.get(numframes), info.presentationTimeUs);
+ } else if ((checkFlags & CHECKFLAG_SETPTS) != 0) {
+ outputTimestamps.add(info.presentationTimeUs);
}
+ if ((checkFlags & (CHECKFLAG_SETCHECKSUM | CHECKFLAG_COMPARECHECKSUM)) != 0) {
+ long sum = 0; // note: checksum is 0 if buffer format unrecognized
+ if (dochecksum) {
+ // TODO: add stride - right now just use info.size (as before)
+ //sum = checksum(codecOutputBuffers[outputBufIndex], width, height,
+ // stride);
+ ByteBuffer outputBuffer = codecOutputBuffers[outputBufIndex];
+ outputBuffer.position(info.offset);
+ sum = checksum(outputBuffer, info.size);
+ }
+ if ((checkFlags & CHECKFLAG_COMPARECHECKSUM) != 0) {
+ assertTrue("number of frames (" + numframes
+ + ") exceeds number of reference checksums",
+ numframes < outputChecksums.size());
+ Log.d(TAG, "orig checksum: " + outputChecksums.get(numframes)
+ + " new checksum: " + sum);
+ assertEquals("frame data mismatch at frame " + numframes,
+ (long) outputChecksums.get(numframes), sum);
+ } else if ((checkFlags & CHECKFLAG_SETCHECKSUM) != 0) {
+ outputChecksums.add(sum);
+ }
+ }
+ if ((checkFlags & CHECKFLAG_COMPAREINPUTOUTPUTPTSMATCH) != 0) {
+ assertTrue("output timestamp " + info.presentationTimeUs
+ + " without corresponding input timestamp"
+ , timestamps.remove(info.presentationTimeUs));
+ }
+ outputSize += info.size;
+ numframes++;
}
- int outputBufIndex = res;
+ // Log.d(TAG, "got frame, size " + info.size + "/" +
+ // info.presentationTimeUs +
+ // "/" + numframes + "/" + info.flags);
codec.releaseOutputBuffer(outputBufIndex, true /* render */);
-
if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
Log.d(TAG, "saw output EOS.");
sawOutputEOS = true;
}
- } else if (res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+ } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
codecOutputBuffers = codec.getOutputBuffers();
-
Log.d(TAG, "output buffers have changed.");
- } else if (res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+ } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
MediaFormat oformat = codec.getOutputFormat();
-
- Log.d(TAG, "output format has changed to " + oformat);
+ if (oformat.containsKey(MediaFormat.KEY_COLOR_FORMAT) &&
+ oformat.containsKey(MediaFormat.KEY_WIDTH) &&
+ oformat.containsKey(MediaFormat.KEY_HEIGHT)) {
+ int colorFormat = oformat.getInteger(MediaFormat.KEY_COLOR_FORMAT);
+ width = oformat.getInteger(MediaFormat.KEY_WIDTH);
+ height = oformat.getInteger(MediaFormat.KEY_HEIGHT);
+ dochecksum = isRecognizedFormat(colorFormat); // only checksum known raw
+ // buf formats
+ Log.d(TAG, "checksum fmt: " + colorFormat + " dim " + width + "x" + height);
+ } else {
+ dochecksum = false; // check with audio later
+ width = height = 0;
+ Log.d(TAG, "output format has changed to (unknown video) " + oformat);
+ }
} else {
- Log.d(TAG, "no output");
+ assertEquals(
+ "codec.dequeueOutputBuffer() unrecognized return index: "
+ + outputBufIndex,
+ MediaCodec.INFO_TRY_AGAIN_LATER, outputBufIndex);
}
}
-
codec.stop();
codec.release();
- testFd.close();
- return numframes;
+
+ assertTrue("last frame didn't have EOS", sawOutputEOS);
+ if ((checkFlags & CHECKFLAG_COMPAREINPUTOUTPUTSAMPLEMATCH) != 0) {
+ assertEquals("I!=O", samplenum, numframes);
+ if (stopAtSample != 0) {
+ assertEquals("did not stop with right number of frames", stopAtSample, numframes);
+ }
+ }
+ return (checkFlags & CHECKFLAG_RETURN_OUTPUTSIZE) != 0 ? outputSize :
+ (checkFlags & CHECKFLAG_RETURN_OUTPUTFRAMES) != 0 ? numframes :
+ 0;
}
public void testEOSBehaviorH264() throws Exception {
// this video has an I frame at 44
- testEOSBehavior(R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz, 44);
- testEOSBehavior(R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz, 45);
- testEOSBehavior(R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz, 55);
+ testEOSBehavior(R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz,
+ new int[] {44, 45, 55});
}
public void testEOSBehaviorH263() throws Exception {
// this video has an I frame every 12 frames.
- testEOSBehavior(R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz, 24);
- testEOSBehavior(R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz, 25);
- testEOSBehavior(R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz, 48);
- testEOSBehavior(R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz, 50);
+ testEOSBehavior(R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz,
+ new int[] {24, 25, 48, 50});
}
public void testEOSBehaviorMpeg4() throws Exception {
// this video has an I frame every 12 frames
- testEOSBehavior(R.raw.video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz, 24);
- testEOSBehavior(R.raw.video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz, 25);
- testEOSBehavior(R.raw.video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz, 48);
- testEOSBehavior(R.raw.video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz, 50);
- testEOSBehavior(R.raw.video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz, 2);
+ testEOSBehavior(R.raw.video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz,
+ new int[] {24, 25, 48, 50, 2});
}
public void testEOSBehaviorVP8() throws Exception {
// this video has an I frame at 46
- testEOSBehavior(R.raw.video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz, 46);
- testEOSBehavior(R.raw.video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz, 47);
- testEOSBehavior(R.raw.video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz, 57);
- testEOSBehavior(R.raw.video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz, 45);
+ testEOSBehavior(R.raw.video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz,
+ new int[] {46, 47, 57, 45});
}
public void testEOSBehaviorVP9() throws Exception {
// this video has an I frame at 44
- testEOSBehavior(R.raw.video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_44100hz, 44);
- testEOSBehavior(R.raw.video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_44100hz, 45);
- testEOSBehavior(R.raw.video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_44100hz, 55);
- testEOSBehavior(R.raw.video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_44100hz, 43);
- }
-
- private void testEOSBehavior(int movie, int stopatsample) throws Exception {
-
- int numframes = 0;
-
- long [] checksums = new long[stopatsample];
-
- AssetFileDescriptor testFd = mResources.openRawResourceFd(movie);
-
- MediaExtractor extractor;
- MediaCodec codec = null;
- ByteBuffer[] codecInputBuffers;
- ByteBuffer[] codecOutputBuffers;
-
- extractor = new MediaExtractor();
- extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(),
- testFd.getLength());
-
- MediaFormat format = extractor.getTrackFormat(0);
- String mime = format.getString(MediaFormat.KEY_MIME);
- boolean isAudio = mime.startsWith("audio/");
-
- codec = createDecoder(mime);
-
- assertNotNull("couldn't find codec", codec);
- Log.i("@@@@", "using codec: " + codec.getName());
- codec.configure(format, null /* surface */, null /* crypto */, 0 /* flags */);
- codec.start();
- codecInputBuffers = codec.getInputBuffers();
- codecOutputBuffers = codec.getOutputBuffers();
-
- extractor.selectTrack(0);
-
- // start decoding
- final long kTimeOutUs = 5000;
- MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
- boolean sawInputEOS = false;
- boolean sawOutputEOS = false;
- int deadDecoderCounter = 0;
- int samplenum = 0;
- boolean dochecksum = false;
- while (!sawOutputEOS && deadDecoderCounter < 100) {
- if (!sawInputEOS) {
- int inputBufIndex = codec.dequeueInputBuffer(kTimeOutUs);
-
- if (inputBufIndex >= 0) {
- ByteBuffer dstBuf = codecInputBuffers[inputBufIndex];
-
- int sampleSize =
- extractor.readSampleData(dstBuf, 0 /* offset */);
-// Log.i("@@@@", "read sample " + samplenum + ":" + extractor.getSampleFlags()
-// + " @ " + extractor.getSampleTime() + " size " + sampleSize);
- samplenum++;
-
- long presentationTimeUs = 0;
-
- if (sampleSize < 0 || samplenum >= (stopatsample + 100)) {
- Log.d(TAG, "saw input EOS.");
- sawInputEOS = true;
- sampleSize = 0;
- } else {
- presentationTimeUs = extractor.getSampleTime();
- }
-
- int flags = extractor.getSampleFlags();
-
- codec.queueInputBuffer(
- inputBufIndex,
- 0 /* offset */,
- sampleSize,
- presentationTimeUs,
- sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
-
- if (!sawInputEOS) {
- extractor.advance();
- }
- }
- }
-
- int res = codec.dequeueOutputBuffer(info, kTimeOutUs);
-
- deadDecoderCounter++;
- if (res >= 0) {
-
- // Some decoders output a 0-sized buffer at the end. Disregard those.
- if (info.size > 0) {
- deadDecoderCounter = 0;
-
- if (isAudio) {
- // for audio, count the number of bytes that were decoded, not the number
- // of access units
- numframes += info.size;
- } else {
- // for video, count the number of video frames
- long sum = dochecksum ? checksum(codecOutputBuffers[res], info.size) : 0;
- if (numframes < checksums.length) {
- checksums[numframes] = sum;
- }
- numframes++;
- }
- }
-// Log.d(TAG, "got frame, size " + info.size + "/" + info.presentationTimeUs +
-// "/" + numframes + "/" + info.flags);
-
- int outputBufIndex = res;
- codec.releaseOutputBuffer(outputBufIndex, true /* render */);
-
- if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
- Log.d(TAG, "saw output EOS.");
- sawOutputEOS = true;
- }
- } else if (res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
- codecOutputBuffers = codec.getOutputBuffers();
-
- Log.d(TAG, "output buffers have changed.");
- } else if (res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
- MediaFormat oformat = codec.getOutputFormat();
- int colorFormat = oformat.getInteger(MediaFormat.KEY_COLOR_FORMAT);
- dochecksum = isRecognizedFormat(colorFormat);
- Log.d(TAG, "output format has changed to " + oformat);
- } else {
- Log.d(TAG, "no output");
- }
- }
-
- codec.stop();
- codec.release();
- extractor.release();
-
-
- // We now have checksums for every frame.
- // Now decode again, but signal EOS right before an index frame, and ensure the frames
- // prior to that are the same.
-
- extractor = new MediaExtractor();
- extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(),
- testFd.getLength());
-
- codec = createDecoder(mime);
- codec.configure(format, null /* surface */, null /* crypto */, 0 /* flags */);
- codec.start();
- codecInputBuffers = codec.getInputBuffers();
- codecOutputBuffers = codec.getOutputBuffers();
-
- extractor.selectTrack(0);
-
- // start decoding
- info = new MediaCodec.BufferInfo();
- sawInputEOS = false;
- sawOutputEOS = false;
- deadDecoderCounter = 0;
- samplenum = 0;
- numframes = 0;
- dochecksum = false;
- while (!sawOutputEOS && deadDecoderCounter < 100) {
- if (!sawInputEOS) {
- int inputBufIndex = codec.dequeueInputBuffer(kTimeOutUs);
-
- if (inputBufIndex >= 0) {
- ByteBuffer dstBuf = codecInputBuffers[inputBufIndex];
-
- int sampleSize =
- extractor.readSampleData(dstBuf, 0 /* offset */);
-// Log.i("@@@@", "read sample " + samplenum + ":" + extractor.getSampleFlags()
-// + " @ " + extractor.getSampleTime() + " size " + sampleSize);
- samplenum++;
-
- long presentationTimeUs = extractor.getSampleTime();
-
- if (sampleSize < 0 || samplenum >= stopatsample) {
- Log.d(TAG, "saw input EOS.");
- sawInputEOS = true;
- if (sampleSize < 0) {
- sampleSize = 0;
- }
- }
-
- int flags = extractor.getSampleFlags();
-
- codec.queueInputBuffer(
- inputBufIndex,
- 0 /* offset */,
- sampleSize,
- presentationTimeUs,
- sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
-
- if (!sawInputEOS) {
- extractor.advance();
- }
- }
- }
-
- int res = codec.dequeueOutputBuffer(info, kTimeOutUs);
-
- deadDecoderCounter++;
- if (res >= 0) {
-
- // Some decoders output a 0-sized buffer at the end. Disregard those.
- if (info.size > 0) {
- deadDecoderCounter = 0;
-
- if (isAudio) {
- // for audio, count the number of bytes that were decoded, not the number
- // of access units
- numframes += info.size;
- } else {
- // for video, count the number of video frames
- long sum = dochecksum ? checksum(codecOutputBuffers[res], info.size) : 0;
- if (numframes < checksums.length) {
- assertEquals("frame data mismatch at frame " + numframes,
- checksums[numframes], sum);
- }
- numframes++;
- }
- }
-// Log.d(TAG, "got frame, size " + info.size + "/" + info.presentationTimeUs +
-// "/" + numframes + "/" + info.flags);
-
- int outputBufIndex = res;
- codec.releaseOutputBuffer(outputBufIndex, true /* render */);
-
- if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
- Log.d(TAG, "saw output EOS.");
- sawOutputEOS = true;
- }
- } else if (res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
- codecOutputBuffers = codec.getOutputBuffers();
-
- Log.d(TAG, "output buffers have changed.");
- } else if (res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
- MediaFormat oformat = codec.getOutputFormat();
- int colorFormat = oformat.getInteger(MediaFormat.KEY_COLOR_FORMAT);
- dochecksum = isRecognizedFormat(colorFormat);
- Log.d(TAG, "output format has changed to " + oformat);
- } else {
- Log.d(TAG, "no output");
- }
- }
-
- codec.stop();
- codec.release();
- extractor.release();
-
- assertEquals("I!=O", samplenum, numframes);
- assertTrue("last frame didn't have EOS", sawOutputEOS);
- assertEquals(stopatsample, numframes);
-
- testFd.close();
+ testEOSBehavior(R.raw.video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_44100hz,
+ new int[] {44, 45, 55, 43});
}
/* from EncodeDecodeTest */
private static boolean isRecognizedFormat(int colorFormat) {
+ // Log.d(TAG, "color format: " + String.format("0x%08x", colorFormat));
switch (colorFormat) {
- // these are the formats we know how to handle for this test
+ // these are the formats we know how to handle for this test
case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar:
case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar:
case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar:
case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar:
case MediaCodecInfo.CodecCapabilities.COLOR_TI_FormatYUV420PackedSemiPlanar:
+ case MediaCodecInfo.CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar:
+ /*
+ * TODO: Check newer formats or ignore.
+ * OMX_SEC_COLOR_FormatNV12Tiled = 0x7FC00002
+ * OMX_QCOM_COLOR_FormatYUV420PackedSemiPlanar64x32Tile2m8ka = 0x7FA30C03: N4/N7_2
+ * OMX_QCOM_COLOR_FormatYUV420PackedSemiPlanar32m = 0x7FA30C04: N5
+ */
return true;
default:
return false;
}
}
- private long checksum(ByteBuffer buf, int size) {
- assertTrue(size != 0);
- assertTrue(size <= buf.capacity());
+ private static long checksum(ByteBuffer buf, int size) {
+ int cap = buf.capacity();
+ assertTrue("checksum() params are invalid: size = " + size + " cap = " + cap,
+ size > 0 && size <= cap);
CRC32 crc = new CRC32();
- int pos = buf.position();
- buf.rewind();
- for (int i = 0; i < size; i++) {
- crc.update(buf.get());
+ if (buf.hasArray()) {
+ crc.update(buf.array(), buf.position() + buf.arrayOffset(), size);
+ } else {
+ int pos = buf.position();
+ final int rdsize = Math.min(4096, size);
+ byte bb[] = new byte[rdsize];
+ int chk;
+ for (int i = 0; i < size; i += chk) {
+ chk = Math.min(rdsize, size - i);
+ buf.get(bb, 0, chk);
+ crc.update(bb, 0, chk);
+ }
+ buf.position(pos);
}
- buf.position(pos);
+ return crc.getValue();
+ }
+
+ private static long checksum(ByteBuffer buf, int width, int height, int stride) {
+ int cap = buf.capacity();
+ assertTrue("checksum() params are invalid: w x h , s = "
+ + width + " x " + height + " , " + stride + " cap = " + cap,
+ width > 0 && width <= stride && height > 0 && height * stride <= cap);
+ // YUV 4:2:0 should generally have a data storage height 1.5x greater
+ // than the declared image height, representing the UV planes.
+ //
+ // We only check Y frame for now. Somewhat unknown with tiling effects.
+ //
+ //long tm = System.nanoTime();
+ final int lineinterval = 1; // line sampling frequency
+ CRC32 crc = new CRC32();
+ if (buf.hasArray()) {
+ byte b[] = buf.array();
+ int offs = buf.arrayOffset();
+ for (int i = 0; i < height; i += lineinterval) {
+ crc.update(b, i * stride + offs, width);
+ }
+ } else { // almost always ends up here due to direct buffers
+ int pos = buf.position();
+ if (true) { // this {} is 80x times faster than else {} below.
+ byte[] bb = new byte[width]; // local line buffer
+ for (int i = 0; i < height; i += lineinterval) {
+ buf.position(i * stride);
+ buf.get(bb, 0, width);
+ crc.update(bb, 0, width);
+ }
+ } else {
+ for (int i = 0; i < height; i += lineinterval) {
+ buf.position(i * stride);
+ for (int j = 0; j < width; ++j) {
+ crc.update(buf.get());
+ }
+ }
+ }
+ buf.position(pos);
+ }
+ //tm = System.nanoTime() - tm;
+ //Log.d(TAG, "checksum time " + tm);
return crc.getValue();
}
@@ -1017,6 +1431,8 @@
assertTrue("not an audio file", mime.startsWith("audio/"));
codec = MediaCodec.createDecoderByType(mime);
+ assertNotNull("couldn't find codec " + mime, codec);
+
codec.configure(format, null /* surface */, null /* crypto */, 0 /* flags */);
codec.start();
codecInputBuffers = codec.getInputBuffers();
@@ -1073,8 +1489,9 @@
int outputBufIndex = res;
ByteBuffer buf = codecOutputBuffers[outputBufIndex];
+ buf.position(info.offset);
for (int i = 0; i < info.size; i += 2) {
- short sample = buf.getShort(i);
+ short sample = buf.getShort();
if (maxvalue < sample) {
maxvalue = sample;
}
diff --git a/tests/tests/media/src/android/media/cts/EncodeDecodeTest.java b/tests/tests/media/src/android/media/cts/EncodeDecodeTest.java
index 027e00d..5e967eb 100644
--- a/tests/tests/media/src/android/media/cts/EncodeDecodeTest.java
+++ b/tests/tests/media/src/android/media/cts/EncodeDecodeTest.java
@@ -593,7 +593,7 @@
encoderOutputBuffers = encoder.getOutputBuffers();
if (VERBOSE) Log.d(TAG, "encoder output buffers changed");
} else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
- // not expected for an encoder
+ // expected on API 18+
MediaFormat newFormat = encoder.getOutputFormat();
if (VERBOSE) Log.d(TAG, "encoder output format changed: " + newFormat);
} else if (encoderStatus < 0) {
@@ -882,7 +882,7 @@
encoderOutputBuffers = encoder.getOutputBuffers();
if (VERBOSE) Log.d(TAG, "encoder output buffers changed");
} else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
- // not expected for an encoder
+ // expected on API 18+
MediaFormat newFormat = encoder.getOutputFormat();
if (VERBOSE) Log.d(TAG, "encoder output format changed: " + newFormat);
} else if (encoderStatus < 0) {
@@ -1061,16 +1061,17 @@
x += cropLeft;
int testY, testU, testV;
+ int off = frameData.position();
if (semiPlanar) {
// Galaxy Nexus uses OMX_TI_COLOR_FormatYUV420PackedSemiPlanar
- testY = frameData.get(y * width + x) & 0xff;
- testU = frameData.get(width*height + 2*(y/2) * halfWidth + 2*(x/2)) & 0xff;
- testV = frameData.get(width*height + 2*(y/2) * halfWidth + 2*(x/2) + 1) & 0xff;
+ testY = frameData.get(off + y * width + x) & 0xff;
+ testU = frameData.get(off + width*height + 2*(y/2) * halfWidth + 2*(x/2)) & 0xff;
+ testV = frameData.get(off + width*height + 2*(y/2) * halfWidth + 2*(x/2) + 1) & 0xff;
} else {
// Nexus 10, Nexus 7 use COLOR_FormatYUV420Planar
- testY = frameData.get(y * width + x) & 0xff;
- testU = frameData.get(width*height + (y/2) * halfWidth + (x/2)) & 0xff;
- testV = frameData.get(width*height + halfWidth * (height / 2) +
+ testY = frameData.get(off + y * width + x) & 0xff;
+ testU = frameData.get(off + width*height + (y/2) * halfWidth + (x/2)) & 0xff;
+ testV = frameData.get(off + width*height + halfWidth * (height / 2) +
(y/2) * halfWidth + (x/2)) & 0xff;
}
diff --git a/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayTest.java b/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayTest.java
index 872b905..d9ef023 100755
--- a/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayTest.java
+++ b/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayTest.java
@@ -220,7 +220,7 @@
/**
* Prepares the encoder, decoder, and virtual display.
*/
- private void encodeVirtualDisplayTest() {
+ private void encodeVirtualDisplayTest() throws IOException {
MediaCodec encoder = null;
MediaCodec decoder = null;
OutputSurface outputSurface = null;
diff --git a/tests/tests/media/src/android/media/cts/EncoderTest.java b/tests/tests/media/src/android/media/cts/EncoderTest.java
index e9d0b5f..c2e59d4 100644
--- a/tests/tests/media/src/android/media/cts/EncoderTest.java
+++ b/tests/tests/media/src/android/media/cts/EncoderTest.java
@@ -187,8 +187,13 @@
}
private void testEncoder(String componentName, MediaFormat format) {
- MediaCodec codec = MediaCodec.createByCodecName(componentName);
-
+ MediaCodec codec;
+ try {
+ codec = MediaCodec.createByCodecName(componentName);
+ } catch (Exception e) {
+ fail("codec '" + componentName + "' failed construction.");
+ return; /* does not get here, but avoids warning */
+ }
try {
codec.configure(
format,
@@ -196,9 +201,7 @@
null /* crypto */,
MediaCodec.CONFIGURE_FLAG_ENCODE);
} catch (IllegalStateException e) {
- Log.e(TAG, "codec '" + componentName + "' failed configuration.");
-
- assertTrue("codec '" + componentName + "' failed configuration.", false);
+ fail("codec '" + componentName + "' failed configuration.");
}
codec.start();
diff --git a/tests/tests/media/src/android/media/cts/ExtractDecodeEditEncodeMuxTest.java b/tests/tests/media/src/android/media/cts/ExtractDecodeEditEncodeMuxTest.java
index 40b4949..43b769a 100644
--- a/tests/tests/media/src/android/media/cts/ExtractDecodeEditEncodeMuxTest.java
+++ b/tests/tests/media/src/android/media/cts/ExtractDecodeEditEncodeMuxTest.java
@@ -517,7 +517,8 @@
* @param inputFormat the format of the stream to decode
* @param surface into which to decode the frames
*/
- private MediaCodec createVideoDecoder(MediaFormat inputFormat, Surface surface) {
+ private MediaCodec createVideoDecoder(MediaFormat inputFormat, Surface surface)
+ throws IOException {
MediaCodec decoder = MediaCodec.createDecoderByType(getMimeTypeFor(inputFormat));
decoder.configure(inputFormat, surface, null, 0);
decoder.start();
@@ -537,7 +538,8 @@
private MediaCodec createVideoEncoder(
MediaCodecInfo codecInfo,
MediaFormat format,
- AtomicReference<Surface> surfaceReference) {
+ AtomicReference<Surface> surfaceReference)
+ throws IOException {
MediaCodec encoder = MediaCodec.createByCodecName(codecInfo.getName());
encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
// Must be called before start() is.
@@ -551,7 +553,8 @@
*
* @param inputFormat the format of the stream to decode
*/
- private MediaCodec createAudioDecoder(MediaFormat inputFormat) {
+ private MediaCodec createAudioDecoder(MediaFormat inputFormat)
+ throws IOException {
MediaCodec decoder = MediaCodec.createDecoderByType(getMimeTypeFor(inputFormat));
decoder.configure(inputFormat, null, null, 0);
decoder.start();
@@ -564,7 +567,8 @@
* @param codecInfo of the codec to use
* @param format of the stream to be produced
*/
- private MediaCodec createAudioEncoder(MediaCodecInfo codecInfo, MediaFormat format) {
+ private MediaCodec createAudioEncoder(MediaCodecInfo codecInfo, MediaFormat format)
+ throws IOException {
MediaCodec encoder = MediaCodec.createByCodecName(codecInfo.getName());
encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
encoder.start();
diff --git a/tests/tests/media/src/android/media/cts/ImageReaderDecoderTest.java b/tests/tests/media/src/android/media/cts/ImageReaderDecoderTest.java
new file mode 100644
index 0000000..f115b63
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/ImageReaderDecoderTest.java
@@ -0,0 +1,572 @@
+/*
+ * Copyright 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 android.media.cts;
+
+import com.android.cts.media.R;
+
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.content.res.Resources;
+import android.graphics.ImageFormat;
+import android.media.Image;
+import android.media.Image.Plane;
+import android.media.ImageReader;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecList;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.test.AndroidTestCase;
+import android.util.Log;
+import android.view.Surface;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Basic test for ImageReader APIs.
+ * <p>
+ * It uses MediaCodec to decode a short video stream, send the video frames to
+ * the surface provided by ImageReader. Then compare if output buffers of the
+ * ImageReader matches the output buffers of the MediaCodec. The video format
+ * used here is AVC although the compression format doesn't matter for this
+ * test. For decoder test, hw and sw decoders are tested,
+ * </p>
+ */
+public class ImageReaderDecoderTest extends AndroidTestCase {
+ private static final String TAG = "ImageReaderDecoderTest";
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final long DEFAULT_TIMEOUT_US = 10000;
+ private static final long WAIT_FOR_IMAGE_TIMEOUT_MS = 1000;
+ private static final String DEBUG_FILE_NAME_BASE = "/sdcard/";
+ private static final int NUM_FRAME_DECODED = 100;
+ private static final int MAX_NUM_IMAGES = 3;
+
+ 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;
+ private Handler mHandler;
+ private ImageListener mImageListener;
+
+ @Override
+ public void setContext(Context context) {
+ super.setContext(context);
+ mResources = mContext.getResources();
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mHandlerThread = new HandlerThread(TAG);
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+ mImageListener = new ImageListener();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ mHandlerThread.quitSafely();
+ 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();
+ }
+ }
+
+ /**
+ * Test ImageReader with 480x360 sw AVC decoding for flexible yuv format, which is mandatory
+ * to be supported by sw decoder.
+ */
+ public void testSwAVCDecode360pForFlexibleYuv() 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 */ false);
+ } finally {
+ closeImageReader();
+ }
+ }
+
+ private static class ImageListener implements ImageReader.OnImageAvailableListener {
+ private final LinkedBlockingQueue<Image> mQueue =
+ new LinkedBlockingQueue<Image>();
+
+ @Override
+ public void onImageAvailable(ImageReader reader) {
+ try {
+ mQueue.put(reader.acquireNextImage());
+ } catch (InterruptedException e) {
+ throw new UnsupportedOperationException(
+ "Can't handle InterruptedException in onImageAvailable");
+ }
+ }
+
+ /**
+ * Get an image from the image reader.
+ *
+ * @param timeout Timeout value for the wait.
+ * @return The image from the image reader.
+ */
+ public Image getImage(long timeout) throws InterruptedException {
+ Image image = mQueue.poll(timeout, TimeUnit.MILLISECONDS);
+ assertNotNull("Wait for an image timed out in " + timeout + "ms", image);
+ return image;
+ }
+ }
+
+ 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;
+
+ AssetFileDescriptor vidFD = mResources.openRawResourceFd(video);
+
+ extractor = new MediaExtractor();
+ extractor.setDataSource(vidFD.getFileDescriptor(), vidFD.getStartOffset(),
+ vidFD.getLength());
+
+ MediaFormat mediaFmt = extractor.getTrackFormat(0);
+ 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 {
+ ByteBuffer[] decoderInputBuffers;
+ ByteBuffer[] decoderOutputBuffers;
+ // Get decoder output ImageFormat, will be used to create ImageReader
+ int codecImageFormat = getImageFormatFromCodecType(mime);
+ assertEquals("Codec image format should match image reader format",
+ imageFormat, codecImageFormat);
+ 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);
+ decoder.start();
+ decoderInputBuffers = decoder.getInputBuffers();
+ decoderOutputBuffers = decoder.getOutputBuffers();
+ extractor.selectTrack(0);
+
+ // Start decoding and get Image, only test the first NUM_FRAME_DECODED frames.
+ MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+ boolean sawInputEOS = false;
+ boolean sawOutputEOS = false;
+ int outputFrameCount = 0;
+ while (!sawOutputEOS && outputFrameCount < NUM_FRAME_DECODED) {
+ if (VERBOSE) Log.v(TAG, "loop:" + outputFrameCount);
+ // Feed input frame.
+ if (!sawInputEOS) {
+ int inputBufIndex = decoder.dequeueInputBuffer(DEFAULT_TIMEOUT_US);
+ if (inputBufIndex >= 0) {
+ ByteBuffer dstBuf = decoderInputBuffers[inputBufIndex];
+ int sampleSize =
+ extractor.readSampleData(dstBuf, 0 /* offset */);
+
+ if (VERBOSE) Log.v(TAG, "queue a input buffer, idx/size: "
+ + inputBufIndex + "/" + sampleSize);
+
+ long presentationTimeUs = 0;
+
+ if (sampleSize < 0) {
+ if (VERBOSE) Log.v(TAG, "saw input EOS.");
+ sawInputEOS = true;
+ sampleSize = 0;
+ } else {
+ presentationTimeUs = extractor.getSampleTime();
+ }
+
+ decoder.queueInputBuffer(
+ inputBufIndex,
+ 0 /* offset */,
+ sampleSize,
+ presentationTimeUs,
+ sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
+
+ if (!sawInputEOS) {
+ extractor.advance();
+ }
+ }
+ }
+
+ // Get output frame
+ int res = decoder.dequeueOutputBuffer(info, DEFAULT_TIMEOUT_US);
+ if (VERBOSE) Log.v(TAG, "got a buffer: " + info.size + "/" + res);
+ if (res == MediaCodec.INFO_TRY_AGAIN_LATER) {
+ // no output available yet
+ if (VERBOSE) Log.v(TAG, "no output frame available");
+ } else if (res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+ // decoder output buffers changed, need update.
+ if (VERBOSE) Log.v(TAG, "decoder output buffers changed");
+ decoderOutputBuffers = decoder.getOutputBuffers();
+ } else if (res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+ // this happens before the first frame is returned.
+ MediaFormat outFormat = decoder.getOutputFormat();
+ if (VERBOSE) Log.v(TAG, "decoder output format changed: " + outFormat);
+ } else if (res < 0) {
+ // Should be decoding error.
+ fail("unexpected result from deocder.dequeueOutputBuffer: " + res);
+ } else {
+ // 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();
+
+ //Verify
+ String fileName = DEBUG_FILE_NAME_BASE + width + "x" + height + "_"
+ + outputFrameCount + ".yuv";
+ 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());
+ }
+ image.close();
+ }
+ }
+ }
+ }
+
+ private int getImageFormatFromCodecType(String mimeType) {
+ // TODO: Need pick a codec first, then get the codec info, will revisit for future.
+ MediaCodecInfo codecInfo = getCodecInfoByType(mimeType);
+ if (VERBOSE) Log.v(TAG, "found decoder: " + codecInfo.getName());
+
+ int colorFormat = selectDecoderOutputColorFormat(codecInfo, mimeType);
+ if (VERBOSE) Log.v(TAG, "found decoder output color format: " + colorFormat);
+ switch (colorFormat) {
+ case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar:
+ // TODO: This is fishy, OMX YUV420P is not identical as YV12, U and V planes are
+ // swapped actually. It should give YV12 if producer is setup first, that is, after
+ // Configuring the Surface (provided by ImageReader object) into codec, but this
+ // is Chicken-egg issue, do the translation on behalf of driver here:)
+ return ImageFormat.YV12;
+ case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar:
+ // same as above.
+ return ImageFormat.NV21;
+ default:
+ return colorFormat;
+ }
+ }
+
+ private static MediaCodecInfo getCodecInfoByType(String mimeType) {
+ int numCodecs = MediaCodecList.getCodecCount();
+ for (int i = 0; i < numCodecs; i++) {
+ MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
+
+ if (codecInfo.isEncoder()) {
+ continue;
+ }
+
+ String[] types = codecInfo.getSupportedTypes();
+ for (int j = 0; j < types.length; j++) {
+ if (types[j].equalsIgnoreCase(mimeType)) {
+ return codecInfo;
+ }
+ }
+ }
+ return null;
+ }
+
+ private static int selectDecoderOutputColorFormat(MediaCodecInfo codecInfo, String mimeType) {
+ MediaCodecInfo.CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(mimeType);
+ for (int i = 0; i < capabilities.colorFormats.length; i++) {
+ int colorFormat = capabilities.colorFormats[i];
+ if (isRecognizedFormat(colorFormat)) {
+ return colorFormat;
+ }
+ }
+ fail("couldn't find a good color format for " + codecInfo.getName() + " / " + mimeType);
+ return 0; // not reached
+ }
+
+ // Need make this function simple, may be merge into above functions.
+ private static boolean isRecognizedFormat(int colorFormat) {
+ if (VERBOSE) Log.v(TAG, "colorformat: " + colorFormat);
+ switch (colorFormat) {
+ // these are the formats we know how to handle for this test
+ case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar:
+ case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar:
+ case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar:
+ case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar:
+ case MediaCodecInfo.CodecCapabilities.COLOR_TI_FormatYUV420PackedSemiPlanar:
+ case ImageFormat.YUV_420_888:
+ return true;
+ default:
+ 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.
+ *
+ * @param image The image to be validated.
+ * @param width The image width.
+ * @param height The image height.
+ * @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) {
+ 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());
+
+ 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);
+ }
+
+ private static void validateYuvData(byte[] yuvData, int width, int height, int format,
+ long ts, String fileName) {
+
+ assertTrue("YUV format must be one of the YUV420_888, NV21, or YV12",
+ format == ImageFormat.YUV_420_888 ||
+ format == ImageFormat.NV21 ||
+ format == ImageFormat.YV12);
+
+ 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) {
+ if ((format != ImageFormat.YUV_420_888) &&
+ (format != ImageFormat.NV21) &&
+ (format != ImageFormat.YV12)) {
+ fail("Wrong formats: " + format);
+ }
+ }
+ /**
+ * <p>Check android image format validity for an image, only support below formats:</p>
+ *
+ * <p>Valid formats are YUV_420_888/NV21/YV12 for video decoder</p>
+ */
+ private static void checkAndroidImageFormat(Image image) {
+ int format = image.getFormat();
+ Plane[] planes = image.getPlanes();
+ switch (format) {
+ case ImageFormat.YUV_420_888:
+ case ImageFormat.NV21:
+ case ImageFormat.YV12:
+ assertEquals("YUV420 format Images should have 3 planes", 3, planes.length);
+ break;
+ default:
+ fail("Unsupported Image Format: " + format);
+ }
+ }
+
+ /**
+ * Get a byte array image data from an Image object.
+ * <p>
+ * Read data from all planes of an Image into a contiguous unpadded,
+ * unpacked 1-D linear byte array, such that it can be write into disk, or
+ * accessed by software conveniently. It supports YUV_420_888/NV21/YV12
+ * input Image format.
+ * </p>
+ * <p>
+ * For YUV_420_888/NV21/YV12/Y8/Y16, it returns a byte array that contains
+ * the Y plane data first, followed by U(Cb), V(Cr) planes if there is any
+ * (xstride = width, ystride = height for chroma and luma components).
+ * </p>
+ */
+ private static byte[] getDataFromImage(Image image) {
+ assertNotNull("Invalid image:", image);
+ int format = image.getFormat();
+ int width = image.getWidth();
+ int height = image.getHeight();
+ int rowStride, pixelStride;
+ byte[] data = null;
+
+ // Read image data
+ Plane[] planes = image.getPlanes();
+ assertTrue("Fail to get image planes", planes != null && planes.length > 0);
+
+ // Check image validity
+ checkAndroidImageFormat(image);
+
+ ByteBuffer buffer = null;
+
+ int offset = 0;
+ data = new byte[width * height * ImageFormat.getBitsPerPixel(format) / 8];
+ 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++) {
+ buffer = planes[i].getBuffer();
+ assertNotNull("Fail to get bytebuffer from plane", buffer);
+ rowStride = planes[i].getRowStride();
+ pixelStride = planes[i].getPixelStride();
+ assertTrue("pixel stride " + pixelStride + " is invalid", pixelStride > 0);
+ if (VERBOSE) {
+ Log.v(TAG, "pixelStride " + pixelStride);
+ Log.v(TAG, "rowStride " + rowStride);
+ Log.v(TAG, "width " + width);
+ 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;
+ assertTrue("rowStride " + rowStride + " should be >= width " + w , rowStride >= w);
+ for (int row = 0; row < h; row++) {
+ int bytesPerPixel = ImageFormat.getBitsPerPixel(format) / 8;
+ if (pixelStride == bytesPerPixel) {
+ // Special case: optimized read of the entire row
+ int 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);
+ for (int col = 0; col < w; col++) {
+ data[offset++] = rowData[col * pixelStride];
+ }
+ }
+ }
+ if (VERBOSE) Log.v(TAG, "Finished reading data from plane " + i);
+ }
+ return data;
+ }
+
+ private static void dumpFile(String fileName, byte[] data) {
+ assertNotNull("fileName must not be null", fileName);
+ assertNotNull("data must not be null", data);
+
+ FileOutputStream outStream;
+ try {
+ Log.v(TAG, "output will be saved as " + fileName);
+ outStream = new FileOutputStream(fileName);
+ } catch (IOException ioe) {
+ throw new RuntimeException("Unable to create debug output file " + fileName, ioe);
+ }
+
+ try {
+ outStream.write(data);
+ outStream.close();
+ } catch (IOException ioe) {
+ throw new RuntimeException("failed writing data to file " + fileName, ioe);
+ }
+ }
+
+ private void createImageReader(int width, int height, int format, int maxNumImages,
+ ImageReader.OnImageAvailableListener listener) throws Exception {
+ closeImageReader();
+
+ mReader = ImageReader.newInstance(width, height, format, maxNumImages);
+ mReaderSurface = mReader.getSurface();
+ mReader.setOnImageAvailableListener(listener, mHandler);
+ if (VERBOSE) {
+ Log.v(TAG, String.format("Created ImageReader size (%dx%d), format %d", width, height,
+ format));
+ }
+ }
+
+ /**
+ * Close the pending images then close current active {@link ImageReader} object.
+ */
+ private void closeImageReader() {
+ if (mReader != null) {
+ try {
+ // Close all possible pending images first.
+ Image image = mReader.acquireLatestImage();
+ if (image != null) {
+ image.close();
+ }
+ } finally {
+ mReader.close();
+ mReader = null;
+ }
+ }
+ }
+}
diff --git a/tests/tests/media/src/android/media/cts/IvfReader.java b/tests/tests/media/src/android/media/cts/IvfReader.java
index 508ae25..2f679ae 100644
--- a/tests/tests/media/src/android/media/cts/IvfReader.java
+++ b/tests/tests/media/src/android/media/cts/IvfReader.java
@@ -22,18 +22,19 @@
/**
* A simple reader for an IVF file.
*
- * IVF format is a simple container format for VP8 encoded frames.
+ * IVF format is a simple container format for VP8 encoded frames defined at
+ * http://wiki.multimedia.cx/index.php?title=IVF.
* This reader is capable of getting frame count, width and height
* from the header, and access individual frames randomly by
* frame number.
*/
public class IvfReader {
- private static final byte HEADER_END = 32;
- private static final byte FOURCC_HEAD = 8;
- private static final byte WIDTH_HEAD = 12;
- private static final byte HEIGHT_HEAD = 14;
- private static final byte FRAMECOUNT_HEAD = 24;
+ private static final byte HEADER_SIZE = 32;
+ private static final byte FOURCC_OFFSET = 8;
+ private static final byte WIDTH_OFFSET = 12;
+ private static final byte HEIGHT_OFFSET = 14;
+ private static final byte FRAMECOUNT_OFFSET = 24;
private static final byte FRAME_HEADER_SIZE = 12;
private RandomAccessFile mIvfFile;
@@ -101,7 +102,7 @@
* than 0 and less than frameCount.
*/
public byte[] readFrame(int frameIndex) throws IOException {
- if (frameIndex > mFrameCount | frameIndex < 0){
+ if (frameIndex > mFrameCount || frameIndex < 0){
return null;
}
int frameSize = mFrameSizes[frameIndex];
@@ -124,7 +125,7 @@
private boolean verifyHeader() throws IOException{
mIvfFile.seek(0);
- if (mIvfFile.length() < HEADER_END){
+ if (mIvfFile.length() < HEADER_SIZE){
return false;
}
@@ -135,7 +136,7 @@
(mIvfFile.readByte() == (byte)'F'));
// Fourcc
- mIvfFile.seek(FOURCC_HEAD);
+ mIvfFile.seek(FOURCC_OFFSET);
boolean fourccMatch = ((mIvfFile.readByte() == (byte)'V') &&
(mIvfFile.readByte() == (byte)'P') &&
(mIvfFile.readByte() == (byte)'8') &&
@@ -146,15 +147,15 @@
private void readHeaderData() throws IOException{
// width
- mIvfFile.seek(WIDTH_HEAD);
+ mIvfFile.seek(WIDTH_OFFSET);
mWidth = (int) changeEndianness(mIvfFile.readShort());
// height
- mIvfFile.seek(HEIGHT_HEAD);
+ mIvfFile.seek(HEIGHT_OFFSET);
mHeight = (int) changeEndianness(mIvfFile.readShort());
// frame count
- mIvfFile.seek(FRAMECOUNT_HEAD);
+ mIvfFile.seek(FRAMECOUNT_OFFSET);
mFrameCount = changeEndianness(mIvfFile.readInt());
// allocate frame metadata
@@ -163,7 +164,7 @@
}
private void readFrameMetadata() throws IOException{
- int frameHead = HEADER_END;
+ int frameHead = HEADER_SIZE;
for(int i = 0; i < mFrameCount; i++){
mIvfFile.seek(frameHead);
int frameSize = changeEndianness(mIvfFile.readInt());
diff --git a/tests/tests/media/src/android/media/cts/IvfWriter.java b/tests/tests/media/src/android/media/cts/IvfWriter.java
index ccc0ac5..075f73c 100644
--- a/tests/tests/media/src/android/media/cts/IvfWriter.java
+++ b/tests/tests/media/src/android/media/cts/IvfWriter.java
@@ -22,7 +22,8 @@
/**
* Writes an IVF file.
*
- * IVF format is a simple container format for VP8 encoded frames.
+ * IVF format is a simple container format for VP8 encoded frames defined at
+ * http://wiki.multimedia.cx/index.php?title=IVF.
*/
public class IvfWriter {
@@ -56,13 +57,13 @@
mScale = scale;
mRate = rate;
mFrameCount = 0;
+ mOutputFile.setLength(0);
mOutputFile.seek(HEADER_END); // Skip the header for now, as framecount is unknown
}
/**
* Initializes the IVF file writer with a microsecond timebase.
*
- *
* Microsecond timebase is default for OMX thus stagefright.
*
* @param filename name of the IVF file
@@ -87,7 +88,7 @@
* Writes a single encoded VP8 frame with its frame header.
*
* @param frame actual contents of the encoded frame data
- * @param width timestamp of the frame (in accordance to specified timebase)
+ * @param timeStamp timestamp of the frame (in accordance to specified timebase)
*/
public void writeFrame(byte[] frame, long timeStamp) throws IOException {
mOutputFile.write(makeIvfFrameHeader(frame.length, timeStamp));
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecListTest.java b/tests/tests/media/src/android/media/cts/MediaCodecListTest.java
index 3428e86..9c07cf1 100644
--- a/tests/tests/media/src/android/media/cts/MediaCodecListTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaCodecListTest.java
@@ -26,6 +26,7 @@
import android.util.Log;
import java.io.File;
+import java.io.IOException;
import java.util.List;
import java.util.ArrayList;
@@ -56,7 +57,7 @@
// Each component advertised by MediaCodecList should at least be
// instantiate-able.
- public void testComponentInstantiation() {
+ public void testComponentInstantiation() throws IOException {
Log.d(TAG, "testComponentInstantiation");
int codecCount = MediaCodecList.getCodecCount();
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecTest.java b/tests/tests/media/src/android/media/cts/MediaCodecTest.java
index 4b8abcd..ae14e6f 100644
--- a/tests/tests/media/src/android/media/cts/MediaCodecTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaCodecTest.java
@@ -87,7 +87,11 @@
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);
try {
- encoder = MediaCodec.createByCodecName(codecInfo.getName());
+ try {
+ encoder = MediaCodec.createByCodecName(codecInfo.getName());
+ } catch (IOException e) {
+ fail("failed to create codec " + codecInfo.getName());
+ }
try {
surface = encoder.createInputSurface();
fail("createInputSurface should not work pre-configure");
@@ -120,7 +124,6 @@
assertNull(surface);
}
-
/**
* Tests:
* <br> signaling end-of-stream before any data is sent works
@@ -133,7 +136,11 @@
InputSurface inputSurface = null;
try {
- encoder = MediaCodec.createEncoderByType(MIME_TYPE);
+ try {
+ encoder = MediaCodec.createEncoderByType(MIME_TYPE);
+ } catch (IOException e) {
+ fail("failed to create " + MIME_TYPE + " encoder");
+ }
encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
inputSurface = new InputSurface(encoder.createInputSurface());
inputSurface.makeCurrent();
@@ -173,6 +180,61 @@
/**
* Tests:
+ * <br> stopping with buffers in flight doesn't crash or hang
+ */
+ public void testAbruptStop() {
+ // There appears to be a race, so run it several times with a short delay between runs
+ // to allow any previous activity to shut down.
+ for (int i = 0; i < 50; i++) {
+ Log.d(TAG, "testAbruptStop " + i);
+ doTestAbruptStop();
+ try { Thread.sleep(400); } catch (InterruptedException ignored) {}
+ }
+ }
+ private void doTestAbruptStop() {
+ MediaFormat format = createMediaFormat();
+ MediaCodec encoder = null;
+ InputSurface inputSurface = null;
+
+ try {
+ try {
+ encoder = MediaCodec.createEncoderByType(MIME_TYPE);
+ } catch (IOException e) {
+ fail("failed to create " + MIME_TYPE + " encoder");
+ }
+ encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+ inputSurface = new InputSurface(encoder.createInputSurface());
+ inputSurface.makeCurrent();
+ encoder.start();
+
+ int totalBuffers = encoder.getInputBuffers().length +
+ encoder.getOutputBuffers().length;
+ if (VERBOSE) Log.d(TAG, "Total buffers: " + totalBuffers);
+
+ // Submit several frames quickly, without draining the encoder output, to try to
+ // ensure that we've got some queued up when we call stop(). If we do too many
+ // we'll block in swapBuffers().
+ for (int i = 0; i < totalBuffers; i++) {
+ GLES20.glClearColor(0.0f, (i % 8) / 8.0f, 0.0f, 1.0f);
+ GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+ inputSurface.swapBuffers();
+ }
+ Log.d(TAG, "stopping");
+ encoder.stop();
+ Log.d(TAG, "stopped");
+ } finally {
+ if (encoder != null) {
+ encoder.stop();
+ encoder.release();
+ }
+ if (inputSurface != null) {
+ inputSurface.release();
+ }
+ }
+ }
+
+ /**
+ * Tests:
* <br> dequeueInputBuffer() fails when encoder configured with an input Surface
*/
public void testDequeueSurface() {
@@ -181,7 +243,11 @@
Surface surface = null;
try {
- encoder = MediaCodec.createEncoderByType(MIME_TYPE);
+ try {
+ encoder = MediaCodec.createEncoderByType(MIME_TYPE);
+ } catch (IOException e) {
+ fail("failed to create " + MIME_TYPE + " encoder");
+ }
encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
surface = encoder.createInputSurface();
encoder.start();
@@ -215,7 +281,11 @@
Surface surface = null;
try {
- encoder = MediaCodec.createEncoderByType(MIME_TYPE);
+ try {
+ encoder = MediaCodec.createEncoderByType(MIME_TYPE);
+ } catch (IOException e) {
+ fail("failed to create " + MIME_TYPE + " encoder");
+ }
encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
surface = encoder.createInputSurface();
encoder.start();
@@ -365,11 +435,19 @@
MediaCodec audioDecoderA = null;
MediaCodec audioDecoderB = null;
try {
- audioDecoderA = MediaCodec.createDecoderByType(MIME_TYPE_AUDIO);
+ try {
+ audioDecoderA = MediaCodec.createDecoderByType(MIME_TYPE_AUDIO);
+ } catch (IOException e) {
+ fail("failed to create first " + MIME_TYPE_AUDIO + " decoder");
+ }
audioDecoderA.configure(format, null, null, 0);
audioDecoderA.start();
- audioDecoderB = MediaCodec.createDecoderByType(MIME_TYPE_AUDIO);
+ try {
+ audioDecoderB = MediaCodec.createDecoderByType(MIME_TYPE_AUDIO);
+ } catch (IOException e) {
+ fail("failed to create second " + MIME_TYPE_AUDIO + " decoder");
+ }
audioDecoderB.configure(format, null, null, 0);
audioDecoderB.start();
} finally {
@@ -407,11 +485,19 @@
MediaCodec audioEncoder = null;
MediaCodec audioDecoder = null;
try {
- audioEncoder = MediaCodec.createEncoderByType(MIME_TYPE_AUDIO);
+ try {
+ audioEncoder = MediaCodec.createEncoderByType(MIME_TYPE_AUDIO);
+ } catch (IOException e) {
+ fail("failed to create " + MIME_TYPE_AUDIO + " encoder");
+ }
audioEncoder.configure(encoderFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
audioEncoder.start();
- audioDecoder = MediaCodec.createDecoderByType(MIME_TYPE_AUDIO);
+ try {
+ audioDecoder = MediaCodec.createDecoderByType(MIME_TYPE_AUDIO);
+ } catch (IOException e) {
+ fail("failed to create " + MIME_TYPE_AUDIO + " decoder");
+ }
audioDecoder.configure(decoderFormat, null, null, 0);
audioDecoder.start();
} finally {
diff --git a/tests/tests/media/src/android/media/cts/MediaPlayerTest.java b/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
index c670b8c..275a648 100644
--- a/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
@@ -37,7 +37,10 @@
import android.os.SystemClock;
import android.util.Log;
+import java.io.BufferedReader;
import java.io.File;
+import java.io.InputStream;
+import java.io.InputStreamReader;
import java.util.StringTokenizer;
import java.util.UUID;
import java.util.Vector;
@@ -117,11 +120,74 @@
}
}
- public void testPlayAudio() throws Exception {
+ public void testPlayAudioFromDataURI() throws Exception {
final int mp3Duration = 34909;
final int tolerance = 70;
final int seekDuration = 100;
+
+ // This is "R.raw.testmp3_2", base64-encoded.
+ final int resid = R.raw.testmp3_3;
+
+ InputStream is = mContext.getResources().openRawResource(resid);
+ BufferedReader reader = new BufferedReader(new InputStreamReader(is));
+
+ StringBuilder builder = new StringBuilder();
+ builder.append("data:;base64,");
+ builder.append(reader.readLine());
+ Uri uri = Uri.parse(builder.toString());
+
+ MediaPlayer mp = MediaPlayer.create(mContext, uri);
+
+ try {
+ mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
+ mp.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
+
+ assertFalse(mp.isPlaying());
+ mp.start();
+ assertTrue(mp.isPlaying());
+
+ assertFalse(mp.isLooping());
+ mp.setLooping(true);
+ assertTrue(mp.isLooping());
+
+ assertEquals(mp3Duration, mp.getDuration(), tolerance);
+ int pos = mp.getCurrentPosition();
+ assertTrue(pos >= 0);
+ assertTrue(pos < mp3Duration - seekDuration);
+
+ mp.seekTo(pos + seekDuration);
+ assertEquals(pos + seekDuration, mp.getCurrentPosition(), tolerance);
+
+ // test pause and restart
+ mp.pause();
+ Thread.sleep(SLEEP_TIME);
+ assertFalse(mp.isPlaying());
+ mp.start();
+ assertTrue(mp.isPlaying());
+
+ // test stop and restart
+ mp.stop();
+ mp.reset();
+ mp.setDataSource(mContext, uri);
+ mp.prepare();
+ assertFalse(mp.isPlaying());
+ mp.start();
+ assertTrue(mp.isPlaying());
+
+ // waiting to complete
+ while(mp.isPlaying()) {
+ Thread.sleep(SLEEP_TIME);
+ }
+ } finally {
+ mp.release();
+ }
+ }
+
+ public void testPlayAudio() throws Exception {
final int resid = R.raw.testmp3_2;
+ final int mp3Duration = 34909;
+ final int tolerance = 70;
+ final int seekDuration = 100;
MediaPlayer mp = MediaPlayer.create(mContext, resid);
try {
@@ -657,6 +723,14 @@
R.raw.video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz, 480, 360);
}
+ public void testLocalVideo_MP4_H264_480x360_1350kbps_30fps_AAC_Stereo_128kbps_44110Hz_frag()
+ throws Exception {
+ playVideoTest(
+ R.raw.video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz_fragmented,
+ 480, 360);
+ }
+
+
public void testLocalVideo_MP4_H264_480x360_1350kbps_30fps_AAC_Stereo_192kbps_44110Hz()
throws Exception {
playVideoTest(
@@ -918,7 +992,7 @@
mMediaPlayer.setOnTimedTextListener(new MediaPlayer.OnTimedTextListener() {
@Override
public void onTimedText(MediaPlayer mp, TimedText text) {
- final int toleranceMs = 100;
+ final int toleranceMs = 150;
final int durationMs = 500;
int posMs = mMediaPlayer.getCurrentPosition();
if (text != null) {
@@ -1000,6 +1074,41 @@
assertEquals(4, count);
}
+ /*
+ * This test assumes the resources being tested are between 8 and 14 seconds long
+ * The ones being used here are 10 seconds long.
+ */
+ public void testResumeAtEnd() throws Throwable {
+ testResumeAtEnd(R.raw.loudsoftmp3);
+ testResumeAtEnd(R.raw.loudsoftwav);
+ testResumeAtEnd(R.raw.loudsoftogg);
+ testResumeAtEnd(R.raw.loudsoftitunes);
+ testResumeAtEnd(R.raw.loudsoftfaac);
+ testResumeAtEnd(R.raw.loudsoftaac);
+ }
+
+ private void testResumeAtEnd(int res) throws Throwable {
+
+ loadResource(res);
+ mMediaPlayer.prepare();
+ mOnCompletionCalled.reset();
+ mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
+ @Override
+ public void onCompletion(MediaPlayer mp) {
+ mOnCompletionCalled.signal();
+ mMediaPlayer.start();
+ }
+ });
+ // skip the first part of the file so we reach EOF sooner
+ mMediaPlayer.seekTo(5000);
+ mMediaPlayer.start();
+ // sleep long enough that we restart playback at least once, but no more
+ Thread.sleep(10000);
+ assertTrue("MediaPlayer should still be playing", mMediaPlayer.isPlaying());
+ mMediaPlayer.reset();
+ assertEquals("wrong number of repetitions", 1, mOnCompletionCalled.getNumSignal());
+ }
+
public void testCallback() throws Throwable {
final int mp4Duration = 8484;
diff --git a/tests/tests/media/src/android/media/cts/MediaScannerTest.java b/tests/tests/media/src/android/media/cts/MediaScannerTest.java
index 2dafdc5..1d492bb 100644
--- a/tests/tests/media/src/android/media/cts/MediaScannerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaScannerTest.java
@@ -76,6 +76,7 @@
@Override
protected void tearDown() throws Exception {
+ cleanup();
super.tearDown();
}
@@ -84,10 +85,12 @@
mMediaFile.delete();
}
if (mFileDir != null) {
- new File(mFileDir + "/testmp3.mp3").delete();
- new File(mFileDir + "/testmp3_2.mp3").delete();
- new File(mFileDir + "/ctsmediascanplaylist1.pls").delete();
- new File(mFileDir + "/ctsmediascanplaylist2.m3u").delete();
+ String files[] = new File(mFileDir).list();
+ if (files != null) {
+ for (String f: files) {
+ new File(mFileDir + "/" + f).delete();
+ }
+ }
new File(mFileDir).delete();
}
@@ -367,6 +370,129 @@
assertTrue(new File(path2).delete());
}
+ static class MediaScanEntry {
+ MediaScanEntry(int r, String[] t) {
+ this.res = r;
+ this.tags = t;
+ }
+ int res;
+ String[] tags;
+ }
+
+ MediaScanEntry encodingtestfiles[] = {
+ new MediaScanEntry(R.raw.gb18030_1,
+ new String[] {"罗志祥", "2009年11月新歌", "罗志祥", "爱不单行(TV Version)", null} ),
+ new MediaScanEntry(R.raw.gb18030_2,
+ new String[] {"张杰", "明天过后", null, "明天过后", null} ),
+ new MediaScanEntry(R.raw.gb18030_3,
+ new String[] {"电视原声带", "格斗天王(限量精装版)(预购版)", null, "11.Open Arms.( cn808.net )", null} ),
+ new MediaScanEntry(R.raw.gb18030_4,
+ new String[] {"莫扎特", "黄金古典", "柏林爱乐乐团", "第25号交响曲", "莫扎特"} ),
+ new MediaScanEntry(R.raw.gb18030_5,
+ new String[] {"光良", "童话", "光良", "02.童话", "鍏夎壇"} ),
+ new MediaScanEntry(R.raw.gb18030_6,
+ new String[] {"张韶涵", "潘朵拉", "張韶涵", "隐形的翅膀", "王雅君"} ),
+ new MediaScanEntry(R.raw.gb18030_7, // this is actually utf-8
+ new String[] {"五月天", "后青春期的诗", null, "突然好想你", null} ),
+ new MediaScanEntry(R.raw.gb18030_8,
+ new String[] {"周杰伦", "Jay", null, "反方向的钟", null} ),
+ new MediaScanEntry(R.raw.big5_1,
+ new String[] {"蘇永康", "So I Sing 08 Live", "蘇永康", "囍帖街", null} ),
+ new MediaScanEntry(R.raw.big5_2,
+ new String[] {"蘇永康", "So I Sing 08 Live", "蘇永康", "從不喜歡孤單一個 - 蘇永康/吳雨霏", null} ),
+ new MediaScanEntry(R.raw.cp1251_v1,
+ new String[] {"Екатерина Железнова", "Корабль игрушек", null, "Раз, два, три", null} ),
+ new MediaScanEntry(R.raw.cp1251_v1v2,
+ new String[] {"Мельница", "Перевал", null, "Королевна", null} ),
+ new MediaScanEntry(R.raw.cp1251_3,
+ new String[] {"Тату (tATu)", "200 По Встречной [Limited edi", null, "Я Сошла С Ума", null} ),
+ // The following 3 use cp1251 encoding, expanded to 16 bits and stored as utf16
+ new MediaScanEntry(R.raw.cp1251_4,
+ new String[] {"Александр Розенбаум", "Философия любви", null, "Разговор в гостинице (Как жить без веры)", "А.Розенбаум"} ),
+ new MediaScanEntry(R.raw.cp1251_5,
+ new String[] {"Александр Розенбаум", "Философия любви", null, "Четвертиночка", "А.Розенбаум"} ),
+ new MediaScanEntry(R.raw.cp1251_6,
+ new String[] {"Александр Розенбаум", "Философия ремесла", null, "Ну, вот...", "А.Розенбаум"} ),
+ new MediaScanEntry(R.raw.cp1251_7,
+ new String[] {"Вопли Видоплясова", "Хвилі Амура", null, "Або або", null} ),
+ new MediaScanEntry(R.raw.cp1251_8,
+ new String[] {"Вопли Видоплясова", "Хвилі Амура", null, "Таємнi сфери", null} ),
+ new MediaScanEntry(R.raw.shiftjis1,
+ new String[] {"", "", null, "中島敦「山月記」(第1回)", null} ),
+ new MediaScanEntry(R.raw.shiftjis2,
+ new String[] {"音人", "SoundEffects", null, "ファンファーレ", null} ),
+ new MediaScanEntry(R.raw.shiftjis3,
+ new String[] {"音人", "SoundEffects", null, "シンキングタイム", null} ),
+ new MediaScanEntry(R.raw.shiftjis4,
+ new String[] {"音人", "SoundEffects", null, "出題", null} ),
+ new MediaScanEntry(R.raw.shiftjis5,
+ new String[] {"音人", "SoundEffects", null, "時報", null} ),
+ new MediaScanEntry(R.raw.shiftjis6,
+ new String[] {"音人", "SoundEffects", null, "正解", null} ),
+ new MediaScanEntry(R.raw.shiftjis7,
+ new String[] {"音人", "SoundEffects", null, "残念", null} ),
+ new MediaScanEntry(R.raw.shiftjis8,
+ new String[] {"音人", "SoundEffects", null, "間違い", null} ),
+ new MediaScanEntry(R.raw.iso88591_1,
+ new String[] {"Mozart", "Best of Mozart", null, "Overtüre (Die Hochzeit des Figaro)", null} ),
+ new MediaScanEntry(R.raw.iso88591_2, // actually UTF16, but only uses iso8859-1 chars
+ new String[] {"Björk", "Telegram", "Björk", "Possibly Maybe (Lucy Mix)", null} ),
+ new MediaScanEntry(R.raw.hebrew,
+ new String[] {"אריק סיני", "", null, "לי ולך", null } ),
+ new MediaScanEntry(R.raw.hebrew2,
+ new String[] {"הפרוייקט של עידן רייכל", "Untitled - 11-11-02 (9)", null, "בואי", null } )
+ };
+
+ public void testEncodingDetection() throws Exception {
+ for (int i = 0; i< encodingtestfiles.length; i++) {
+ MediaScanEntry entry = encodingtestfiles[i];
+ String name = mContext.getResources().getResourceEntryName(entry.res);
+ String path = mFileDir + "/" + name + ".mp3";
+ writeFile(entry.res, path);
+ }
+
+ startMediaScanAndWait();
+
+ String columns[] = {
+ MediaStore.Audio.Media.ARTIST,
+ MediaStore.Audio.Media.ALBUM,
+ MediaStore.Audio.Media.ALBUM_ARTIST,
+ MediaStore.Audio.Media.TITLE,
+ MediaStore.Audio.Media.COMPOSER
+ };
+ ContentResolver res = mContext.getContentResolver();
+ for (int i = 0; i< encodingtestfiles.length; i++) {
+ MediaScanEntry entry = encodingtestfiles[i];
+ String name = mContext.getResources().getResourceEntryName(entry.res);
+ String path = mFileDir + "/" + name + ".mp3";
+ Cursor c = res.query(MediaStore.Audio.Media.getContentUri("external"), columns,
+ MediaStore.Audio.Media.DATA + "=?", new String[] {path}, null);
+ assertNotNull("null cursor", c);
+ assertEquals("wrong number or results", 1, c.getCount());
+ assertTrue("failed to move cursor", c.moveToFirst());
+
+ for (int j =0; j < 5; j++) {
+ String expected = entry.tags[j];
+ if ("".equals(expected)) {
+ // empty entry in the table means an unset id3 tag that is filled in by
+ // the media scanner, e.g. by using "<unknown>". Since this may be localized,
+ // don't check it for any particular value.
+ assertNotNull("unexpected null entry " + i + " field " + j + "(" + path + ")",
+ c.getString(j));
+ } else {
+ assertEquals("mismatch on entry " + i + " field " + j + "(" + path + ")",
+ expected, c.getString(j));
+ }
+ }
+ // clean up
+ new File(path).delete();
+ res.delete(MediaStore.Audio.Media.getContentUri("external"),
+ MediaStore.Audio.Media.DATA + "=?", new String[] {path});
+
+ c.close();
+ }
+ }
+
private void startMediaScanAndWait() throws InterruptedException {
ScannerNotificationReceiver finishedReceiver = new ScannerNotificationReceiver(
Intent.ACTION_MEDIA_SCANNER_FINISHED);
diff --git a/tests/tests/media/src/android/media/cts/NativeDecoderTest.java b/tests/tests/media/src/android/media/cts/NativeDecoderTest.java
new file mode 100644
index 0000000..fc27dfa
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/NativeDecoderTest.java
@@ -0,0 +1,491 @@
+/*
+ * 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 android.media.cts;
+
+import com.android.cts.media.R;
+
+import android.content.res.AssetFileDescriptor;
+import android.content.res.Resources;
+import android.media.MediaCodec;
+import android.media.MediaCodec.BufferInfo;
+import android.media.MediaCodecInfo;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.media.MediaMetadataRetriever;
+import android.media.MediaPlayer;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+import android.view.Surface;
+import android.webkit.cts.CtsTestServer;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+public class NativeDecoderTest extends MediaPlayerTestBase {
+ private static final String TAG = "DecoderTest";
+
+ private static final int RESET_MODE_NONE = 0;
+ private static final int RESET_MODE_RECONFIGURE = 1;
+ private static final int RESET_MODE_FLUSH = 2;
+ private static final int RESET_MODE_EOS_FLUSH = 3;
+
+ private static final String[] CSD_KEYS = new String[] { "csd-0", "csd-1" };
+
+ private static final int CONFIG_MODE_NONE = 0;
+ private static final int CONFIG_MODE_QUEUE = 1;
+
+ private static Resources mResources;
+ short[] mMasterBuffer;
+
+ /** Load jni on initialization */
+ static {
+ Log.i("@@@", "before loadlibrary");
+ System.loadLibrary("ctsmediacodec_jni");
+ Log.i("@@@", "after loadlibrary");
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mResources = mContext.getResources();
+
+ }
+
+ // check that native extractor behavior matches java extractor
+
+ public void testExtractor() throws Exception {
+ testExtractor(R.raw.sinesweepogg);
+ testExtractor(R.raw.sinesweepmp3lame);
+ testExtractor(R.raw.sinesweepmp3smpb);
+ testExtractor(R.raw.sinesweepm4a);
+ testExtractor(R.raw.sinesweepflac);
+ testExtractor(R.raw.sinesweepwav);
+
+ testExtractor(R.raw.video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz);
+ testExtractor(R.raw.video_1280x720_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz);
+ testExtractor(R.raw.video_1280x720_webm_vp9_309kbps_25fps_vorbis_stereo_128kbps_44100hz);
+ testExtractor(R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz);
+ testExtractor(R.raw.video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz);
+
+ CtsTestServer foo = new CtsTestServer(mContext);
+ testExtractor(foo.getAssetUrl("noiseandchirps.ogg"));
+ testExtractor(foo.getAssetUrl("ringer.mp3"));
+ testExtractor(foo.getRedirectingAssetUrl("ringer.mp3"));
+ }
+
+ private void testExtractor(String path) throws Exception {
+ int[] jsizes = getSampleSizes(path);
+ int[] nsizes = getSampleSizesNativePath(path);
+
+ //Log.i("@@@", Arrays.toString(jsizes));
+ assertTrue("different samplesizes", Arrays.equals(jsizes, nsizes));
+ }
+
+ private void testExtractor(int res) throws Exception {
+ AssetFileDescriptor fd = mResources.openRawResourceFd(res);
+
+ int[] jsizes = getSampleSizes(
+ fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
+ int[] nsizes = getSampleSizesNative(
+ fd.getParcelFileDescriptor().getFd(), fd.getStartOffset(), fd.getLength());
+
+ fd.close();
+ //Log.i("@@@", Arrays.toString(jsizes));
+ assertTrue("different samplesizes", Arrays.equals(jsizes, nsizes));
+ }
+
+ private static int[] getSampleSizes(String path) throws IOException {
+ MediaExtractor ex = new MediaExtractor();
+ ex.setDataSource(path);
+ return getSampleSizes(ex);
+ }
+
+ private static int[] getSampleSizes(FileDescriptor fd, long offset, long size)
+ throws IOException {
+ MediaExtractor ex = new MediaExtractor();
+ ex.setDataSource(fd, offset, size);
+ return getSampleSizes(ex);
+ }
+
+ private static int[] getSampleSizes(MediaExtractor ex) {
+ ArrayList<Integer> foo = new ArrayList<Integer>();
+ ByteBuffer buf = ByteBuffer.allocate(1024*1024);
+ int numtracks = ex.getTrackCount();
+ assertTrue("no tracks", numtracks > 0);
+ foo.add(numtracks);
+ for (int i = 0; i < numtracks; i++) {
+ MediaFormat format = ex.getTrackFormat(i);
+ String mime = format.getString(MediaFormat.KEY_MIME);
+ if (mime.startsWith("audio/")) {
+ foo.add(0);
+ foo.add(format.getInteger(MediaFormat.KEY_SAMPLE_RATE));
+ foo.add(format.getInteger(MediaFormat.KEY_CHANNEL_COUNT));
+ foo.add((int)format.getLong(MediaFormat.KEY_DURATION));
+ } else if (mime.startsWith("video/")) {
+ foo.add(1);
+ foo.add(format.getInteger(MediaFormat.KEY_WIDTH));
+ foo.add(format.getInteger(MediaFormat.KEY_HEIGHT));
+ foo.add((int)format.getLong(MediaFormat.KEY_DURATION));
+ } else {
+ fail("unexpected mime type: " + mime);
+ }
+ ex.selectTrack(i);
+ }
+ while(true) {
+ int n = ex.readSampleData(buf, 0);
+ if (n < 0) {
+ break;
+ }
+ foo.add(n);
+ foo.add(ex.getSampleTrackIndex());
+ foo.add(ex.getSampleFlags());
+ foo.add((int)ex.getSampleTime()); // just the low bits should be OK
+ ex.advance();
+ }
+
+ int [] ret = new int[foo.size()];
+ for (int i = 0; i < ret.length; i++) {
+ ret[i] = foo.get(i);
+ }
+ return ret;
+ }
+
+ private static native int[] getSampleSizesNative(int fd, long offset, long size);
+ private static native int[] getSampleSizesNativePath(String path);
+
+
+ public void testDecoder() throws Exception {
+ testDecoder(R.raw.sinesweepogg);
+ testDecoder(R.raw.sinesweepmp3lame);
+ testDecoder(R.raw.sinesweepmp3smpb);
+ testDecoder(R.raw.sinesweepm4a);
+ testDecoder(R.raw.sinesweepflac);
+ testDecoder(R.raw.sinesweepwav);
+
+ testDecoder(R.raw.video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz);
+ testDecoder(R.raw.video_1280x720_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz);
+ testDecoder(R.raw.video_1280x720_webm_vp9_309kbps_25fps_vorbis_stereo_128kbps_44100hz);
+ testDecoder(R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz);
+ testDecoder(R.raw.video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz);
+
+ }
+
+ private void testDecoder(int res) throws Exception {
+ AssetFileDescriptor fd = mResources.openRawResourceFd(res);
+
+ int[] jdata = getDecodedData(
+ fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
+ int[] ndata = getDecodedDataNative(
+ fd.getParcelFileDescriptor().getFd(), fd.getStartOffset(), fd.getLength());
+
+ fd.close();
+ Log.i("@@@", Arrays.toString(jdata));
+ Log.i("@@@", Arrays.toString(ndata));
+ assertEquals("number of samples differs", jdata.length, ndata.length);
+ assertTrue("different decoded data", Arrays.equals(jdata, ndata));
+ }
+
+ private static int[] getDecodedData(FileDescriptor fd, long offset, long size)
+ throws IOException {
+ MediaExtractor ex = new MediaExtractor();
+ ex.setDataSource(fd, offset, size);
+ return getDecodedData(ex);
+ }
+ private static int[] getDecodedData(MediaExtractor ex) throws IOException {
+ int numtracks = ex.getTrackCount();
+ assertTrue("no tracks", numtracks > 0);
+ ArrayList<Integer>[] trackdata = new ArrayList[numtracks];
+ MediaCodec[] codec = new MediaCodec[numtracks];
+ MediaFormat[] format = new MediaFormat[numtracks];
+ ByteBuffer[][] inbuffers = new ByteBuffer[numtracks][];
+ ByteBuffer[][] outbuffers = new ByteBuffer[numtracks][];
+ for (int i = 0; i < numtracks; i++) {
+ format[i] = ex.getTrackFormat(i);
+ String mime = format[i].getString(MediaFormat.KEY_MIME);
+ if (mime.startsWith("audio/") || mime.startsWith("video/")) {
+ codec[i] = MediaCodec.createDecoderByType(mime);
+ codec[i].configure(format[i], null, null, 0);
+ codec[i].start();
+ inbuffers[i] = codec[i].getInputBuffers();
+ outbuffers[i] = codec[i].getOutputBuffers();
+ trackdata[i] = new ArrayList<Integer>();
+ } else {
+ fail("unexpected mime type: " + mime);
+ }
+ ex.selectTrack(i);
+ }
+
+ boolean[] sawInputEOS = new boolean[numtracks];
+ boolean[] sawOutputEOS = new boolean[numtracks];
+ int eosCount = 0;
+ BufferInfo info = new BufferInfo();
+ while(eosCount < numtracks) {
+ int t = ex.getSampleTrackIndex();
+ if (t >= 0) {
+ assertFalse("saw input EOS twice", sawInputEOS[t]);
+ int bufidx = codec[t].dequeueInputBuffer(5000);
+ if (bufidx >= 0) {
+ Log.i("@@@@", "track " + t + " buffer " + bufidx);
+ ByteBuffer buf = inbuffers[t][bufidx];
+ int sampleSize = ex.readSampleData(buf, 0);
+ Log.i("@@@@", "read " + sampleSize);
+ if (sampleSize < 0) {
+ sampleSize = 0;
+ sawInputEOS[t] = true;
+ Log.i("@@@@", "EOS");
+ //break;
+ }
+ long presentationTimeUs = ex.getSampleTime();
+
+ codec[t].queueInputBuffer(bufidx, 0, sampleSize, presentationTimeUs,
+ sawInputEOS[t] ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
+ ex.advance();
+ }
+ } else {
+ Log.i("@@@@", "no more input samples");
+ for (int tt = 0; tt < codec.length; tt++) {
+ if (!sawInputEOS[tt]) {
+ // we ran out of samples without ever signaling EOS to the codec,
+ // so do that now
+ int bufidx = codec[tt].dequeueInputBuffer(5000);
+ if (bufidx >= 0) {
+ codec[tt].queueInputBuffer(bufidx, 0, 0, 0,
+ MediaCodec.BUFFER_FLAG_END_OF_STREAM);
+ sawInputEOS[tt] = true;
+ }
+ }
+ }
+ }
+
+ // see if any of the codecs have data available
+ for (int tt = 0; tt < codec.length; tt++) {
+ if (!sawOutputEOS[tt]) {
+ int status = codec[tt].dequeueOutputBuffer(info, 1);
+ if (status >= 0) {
+ if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+ Log.i("@@@@", "EOS on track " + tt);
+ sawOutputEOS[tt] = true;
+ eosCount++;
+ }
+ Log.i("@@@@", "got decoded buffer for track " + tt + ", size " + info.size);
+ if (info.size > 0) {
+ addSampleData(trackdata[tt], outbuffers[tt][status], info.size, format[tt]);
+ }
+ codec[tt].releaseOutputBuffer(status, false);
+ } else if (status == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+ Log.i("@@@@", "output buffers changed for track " + tt);
+ outbuffers[tt] = codec[tt].getOutputBuffers();
+ } else if (status == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+ format[tt] = codec[tt].getOutputFormat();
+ Log.i("@@@@", "format changed for track " + t + ": " + format[tt].toString());
+ } else if (status == MediaCodec.INFO_TRY_AGAIN_LATER) {
+ Log.i("@@@@", "no buffer right now for track " + tt);
+ } else {
+ Log.i("@@@@", "unexpected info code for track " + tt + ": " + status);
+ }
+ } else {
+ Log.i("@@@@", "already at EOS on track " + tt);
+ }
+ }
+ }
+
+ int totalsize = 0;
+ for (int i = 0; i < numtracks; i++) {
+ totalsize += trackdata[i].size();
+ }
+ int[] trackbytes = new int[totalsize];
+ int idx = 0;
+ for (int i = 0; i < numtracks; i++) {
+ ArrayList<Integer> src = trackdata[i];
+ int tracksize = src.size();
+ for (int j = 0; j < tracksize; j++) {
+ trackbytes[idx++] = src.get(j);
+ }
+ }
+
+ return trackbytes;
+ }
+
+ static void addSampleData(ArrayList<Integer> dst,
+ ByteBuffer buf, int size, MediaFormat format) throws IOException{
+
+ Log.i("@@@", "addsample " + dst.size() + "/" + size);
+ int width = format.getInteger(MediaFormat.KEY_WIDTH, size);
+ int stride = format.getInteger(MediaFormat.KEY_STRIDE, width);
+ int height = format.getInteger(MediaFormat.KEY_HEIGHT, 1);
+ byte[] bb = new byte[width * height];
+ for (int i = 0; i < height; i++) {
+ buf.position(i * stride);
+ buf.get(bb, i * width, width);
+ }
+ // bb is filled with data
+ long sum = adler32(bb);
+ dst.add( (int) (sum & 0xffffffff));
+ }
+
+ // simple checksum computed over every decoded buffer
+ static long adler32(byte[] input) {
+ int a = 1;
+ int b = 0;
+ for (int i = 0; i < input.length; i++) {
+ int unsignedval = input[i];
+ if (unsignedval < 0) {
+ unsignedval = 256 + unsignedval;
+ }
+ a += unsignedval;
+ b += a;
+ }
+ a = a % 65521;
+ b = b % 65521;
+ long ret = b * 65536 + a;
+ Log.i("@@@", "adler " + input.length + "/" + ret);
+ return ret;
+ }
+
+ private static native int[] getDecodedDataNative(int fd, long offset, long size)
+ throws IOException;
+
+ public void testVideoPlayback() throws Exception {
+ testVideoPlayback(R.raw.video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz);
+ testVideoPlayback(R.raw.video_1280x720_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz);
+ testVideoPlayback(R.raw.video_1280x720_webm_vp9_309kbps_25fps_vorbis_stereo_128kbps_44100hz);
+ testVideoPlayback(R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz);
+ testVideoPlayback(R.raw.video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz);
+ }
+
+ private void testVideoPlayback(int res) throws Exception {
+ AssetFileDescriptor fd = mResources.openRawResourceFd(res);
+
+ boolean ret = testPlaybackNative(mActivity.getSurfaceHolder().getSurface(),
+ fd.getParcelFileDescriptor().getFd(), fd.getStartOffset(), fd.getLength());
+ assertTrue("native playback error", ret);
+ }
+
+ private static native boolean testPlaybackNative(Surface surface,
+ int fd, long startOffset, long length);
+
+ public void testMuxer() throws Exception {
+ testMuxer(R.raw.video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz, false);
+ }
+
+ private void testMuxer(int res, boolean webm) throws Exception {
+ AssetFileDescriptor infd = mResources.openRawResourceFd(res);
+
+ File base = mContext.getExternalFilesDir(null);
+ String tmpFile = base.getPath() + "/tmp.dat";
+ Log.i("@@@", "using tmp file " + tmpFile);
+ new File(tmpFile).delete();
+ ParcelFileDescriptor out = ParcelFileDescriptor.open(new File(tmpFile),
+ ParcelFileDescriptor.MODE_READ_WRITE | ParcelFileDescriptor.MODE_CREATE);
+
+ assertTrue("muxer failed", testMuxerNative(
+ infd.getParcelFileDescriptor().getFd(), infd.getStartOffset(), infd.getLength(),
+ out.getFd(), webm));
+
+ // compare the original with the remuxed
+ MediaExtractor org = new MediaExtractor();
+ org.setDataSource(infd.getFileDescriptor(),
+ infd.getStartOffset(), infd.getLength());
+
+ MediaExtractor remux = new MediaExtractor();
+ remux.setDataSource(out.getFileDescriptor());
+
+ assertEquals("mismatched numer of tracks", org.getTrackCount(), remux.getTrackCount());
+ for (int i = 0; i < 2; i++) {
+ MediaFormat format1 = org.getTrackFormat(i);
+ MediaFormat format2 = remux.getTrackFormat(i);
+ Log.i("@@@", "org: " + format1);
+ Log.i("@@@", "remux: " + format2);
+ assertTrue("different formats", compareFormats(format1, format2));
+ }
+
+ org.release();
+ remux.release();
+
+ MediaPlayer player1 = MediaPlayer.create(mContext, res);
+ MediaPlayer player2 = MediaPlayer.create(mContext, Uri.parse("file://" + tmpFile));
+ assertEquals("duration is different", player1.getDuration(), player2.getDuration());
+ player1.release();
+ player2.release();
+ new File(tmpFile).delete();
+ }
+
+ boolean compareFormats(MediaFormat f1, MediaFormat f2) {
+ // there's no good way to compare two MediaFormats, so compare their string
+ // representation
+ return f1.toString().equals(f2.toString());
+ }
+
+ private static native boolean testMuxerNative(int in, long inoffset, long insize,
+ int out, boolean webm);
+
+ public void testFormat() throws Exception {
+ assertTrue("media format fail, see log for details", testFormatNative());
+ }
+
+ private static native boolean testFormatNative();
+
+ public void testPssh() throws Exception {
+ testPssh(R.raw.psshtest);
+ }
+
+ private void testPssh(int res) throws Exception {
+ AssetFileDescriptor fd = mResources.openRawResourceFd(res);
+
+ MediaExtractor ex = new MediaExtractor();
+ ex.setDataSource(fd.getParcelFileDescriptor().getFileDescriptor(),
+ fd.getStartOffset(), fd.getLength());
+ testPssh(ex);
+ ex.release();
+
+ boolean ret = testPsshNative(
+ fd.getParcelFileDescriptor().getFd(), fd.getStartOffset(), fd.getLength());
+ assertTrue("native pssh error", ret);
+ }
+
+ private static void testPssh(MediaExtractor ex) {
+ Map<UUID, byte[]> map = ex.getPsshInfo();
+ Set<UUID> keys = map.keySet();
+ for (UUID uuid: keys) {
+ Log.i("@@@", "uuid: " + uuid + ", data size " +
+ map.get(uuid).length);
+ }
+ }
+
+ private static native boolean testPsshNative(int fd, long offset, long size);
+
+ public void testCryptoInfo() throws Exception {
+ assertTrue("native cryptoinfo failed, see log for details", testCryptoInfoNative());
+ }
+
+ private static native boolean testCryptoInfoNative();
+}
+
diff --git a/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java b/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java
index 8bac442..2b93064 100644
--- a/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java
+++ b/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java
@@ -16,9 +16,13 @@
package android.media.cts;
import android.media.MediaPlayer;
+import android.os.Looper;
+import android.os.SystemClock;
import android.util.Log;
import android.webkit.cts.CtsTestServer;
+import java.io.IOException;
+
/**
* Tests of MediaPlayer streaming capabilities.
@@ -259,6 +263,93 @@
localHlsTest("hls.m3u8", false, true);
}
+ private static class WorkerWithPlayer implements Runnable {
+ private final Object mLock = new Object();
+ private Looper mLooper;
+ private MediaPlayer mMediaPlayer;
+
+ /**
+ * Creates a worker thread with the given name. The thread
+ * then runs a {@link android.os.Looper}.
+ * @param name A name for the new thread
+ */
+ WorkerWithPlayer(String name) {
+ Thread t = new Thread(null, this, name);
+ t.setPriority(Thread.MIN_PRIORITY);
+ t.start();
+ synchronized (mLock) {
+ while (mLooper == null) {
+ try {
+ mLock.wait();
+ } catch (InterruptedException ex) {
+ }
+ }
+ }
+ }
+
+ public MediaPlayer getPlayer() {
+ return mMediaPlayer;
+ }
+
+ @Override
+ public void run() {
+ synchronized (mLock) {
+ Looper.prepare();
+ mLooper = Looper.myLooper();
+ mMediaPlayer = new MediaPlayer();
+ mLock.notifyAll();
+ }
+ Looper.loop();
+ }
+
+ public void quit() {
+ mLooper.quit();
+ mMediaPlayer.release();
+ }
+ }
+
+ public void testBlockingReadRelease() throws Throwable {
+
+ mServer = new CtsTestServer(mContext);
+
+ WorkerWithPlayer worker = new WorkerWithPlayer("player");
+ final MediaPlayer mp = worker.getPlayer();
+
+ try {
+ String path = mServer.getDelayedAssetUrl("noiseandchirps.ogg", 15000);
+ mp.setDataSource(path);
+ mp.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
+ @Override
+ public void onPrepared(MediaPlayer mp) {
+ fail("prepare should not succeed");
+ }
+ });
+ mp.prepareAsync();
+ Thread.sleep(1000);
+ long start = SystemClock.elapsedRealtime();
+ mp.release();
+ long end = SystemClock.elapsedRealtime();
+ long releaseDuration = (end - start);
+ assertTrue("release took too long: " + releaseDuration, releaseDuration < 1000);
+ } catch (IllegalArgumentException e) {
+ fail(e.getMessage());
+ } catch (SecurityException e) {
+ fail(e.getMessage());
+ } catch (IllegalStateException e) {
+ fail(e.getMessage());
+ } catch (IOException e) {
+ fail(e.getMessage());
+ } catch (InterruptedException e) {
+ fail(e.getMessage());
+ } finally {
+ mServer.shutdown();
+ }
+
+ // give the worker a bit of time to start processing the message before shutting it down
+ Thread.sleep(5000);
+ worker.quit();
+ }
+
private void localHlsTest(final String name, boolean appendQueryString, boolean redirect)
throws Throwable {
mServer = new CtsTestServer(mContext);
diff --git a/tests/tests/media/src/android/media/cts/Vp8CodecTestBase.java b/tests/tests/media/src/android/media/cts/Vp8CodecTestBase.java
new file mode 100644
index 0000000..58a61ab
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/Vp8CodecTestBase.java
@@ -0,0 +1,1655 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.cts;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaCodecList;
+import android.media.MediaCodecInfo;
+import android.media.MediaFormat;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Looper;
+import android.os.Handler;
+import android.test.AndroidTestCase;
+import android.util.Log;
+import com.android.cts.media.R;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.Locale;
+import java.util.ArrayList;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Verification test for vp8 encoder and decoder.
+ *
+ * A raw yv12 stream is encoded at various settings and written to an IVF
+ * file. Encoded stream bitrate and key frame interval are checked against target values.
+ * The stream is later decoded by vp8 decoder to verify frames are decodable and to
+ * calculate PSNR values for various bitrates.
+ */
+public class Vp8CodecTestBase extends AndroidTestCase {
+
+ protected static final String TAG = "VP8CodecTestBase";
+ private static final String VP8_MIME = "video/x-vnd.on2.vp8";
+ private static final String VPX_SW_DECODER_NAME = "OMX.google.vp8.decoder";
+ private static final String VPX_SW_ENCODER_NAME = "OMX.google.vp8.encoder";
+ private static final String OMX_SW_CODEC_PREFIX = "OMX.google";
+ protected static final String SDCARD_DIR =
+ Environment.getExternalStorageDirectory().getAbsolutePath();
+
+ // Default timeout for MediaCodec buffer dequeue - 200 ms.
+ protected static final long DEFAULT_TIMEOUT_US = 200000;
+ // Default sync frame interval in frames (zero means allow the encoder to auto-select
+ // key frame interval).
+ private static final int SYNC_FRAME_INTERVAL = 0;
+ // Video bitrate type - should be set to OMX_Video_ControlRateConstant from OMX_Video.h
+ protected static final int VIDEO_ControlRateVariable = 1;
+ protected static final int VIDEO_ControlRateConstant = 2;
+ // NV12 color format supported by QCOM codec, but not declared in MediaCodec -
+ // see /hardware/qcom/media/mm-core/inc/OMX_QCOMExtns.h
+ private static final int COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m = 0x7FA30C04;
+ // Allowable color formats supported by codec - in order of preference.
+ private static final int[] mSupportedColorList = {
+ CodecCapabilities.COLOR_FormatYUV420Planar,
+ CodecCapabilities.COLOR_FormatYUV420SemiPlanar,
+ CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar,
+ COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m
+ };
+ // Scaled image cache list - contains scale factors, for which up-scaled frames
+ // were calculated and were written to yuv file.
+ ArrayList<Integer> mScaledImages = new ArrayList<Integer>();
+
+ private Resources mResources;
+
+ @Override
+ public void setContext(Context context) {
+ super.setContext(context);
+ mResources = mContext.getResources();
+ }
+
+ /**
+ * VP8 codec properties generated by getVp8CodecProperties() function.
+ */
+ private class CodecProperties {
+ CodecProperties(String codecName, int colorFormat) {
+ this.codecName = codecName;
+ this.colorFormat = colorFormat;
+ }
+ public boolean isGoogleSwCodec() {
+ return codecName.startsWith(OMX_SW_CODEC_PREFIX);
+ }
+
+ public final String codecName; // OpenMax component name for VP8 codec.
+ public final int colorFormat; // Color format supported by codec.
+ }
+
+ /**
+ * Function to find VP8 codec.
+ *
+ * Iterates through the list of available codecs and tries to find
+ * VP8 codec, which can support either YUV420 planar or NV12 color formats.
+ * If forceSwGoogleCodec parameter set to true the function always returns
+ * Google sw VP8 codec.
+ * If forceSwGoogleCodec parameter set to false the functions looks for platform
+ * specific VP8 codec first. If no platform specific codec exist, falls back to
+ * Google sw VP8 codec.
+ *
+ * @param isEncoder Flag if encoder is requested.
+ * @param forceSwGoogleCodec Forces to use Google sw codec.
+ */
+ private CodecProperties getVp8CodecProperties(boolean isEncoder,
+ boolean forceSwGoogleCodec) throws Exception {
+ CodecProperties codecProperties = null;
+
+ if (!forceSwGoogleCodec) {
+ // Loop through the list of omx components in case platform specific codec
+ // is requested.
+ for (int i = 0; i < MediaCodecList.getCodecCount(); i++) {
+ MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
+ if (isEncoder != codecInfo.isEncoder()) {
+ continue;
+ }
+ Log.v(TAG, codecInfo.getName());
+ // Check if this is sw Google codec - we should ignore it.
+ boolean isGoogleSwCodec = codecInfo.getName().startsWith(OMX_SW_CODEC_PREFIX);
+ if (isGoogleSwCodec) {
+ continue;
+ }
+
+ for (String type : codecInfo.getSupportedTypes()) {
+ if (!type.equalsIgnoreCase(VP8_MIME)) {
+ continue;
+ }
+ CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(VP8_MIME);
+
+ // Get candidate codec properties.
+ Log.v(TAG, "Found candidate codec " + codecInfo.getName());
+ for (int colorFormat : capabilities.colorFormats) {
+ Log.v(TAG, " Color: 0x" + Integer.toHexString(colorFormat));
+ }
+
+ // Check supported color formats.
+ for (int supportedColorFormat : mSupportedColorList) {
+ for (int codecColorFormat : capabilities.colorFormats) {
+ if (codecColorFormat == supportedColorFormat) {
+ codecProperties = new CodecProperties(codecInfo.getName(),
+ codecColorFormat);
+ Log.v(TAG, "Found target codec " + codecProperties.codecName +
+ ". Color: 0x" + Integer.toHexString(codecColorFormat));
+ return codecProperties;
+ }
+ }
+ }
+ // HW codec we found does not support one of necessary color formats.
+ throw new RuntimeException("No hw codec with YUV420 or NV12 color formats");
+ }
+ }
+ }
+ // If no hw vp8 codec exist or sw codec is requested use default Google sw codec.
+ if (codecProperties == null) {
+ Log.v(TAG, "Use SW VP8 codec");
+ if (isEncoder) {
+ codecProperties = new CodecProperties(VPX_SW_ENCODER_NAME,
+ CodecCapabilities.COLOR_FormatYUV420Planar);
+ } else {
+ codecProperties = new CodecProperties(VPX_SW_DECODER_NAME,
+ CodecCapabilities.COLOR_FormatYUV420Planar);
+ }
+ }
+
+ return codecProperties;
+ }
+
+ /**
+ * Parameters for encoded video stream.
+ */
+ protected class EncoderOutputStreamParameters {
+ // Name of raw YUV420 input file. When the value of this parameter
+ // is set to null input file descriptor from inputResourceId parameter
+ // is used instead.
+ public String inputYuvFilename;
+ // Name of scaled YUV420 input file.
+ public String scaledYuvFilename;
+ // File descriptor for the raw input file (YUV420). Used only if
+ // inputYuvFilename parameter is null.
+ int inputResourceId;
+ // Name of the IVF file to write encoded bitsream
+ public String outputIvfFilename;
+ // Force to use Google SW VP8 encoder.
+ boolean forceSwEncoder;
+ // Number of frames to encode.
+ int frameCount;
+ // Frame rate of input file in frames per second.
+ int frameRate;
+ // Encoded frame width.
+ public int frameWidth;
+ // Encoded frame height.
+ public int frameHeight;
+ // Encoding bitrate array in bits/second for every frame. If array length
+ // is shorter than the total number of frames, the last value is re-used for
+ // all remaining frames. For constant bitrate encoding single element
+ // array can be used with first element set to target bitrate value.
+ public int[] bitrateSet;
+ // Encoding bitrate type - VBR or CBR
+ public int bitrateType;
+ // Number of temporal layers
+ public int temporalLayers;
+ // Desired key frame interval - codec is asked to generate key frames
+ // at a period defined by this parameter.
+ public int syncFrameInterval;
+ // Optional parameter - forced key frame interval. Used to
+ // explicitly request the codec to generate key frames using
+ // MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME parameter.
+ public int syncForceFrameInterval;
+ // Buffer timeout
+ long timeoutDequeue;
+ // Flag if encoder should run in Looper thread.
+ boolean runInLooperThread;
+ }
+
+ /**
+ * Generates an array of default parameters for encoder output stream based on
+ * upscaling value.
+ */
+ protected ArrayList<EncoderOutputStreamParameters> getDefaultEncodingParameterList(
+ String inputYuvName,
+ String outputIvfBaseName,
+ int encodeSeconds,
+ int[] resolutionScales,
+ int frameWidth,
+ int frameHeight,
+ int frameRate,
+ int bitrateMode,
+ int[] bitrates,
+ boolean syncEncoding) {
+ assertTrue(resolutionScales.length == bitrates.length);
+ int numCodecs = resolutionScales.length;
+ ArrayList<EncoderOutputStreamParameters> outputParameters =
+ new ArrayList<EncoderOutputStreamParameters>(numCodecs);
+ for (int i = 0; i < numCodecs; i++) {
+ EncoderOutputStreamParameters params = new EncoderOutputStreamParameters();
+ if (inputYuvName != null) {
+ params.inputYuvFilename = SDCARD_DIR + File.separator + inputYuvName;
+ } else {
+ params.inputYuvFilename = null;
+ }
+ params.scaledYuvFilename = SDCARD_DIR + File.separator +
+ outputIvfBaseName + resolutionScales[i]+ ".yuv";
+ params.inputResourceId = R.raw.football_qvga;
+ params.outputIvfFilename = SDCARD_DIR + File.separator +
+ outputIvfBaseName + resolutionScales[i] + ".ivf";
+ params.forceSwEncoder = false;
+ params.frameCount = encodeSeconds * frameRate;
+ params.frameRate = frameRate;
+ params.frameWidth = Math.min(frameWidth * resolutionScales[i], 1280);
+ params.frameHeight = Math.min(frameHeight * resolutionScales[i], 720);
+ params.bitrateSet = new int[1];
+ params.bitrateSet[0] = bitrates[i];
+ params.bitrateType = bitrateMode;
+ params.temporalLayers = 0;
+ params.syncFrameInterval = SYNC_FRAME_INTERVAL;
+ params.syncForceFrameInterval = 0;
+ if (syncEncoding) {
+ params.timeoutDequeue = DEFAULT_TIMEOUT_US;
+ params.runInLooperThread = false;
+ } else {
+ params.timeoutDequeue = 0;
+ params.runInLooperThread = true;
+ continue; // FIXME add support for async
+ }
+ outputParameters.add(params);
+ }
+ return outputParameters;
+ }
+
+ protected EncoderOutputStreamParameters getDefaultEncodingParameters(
+ String inputYuvName,
+ String outputIvfBaseName,
+ int encodeSeconds,
+ int frameWidth,
+ int frameHeight,
+ int frameRate,
+ int bitrateMode,
+ int bitrate,
+ boolean syncEncoding) {
+ int[] scaleValues = { 1 };
+ int[] bitrates = { bitrate };
+ return getDefaultEncodingParameterList(
+ inputYuvName,
+ outputIvfBaseName,
+ encodeSeconds,
+ scaleValues,
+ frameWidth,
+ frameHeight,
+ frameRate,
+ bitrateMode,
+ bitrates,
+ syncEncoding).get(0);
+ }
+
+ /**
+ * Converts (interleaves) YUV420 planar to NV12 (if hw) or NV21 (if sw).
+ * Assumes packed, macroblock-aligned frame with no cropping
+ * (visible/coded row length == stride). Swap U/V if |sw|.
+ */
+ private static byte[] YUV420ToNV(int width, int height, byte[] yuv, boolean sw) {
+ byte[] nv = new byte[yuv.length];
+ // Y plane we just copy.
+ System.arraycopy(yuv, 0, nv, 0, width * height);
+
+ // U & V plane we interleave.
+ int u_offset = width * height;
+ int v_offset = u_offset + u_offset / 4;
+ int nv_offset = width * height;
+ if (sw) {
+ for (int i = 0; i < width * height / 4; i++) {
+ nv[nv_offset++] = yuv[v_offset++];
+ nv[nv_offset++] = yuv[u_offset++];
+ }
+ }
+ else {
+ for (int i = 0; i < width * height / 4; i++) {
+ nv[nv_offset++] = yuv[u_offset++];
+ nv[nv_offset++] = yuv[v_offset++];
+ }
+ }
+ return nv;
+ }
+
+ /**
+ * Converts (de-interleaves) NV12 to YUV420 planar.
+ * Stride may be greater than width, slice height may be greater than height.
+ */
+ private static byte[] NV12ToYUV420(int width, int height,
+ int stride, int sliceHeight, byte[] nv12) {
+ byte[] yuv = new byte[width * height * 3 / 2];
+
+ // Y plane we just copy.
+ for (int i = 0; i < height; i++) {
+ System.arraycopy(nv12, i * stride, yuv, i * width, width);
+ }
+
+ // U & V plane - de-interleave.
+ int u_offset = width * height;
+ int v_offset = u_offset + u_offset / 4;
+ int nv_offset;
+ for (int i = 0; i < height / 2; i++) {
+ nv_offset = stride * (sliceHeight + i);
+ for (int j = 0; j < width / 2; j++) {
+ yuv[u_offset++] = nv12[nv_offset++];
+ yuv[v_offset++] = nv12[nv_offset++];
+ }
+ }
+ return yuv;
+ }
+
+ private static void imageUpscale1To2(byte[] src, int srcByteOffset, int srcStride,
+ byte[] dst, int dstByteOffset, int dstWidth, int dstHeight) {
+ for (int i = 0; i < dstHeight/2 - 1; i++) {
+ int dstOffset0 = 2 * i * dstWidth + dstByteOffset;
+ int dstOffset1 = dstOffset0 + dstWidth;
+ int srcOffset0 = i * srcStride + srcByteOffset;
+ int srcOffset1 = srcOffset0 + srcStride;
+ int pixel00 = (int)src[srcOffset0++] & 0xff;
+ int pixel10 = (int)src[srcOffset1++] & 0xff;
+ for (int j = 0; j < dstWidth/2 - 1; j++) {
+ int pixel01 = (int)src[srcOffset0++] & 0xff;
+ int pixel11 = (int)src[srcOffset1++] & 0xff;
+ dst[dstOffset0++] = (byte)pixel00;
+ dst[dstOffset0++] = (byte)((pixel00 + pixel01 + 1) / 2);
+ dst[dstOffset1++] = (byte)((pixel00 + pixel10 + 1) / 2);
+ dst[dstOffset1++] = (byte)((pixel00 + pixel01 + pixel10 + pixel11 + 2) / 4);
+ pixel00 = pixel01;
+ pixel10 = pixel11;
+ }
+ // last column
+ dst[dstOffset0++] = (byte)pixel00;
+ dst[dstOffset0++] = (byte)pixel00;
+ dst[dstOffset1++] = (byte)((pixel00 + pixel10 + 1) / 2);
+ dst[dstOffset1++] = (byte)((pixel00 + pixel10 + 1) / 2);
+ }
+
+ // last row
+ int dstOffset0 = (dstHeight - 2) * dstWidth + dstByteOffset;
+ int dstOffset1 = dstOffset0 + dstWidth;
+ int srcOffset0 = (dstHeight/2 - 1) * srcStride + srcByteOffset;
+ int pixel00 = (int)src[srcOffset0++] & 0xff;
+ for (int j = 0; j < dstWidth/2 - 1; j++) {
+ int pixel01 = (int)src[srcOffset0++] & 0xff;
+ dst[dstOffset0++] = (byte)pixel00;
+ dst[dstOffset0++] = (byte)((pixel00 + pixel01 + 1) / 2);
+ dst[dstOffset1++] = (byte)pixel00;
+ dst[dstOffset1++] = (byte)((pixel00 + pixel01 + 1) / 2);
+ pixel00 = pixel01;
+ }
+ // the very last pixel - bottom right
+ dst[dstOffset0++] = (byte)pixel00;
+ dst[dstOffset0++] = (byte)pixel00;
+ dst[dstOffset1++] = (byte)pixel00;
+ dst[dstOffset1++] = (byte)pixel00;
+ }
+
+ /**
+ * Up-scale image.
+ * Scale factor is defined by source and destination width ratio.
+ * Only 1:2 and 1:4 up-scaling is supported for now.
+ * For 640x480 -> 1280x720 conversion only top 640x360 part of the original
+ * image is scaled.
+ */
+ private static byte[] imageScale(byte[] src, int srcWidth, int srcHeight,
+ int dstWidth, int dstHeight) throws Exception {
+ int srcYSize = srcWidth * srcHeight;
+ int dstYSize = dstWidth * dstHeight;
+ byte[] dst = null;
+ if (dstWidth == 2 * srcWidth && dstHeight <= 2 * srcHeight) {
+ // 1:2 upscale
+ dst = new byte[dstWidth * dstHeight * 3 / 2];
+ imageUpscale1To2(src, 0, srcWidth,
+ dst, 0, dstWidth, dstHeight); // Y
+ imageUpscale1To2(src, srcYSize, srcWidth / 2,
+ dst, dstYSize, dstWidth / 2, dstHeight / 2); // U
+ imageUpscale1To2(src, srcYSize * 5 / 4, srcWidth / 2,
+ dst, dstYSize * 5 / 4, dstWidth / 2, dstHeight / 2); // V
+ } else if (dstWidth == 4 * srcWidth && dstHeight <= 4 * srcHeight) {
+ // 1:4 upscale - in two steps
+ int midWidth = 2 * srcWidth;
+ int midHeight = 2 * srcHeight;
+ byte[] midBuffer = imageScale(src, srcWidth, srcHeight, midWidth, midHeight);
+ dst = imageScale(midBuffer, midWidth, midHeight, dstWidth, dstHeight);
+
+ } else {
+ throw new RuntimeException("Can not find proper scaling function");
+ }
+
+ return dst;
+ }
+
+ private void cacheScaledImage(
+ String srcYuvFilename, int srcResourceId, int srcFrameWidth, int srcFrameHeight,
+ String dstYuvFilename, int dstFrameWidth, int dstFrameHeight) throws Exception {
+ InputStream srcStream = OpenFileOrResourceId(srcYuvFilename, srcResourceId);
+ FileOutputStream dstFile = new FileOutputStream(dstYuvFilename, false);
+ int srcFrameSize = srcFrameWidth * srcFrameHeight * 3 / 2;
+ byte[] srcFrame = new byte[srcFrameSize];
+ byte[] dstFrame = null;
+ Log.d(TAG, "Scale to " + dstFrameWidth + " x " + dstFrameHeight + ". -> " + dstYuvFilename);
+ while (true) {
+ int bytesRead = srcStream.read(srcFrame);
+ if (bytesRead != srcFrame.length) {
+ break;
+ }
+ if (dstFrameWidth == srcFrameWidth && dstFrameHeight == srcFrameHeight) {
+ dstFrame = srcFrame;
+ } else {
+ dstFrame = imageScale(srcFrame, srcFrameWidth, srcFrameHeight,
+ dstFrameWidth, dstFrameHeight);
+ }
+ dstFile.write(dstFrame);
+ }
+ srcStream.close();
+ dstFile.close();
+ }
+
+
+ /**
+ * A basic check if an encoded stream is decodable.
+ *
+ * The most basic confirmation we can get about a frame
+ * being properly encoded is trying to decode it.
+ * (Especially in realtime mode encode output is non-
+ * deterministic, therefore a more thorough check like
+ * md5 sum comparison wouldn't work.)
+ *
+ * Indeed, MediaCodec will raise an IllegalStateException
+ * whenever vp8 decoder fails to decode a frame, and
+ * this test uses that fact to verify the bitstream.
+ *
+ * @param inputIvfFilename The name of the IVF file containing encoded bitsream.
+ * @param outputYuvFilename The name of the output YUV file (optional).
+ * @param frameRate Frame rate of input file in frames per second
+ * @param forceSwDecoder Force to use Googlw sw VP8 decoder.
+ */
+ protected ArrayList<MediaCodec.BufferInfo> decode(
+ String inputIvfFilename,
+ String outputYuvFilename,
+ int frameRate,
+ boolean forceSwDecoder) throws Exception {
+ ArrayList<MediaCodec.BufferInfo> bufferInfos = new ArrayList<MediaCodec.BufferInfo>();
+ CodecProperties properties = getVp8CodecProperties(false, forceSwDecoder);
+ // Open input/output.
+ IvfReader ivf = new IvfReader(inputIvfFilename);
+ int frameWidth = ivf.getWidth();
+ int frameHeight = ivf.getHeight();
+ int frameCount = ivf.getFrameCount();
+ int frameStride = frameWidth;
+ int frameSliceHeight = frameHeight;
+ int frameColorFormat = properties.colorFormat;
+ assertTrue(frameWidth > 0);
+ assertTrue(frameHeight > 0);
+ assertTrue(frameCount > 0);
+
+ FileOutputStream yuv = null;
+ if (outputYuvFilename != null) {
+ yuv = new FileOutputStream(outputYuvFilename, false);
+ }
+
+ // Create decoder.
+ MediaFormat format = MediaFormat.createVideoFormat(VP8_MIME,
+ ivf.getWidth(),
+ ivf.getHeight());
+ format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
+ Log.d(TAG, "Creating decoder " + properties.codecName +
+ ". Color format: 0x" + Integer.toHexString(frameColorFormat) +
+ ". " + frameWidth + " x " + frameHeight);
+ Log.d(TAG, " Format: " + format);
+ Log.d(TAG, " In: " + inputIvfFilename + ". Out:" + outputYuvFilename);
+ MediaCodec decoder = MediaCodec.createByCodecName(properties.codecName);
+ decoder.configure(format,
+ null, // surface
+ null, // crypto
+ 0); // flags
+ decoder.start();
+
+ ByteBuffer[] inputBuffers = decoder.getInputBuffers();
+ ByteBuffer[] outputBuffers = decoder.getOutputBuffers();
+ MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
+
+ // decode loop
+ int inputFrameIndex = 0;
+ int outputFrameIndex = 0;
+ long inPresentationTimeUs = 0;
+ long outPresentationTimeUs = 0;
+ boolean sawOutputEOS = false;
+ boolean sawInputEOS = false;
+
+ while (!sawOutputEOS) {
+ if (!sawInputEOS) {
+ int inputBufIndex = decoder.dequeueInputBuffer(DEFAULT_TIMEOUT_US);
+ if (inputBufIndex >= 0) {
+ byte[] frame = ivf.readFrame(inputFrameIndex);
+
+ if (inputFrameIndex == frameCount - 1) {
+ Log.d(TAG, " Input EOS for frame # " + inputFrameIndex);
+ sawInputEOS = true;
+ }
+
+ inputBuffers[inputBufIndex].clear();
+ inputBuffers[inputBufIndex].put(frame);
+ inputBuffers[inputBufIndex].rewind();
+ inPresentationTimeUs = (inputFrameIndex * 1000000) / frameRate;
+
+ decoder.queueInputBuffer(
+ inputBufIndex,
+ 0, // offset
+ frame.length,
+ inPresentationTimeUs,
+ sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
+
+ inputFrameIndex++;
+ }
+ }
+
+ int result = decoder.dequeueOutputBuffer(bufferInfo, DEFAULT_TIMEOUT_US);
+ while (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED ||
+ result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+ if (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+ outputBuffers = decoder.getOutputBuffers();
+ } else if (result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+ // Process format change
+ format = decoder.getOutputFormat();
+ frameWidth = format.getInteger(MediaFormat.KEY_WIDTH);
+ frameHeight = format.getInteger(MediaFormat.KEY_HEIGHT);
+ frameColorFormat = format.getInteger(MediaFormat.KEY_COLOR_FORMAT);
+ Log.d(TAG, "Decoder output format change. Color: 0x" +
+ Integer.toHexString(frameColorFormat));
+ Log.d(TAG, "Format: " + format.toString());
+
+ // Parse frame and slice height from undocumented values
+ if (format.containsKey("stride")) {
+ frameStride = format.getInteger("stride");
+ } else {
+ frameStride = frameWidth;
+ }
+ if (format.containsKey("slice-height")) {
+ frameSliceHeight = format.getInteger("slice-height");
+ } else {
+ frameSliceHeight = frameHeight;
+ }
+ Log.d(TAG, "Frame stride and slice height: " + frameStride +
+ " x " + frameSliceHeight);
+ }
+ result = decoder.dequeueOutputBuffer(bufferInfo, DEFAULT_TIMEOUT_US);
+ }
+ if (result >= 0) {
+ int outputBufIndex = result;
+ outPresentationTimeUs = bufferInfo.presentationTimeUs;
+ Log.v(TAG, "Writing buffer # " + outputFrameIndex +
+ ". Size: " + bufferInfo.size +
+ ". InTime: " + (inPresentationTimeUs + 500)/1000 +
+ ". OutTime: " + (outPresentationTimeUs + 500)/1000);
+ if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+ sawOutputEOS = true;
+ Log.d(TAG, " Output EOS for frame # " + outputFrameIndex);
+ }
+
+ if (bufferInfo.size > 0) {
+ // Save decoder output to yuv file.
+ if (yuv != null) {
+ byte[] frame = new byte[bufferInfo.size];
+ outputBuffers[outputBufIndex].position(bufferInfo.offset);
+ outputBuffers[outputBufIndex].get(frame, 0, bufferInfo.size);
+ // Convert NV12 to YUV420 if necessary
+ if (frameColorFormat != CodecCapabilities.COLOR_FormatYUV420Planar) {
+ frame = NV12ToYUV420(frameWidth, frameHeight,
+ frameStride, frameSliceHeight, frame);
+ }
+ yuv.write(frame);
+ }
+ outputFrameIndex++;
+
+ // Update statistics - store presentation time delay in offset
+ long presentationTimeUsDelta = inPresentationTimeUs - outPresentationTimeUs;
+ MediaCodec.BufferInfo bufferInfoCopy = new MediaCodec.BufferInfo();
+ bufferInfoCopy.set((int)presentationTimeUsDelta, bufferInfo.size,
+ outPresentationTimeUs, bufferInfo.flags);
+ bufferInfos.add(bufferInfoCopy);
+ }
+ decoder.releaseOutputBuffer(outputBufIndex, false);
+ }
+ }
+ decoder.stop();
+ decoder.release();
+ ivf.close();
+ if (yuv != null) {
+ yuv.close();
+ }
+
+ return bufferInfos;
+ }
+
+
+ /**
+ * Helper function to return InputStream from either filename (if set)
+ * or resource id (if filename is not set).
+ */
+ private InputStream OpenFileOrResourceId(String filename, int resourceId) throws Exception {
+ if (filename != null) {
+ return new FileInputStream(filename);
+ }
+ return mResources.openRawResource(resourceId);
+ }
+
+ /**
+ * Results of frame encoding.
+ */
+ protected class MediaEncoderOutput {
+ public long inPresentationTimeUs;
+ public long outPresentationTimeUs;
+ public boolean outputGenerated;
+ public int flags;
+ public byte[] buffer;
+ }
+
+ /**
+ * Video encoder wrapper class.
+ * Allows to run the encoder either in a callee's thread or in a looper thread
+ * using buffer dequeue ready notification callbacks.
+ *
+ * Function feedInput() is used to send raw video frame to the encoder input. When encoder
+ * is configured to run in async mode the function will run in a looper thread.
+ * Encoded frame can be retrieved by calling getOutput() function.
+ */
+ protected class MediaEncoderAsync extends Thread /* FIXME implements MediaCodec.NotificationCallback */ {
+ private int mId;
+ private MediaCodec mCodec;
+ private MediaFormat mFormat;
+ private ByteBuffer[] mInputBuffers;
+ private ByteBuffer[] mOutputBuffers;
+ private int mInputFrameIndex;
+ private int mOutputFrameIndex;
+ private int mInputBufIndex;
+ private int mFrameRate;
+ private long mTimeout;
+ private MediaCodec.BufferInfo mBufferInfo;
+ private long mInPresentationTimeUs;
+ private long mOutPresentationTimeUs;
+ private boolean mAsync;
+ // Flag indicating if input frame was consumed by the encoder in feedInput() call.
+ private boolean mConsumedInput;
+ // Result of frame encoding returned by getOutput() call.
+ private MediaEncoderOutput mOutput;
+ // Object used to signal that looper thread has started and Handler instance associated
+ // with looper thread has been allocated.
+ private final Object mThreadEvent = new Object();
+ // Object used to signal that MediaCodec buffer dequeue notification callback
+ // was received.
+ private final Object mCallbackEvent = new Object();
+ private Handler mHandler;
+ private boolean mCallbackReceived;
+
+ /* FIXME @Override */
+ public void onCodecNotify(MediaCodec codec) {
+ synchronized (mCallbackEvent) {
+ Log.v(TAG, "MediaEncoder " + mId + " Event Callback");
+ mCallbackReceived = true;
+ mCallbackEvent.notify();
+ }
+ return;
+ }
+
+ private synchronized void requestStart() throws Exception {
+ mHandler = null;
+ start();
+ // Wait for Hander allocation
+ synchronized (mThreadEvent) {
+ while (mHandler == null) {
+ mThreadEvent.wait();
+ }
+ }
+ }
+
+ @Override
+ public void run() {
+ Looper.prepare();
+ synchronized (mThreadEvent) {
+ mHandler = new Handler();
+ mThreadEvent.notify();
+ }
+ Looper.loop();
+ }
+
+ private void runCallable(final Callable<?> callable) throws Exception {
+ if (mAsync) {
+ final Exception[] exception = new Exception[1];
+ final CountDownLatch countDownLatch = new CountDownLatch(1);
+ mHandler.post( new Runnable() {
+ @Override
+ public void run() {
+ try {
+ callable.call();
+ } catch (Exception e) {
+ exception[0] = e;
+ } finally {
+ countDownLatch.countDown();
+ }
+ }
+ } );
+
+ // Wait for task completion
+ countDownLatch.await();
+ if (exception[0] != null) {
+ throw exception[0];
+ }
+ } else {
+ callable.call();
+ }
+ }
+
+ private synchronized void requestStop() throws Exception {
+ mHandler.post( new Runnable() {
+ @Override
+ public void run() {
+ // This will run on the Looper thread
+ Log.v(TAG, "MediaEncoder looper quitting");
+ Looper.myLooper().quitSafely();
+ }
+ } );
+ // Wait for completion
+ join();
+ mHandler = null;
+ }
+
+ private void createCodecInternal(final String name,
+ final MediaFormat format, final long timeout) throws Exception {
+ mBufferInfo = new MediaCodec.BufferInfo();
+ mFormat = format;
+ mFrameRate = format.getInteger(MediaFormat.KEY_FRAME_RATE);
+ mTimeout = timeout;
+ mInputFrameIndex = 0;
+ mOutputFrameIndex = 0;
+ mInPresentationTimeUs = 0;
+ mOutPresentationTimeUs = 0;
+
+ mCodec = MediaCodec.createByCodecName(name);
+ mCodec.configure(mFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+ mCodec.start();
+ if (mAsync) {
+ /* FIXME mCodec.setNotificationCallback(this); */
+ }
+ mInputBuffers = mCodec.getInputBuffers();
+ mOutputBuffers = mCodec.getOutputBuffers();
+ }
+
+
+ public void createCodec(int id, final String name, final MediaFormat format,
+ final long timeout, boolean async) throws Exception {
+ mId = id;
+ mAsync = async;
+ if (mAsync) {
+ requestStart(); // start looper thread
+ }
+ runCallable( new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ createCodecInternal(name, format, timeout);
+ return null;
+ }
+ } );
+ }
+
+ private void feedInputInternal(final byte[] encFrame, final boolean inputEOS) {
+ mConsumedInput = false;
+ // Feed input
+ mInputBufIndex = mCodec.dequeueInputBuffer(mTimeout);
+
+ if (mInputBufIndex >= 0) {
+ mInputBuffers[mInputBufIndex].clear();
+ mInputBuffers[mInputBufIndex].put(encFrame);
+ mInputBuffers[mInputBufIndex].rewind();
+ int encFrameLength = encFrame.length;
+ int flags = 0;
+ if (inputEOS) {
+ encFrameLength = 0;
+ flags = MediaCodec.BUFFER_FLAG_END_OF_STREAM;
+ }
+ if (!inputEOS) {
+ Log.v(TAG, "Enc" + mId + ". Frame in # " + mInputFrameIndex +
+ ". InTime: " + (mInPresentationTimeUs + 500)/1000);
+ mInPresentationTimeUs = (mInputFrameIndex * 1000000) / mFrameRate;
+ mInputFrameIndex++;
+ }
+
+ mCodec.queueInputBuffer(
+ mInputBufIndex,
+ 0, // offset
+ encFrameLength, // size
+ mInPresentationTimeUs,
+ flags);
+
+ mConsumedInput = true;
+ } else {
+ Log.v(TAG, "In " + mId + " - TRY_AGAIN_LATER");
+ }
+ mCallbackReceived = false;
+ }
+
+ public boolean feedInput(final byte[] encFrame, final boolean inputEOS) throws Exception {
+ runCallable( new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ feedInputInternal(encFrame, inputEOS);
+ return null;
+ }
+ } );
+ return mConsumedInput;
+ }
+
+ private void getOutputInternal() {
+ mOutput = new MediaEncoderOutput();
+ mOutput.inPresentationTimeUs = mInPresentationTimeUs;
+ mOutput.outPresentationTimeUs = mOutPresentationTimeUs;
+ mOutput.outputGenerated = false;
+
+ // Get output from the encoder
+ int result = mCodec.dequeueOutputBuffer(mBufferInfo, mTimeout);
+ while (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED ||
+ result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+ if (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+ mOutputBuffers = mCodec.getOutputBuffers();
+ } else if (result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+ mFormat = mCodec.getOutputFormat();
+ Log.d(TAG, "Format changed: " + mFormat.toString());
+ }
+ result = mCodec.dequeueOutputBuffer(mBufferInfo, mTimeout);
+ }
+ if (result == MediaCodec.INFO_TRY_AGAIN_LATER) {
+ Log.v(TAG, "Out " + mId + " - TRY_AGAIN_LATER");
+ }
+
+ if (result >= 0) {
+ int outputBufIndex = result;
+ mOutput.buffer = new byte[mBufferInfo.size];
+ mOutputBuffers[outputBufIndex].position(mBufferInfo.offset);
+ mOutputBuffers[outputBufIndex].get(mOutput.buffer, 0, mBufferInfo.size);
+ mOutPresentationTimeUs = mBufferInfo.presentationTimeUs;
+
+ String logStr = "Enc" + mId + ". Frame # " + mOutputFrameIndex;
+ if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
+ logStr += " CONFIG. ";
+ }
+ if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0) {
+ logStr += " KEY. ";
+ }
+ if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+ logStr += " EOS. ";
+ }
+ logStr += " Size: " + mBufferInfo.size;
+ logStr += ". InTime: " + (mInPresentationTimeUs + 500)/1000 +
+ ". OutTime: " + (mOutPresentationTimeUs + 500)/1000;
+ Log.v(TAG, logStr);
+ if (mOutputFrameIndex == 0 &&
+ ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) == 0) ) {
+ throw new RuntimeException("First frame is not a sync frame.");
+ }
+
+ if (mBufferInfo.size > 0) {
+ mOutputFrameIndex++;
+ mOutput.outPresentationTimeUs = mOutPresentationTimeUs;
+ }
+ mCodec.releaseOutputBuffer(outputBufIndex, false);
+
+ mOutput.flags = mBufferInfo.flags;
+ mOutput.outputGenerated = true;
+ }
+ mCallbackReceived = false;
+ }
+
+ public MediaEncoderOutput getOutput() throws Exception {
+ runCallable( new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ getOutputInternal();
+ return null;
+ }
+ } );
+ return mOutput;
+ }
+
+ public void forceSyncFrame() throws Exception {
+ final Bundle syncFrame = new Bundle();
+ syncFrame.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0);
+ runCallable( new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ mCodec.setParameters(syncFrame);
+ return null;
+ }
+ } );
+ }
+
+ public void updateBitrate(int bitrate) throws Exception {
+ final Bundle bitrateUpdate = new Bundle();
+ bitrateUpdate.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, bitrate);
+ runCallable( new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ mCodec.setParameters(bitrateUpdate);
+ return null;
+ }
+ } );
+ }
+
+
+ public void waitForBufferEvent() throws Exception {
+ Log.v(TAG, "----Enc" + mId + " waiting for bufferEvent");
+ if (mAsync) {
+ synchronized (mCallbackEvent) {
+ if (!mCallbackReceived) {
+ mCallbackEvent.wait(1000); // wait 1 sec for a callback
+ // throw an exception if callback was not received
+ if (!mCallbackReceived) {
+ throw new RuntimeException("MediaCodec callback was not received");
+ }
+ }
+ }
+ } else {
+ Thread.sleep(5);
+ }
+ Log.v(TAG, "----Waiting for bufferEvent done");
+ }
+
+ public void deleteCodec() throws Exception {
+ runCallable( new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ mCodec.stop();
+ mCodec.release();
+ return null;
+ }
+ } );
+ if (mAsync) {
+ requestStop(); // Stop looper thread
+ }
+ }
+ }
+
+
+ /**
+ * Vp8 encoding loop supporting encoding single streams with an option
+ * to run in a looper thread and use buffer ready notification callbacks.
+ *
+ * Output stream is described by encodingParams parameters.
+ *
+ * MediaCodec will raise an IllegalStateException
+ * whenever vp8 encoder fails to encode a frame.
+ *
+ * Color format of input file should be YUV420, and frameWidth,
+ * frameHeight should be supplied correctly as raw input file doesn't
+ * include any header data.
+ *
+ * @param streamParams Structure with encoder parameters
+ * @return Returns array of encoded frames information for each frame.
+ */
+ protected ArrayList<MediaCodec.BufferInfo> encode(
+ EncoderOutputStreamParameters streamParams) throws Exception {
+
+ ArrayList<MediaCodec.BufferInfo> bufferInfos = new ArrayList<MediaCodec.BufferInfo>();
+ CodecProperties properties = getVp8CodecProperties(true, streamParams.forceSwEncoder);
+ Log.d(TAG, "Source reslution: " + streamParams.frameWidth + " x " +
+ streamParams.frameHeight);
+ int bitrate = streamParams.bitrateSet[0];
+
+ // Open input/output
+ InputStream yuvStream = OpenFileOrResourceId(
+ streamParams.inputYuvFilename, streamParams.inputResourceId);
+ IvfWriter ivf = new IvfWriter(
+ streamParams.outputIvfFilename, streamParams.frameWidth, streamParams.frameHeight);
+
+ // Create a media format signifying desired output.
+ MediaFormat format = MediaFormat.createVideoFormat(
+ VP8_MIME, streamParams.frameWidth, streamParams.frameHeight);
+ format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
+ if (streamParams.bitrateType == VIDEO_ControlRateConstant) {
+ format.setInteger("bitrate-mode", VIDEO_ControlRateConstant); // set CBR
+ }
+ if (streamParams.temporalLayers > 0) {
+ format.setInteger("ts-layers", streamParams.temporalLayers); // 1 temporal layer
+ }
+ format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
+ format.setInteger(MediaFormat.KEY_FRAME_RATE, streamParams.frameRate);
+ int syncFrameInterval = (streamParams.syncFrameInterval + streamParams.frameRate/2) /
+ streamParams.frameRate;
+ format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, syncFrameInterval);
+
+ // Create encoder
+ Log.d(TAG, "Creating encoder " + properties.codecName +
+ ". Color format: 0x" + Integer.toHexString(properties.colorFormat)+ " : " +
+ streamParams.frameWidth + " x " + streamParams.frameHeight +
+ ". Bitrate: " + bitrate + " Bitrate type: " + streamParams.bitrateType +
+ ". Fps:" + streamParams.frameRate + ". TS Layers: " + streamParams.temporalLayers +
+ ". Key frame:" + syncFrameInterval * streamParams.frameRate +
+ ". Force keyFrame: " + streamParams.syncForceFrameInterval);
+ Log.d(TAG, " Format: " + format);
+ Log.d(TAG, " Output ivf:" + streamParams.outputIvfFilename);
+ MediaEncoderAsync codec = new MediaEncoderAsync();
+ codec.createCodec(0, properties.codecName, format,
+ streamParams.timeoutDequeue, streamParams.runInLooperThread);
+
+ // encode loop
+ boolean sawInputEOS = false; // no more data
+ boolean consumedInputEOS = false; // EOS flag is consumed dy encoder
+ boolean sawOutputEOS = false;
+ boolean inputConsumed = true;
+ int inputFrameIndex = 0;
+ int lastBitrate = bitrate;
+ int srcFrameSize = streamParams.frameWidth * streamParams.frameHeight * 3 / 2;
+ byte[] srcFrame = new byte[srcFrameSize];
+
+ while (!sawOutputEOS) {
+
+ // Read and feed input frame
+ if (!consumedInputEOS) {
+
+ // Read new input buffers - if previous input was consumed and no EOS
+ if (inputConsumed && !sawInputEOS) {
+ int bytesRead = yuvStream.read(srcFrame);
+
+ // Check EOS
+ if (streamParams.frameCount > 0 && inputFrameIndex >= streamParams.frameCount) {
+ sawInputEOS = true;
+ Log.d(TAG, "---Sending EOS empty frame for frame # " + inputFrameIndex);
+ }
+
+ if (!sawInputEOS && bytesRead == -1) {
+ if (streamParams.frameCount == 0) {
+ sawInputEOS = true;
+ Log.d(TAG, "---Sending EOS empty frame for frame # " + inputFrameIndex);
+ } else {
+ yuvStream.close();
+ yuvStream = OpenFileOrResourceId(
+ streamParams.inputYuvFilename, streamParams.inputResourceId);
+ bytesRead = yuvStream.read(srcFrame);
+ }
+ }
+
+ // Force sync frame if syncForceFrameinterval is set.
+ if (!sawInputEOS && inputFrameIndex > 0 &&
+ streamParams.syncForceFrameInterval > 0 &&
+ (inputFrameIndex % streamParams.syncForceFrameInterval) == 0) {
+ Log.d(TAG, "---Requesting sync frame # " + inputFrameIndex);
+ codec.forceSyncFrame();
+ }
+
+ // Dynamic bitrate change.
+ if (!sawInputEOS && streamParams.bitrateSet.length > inputFrameIndex) {
+ int newBitrate = streamParams.bitrateSet[inputFrameIndex];
+ if (newBitrate != lastBitrate) {
+ Log.d(TAG, "--- Requesting new bitrate " + newBitrate +
+ " for frame " + inputFrameIndex);
+ codec.updateBitrate(newBitrate);
+ lastBitrate = newBitrate;
+ }
+ }
+
+ // Convert YUV420 to NV12 if necessary
+ if (properties.colorFormat != CodecCapabilities.COLOR_FormatYUV420Planar) {
+ srcFrame = YUV420ToNV(streamParams.frameWidth, streamParams.frameHeight,
+ srcFrame, properties.isGoogleSwCodec());
+ }
+ }
+
+ inputConsumed = codec.feedInput(srcFrame, sawInputEOS);
+ if (inputConsumed) {
+ inputFrameIndex++;
+ consumedInputEOS = sawInputEOS;
+ }
+ }
+
+ // Get output from the encoder
+ MediaEncoderOutput out = codec.getOutput();
+ if (out.outputGenerated) {
+ // Detect output EOS
+ if ((out.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+ Log.d(TAG, "----Output EOS ");
+ sawOutputEOS = true;
+ }
+
+ if (out.buffer.length > 0) {
+ // Save frame
+ ivf.writeFrame(out.buffer, out.outPresentationTimeUs);
+
+ // Update statistics - store presentation time delay in offset
+ long presentationTimeUsDelta = out.inPresentationTimeUs -
+ out.outPresentationTimeUs;
+ MediaCodec.BufferInfo bufferInfoCopy = new MediaCodec.BufferInfo();
+ bufferInfoCopy.set((int)presentationTimeUsDelta, out.buffer.length,
+ out.outPresentationTimeUs, out.flags);
+ bufferInfos.add(bufferInfoCopy);
+ }
+ }
+
+ // If codec is not ready to accept input/poutput - wait for buffer ready callback
+ if ((!inputConsumed || consumedInputEOS) && !out.outputGenerated) {
+ codec.waitForBufferEvent();
+ }
+ }
+
+ codec.deleteCodec();
+ ivf.close();
+ yuvStream.close();
+
+ return bufferInfos;
+ }
+
+ /**
+ * Vp8 encoding loop supporting encoding multiple streams at a time.
+ * Each output stream is described by encodingParams parameters allowing
+ * simultaneous encoding of various resolutions, bitrates with an option to
+ * control key frame and dynamic bitrate for each output stream indepandently.
+ *
+ * MediaCodec will raise an IllegalStateException
+ * whenever vp8 encoder fails to encode a frame.
+ *
+ * Color format of input file should be YUV420, and frameWidth,
+ * frameHeight should be supplied correctly as raw input file doesn't
+ * include any header data.
+ *
+ * @param srcFrameWidth Frame width of input yuv file
+ * @param srcFrameHeight Frame height of input yuv file
+ * @param encodingParams Encoder parameters
+ * @return Returns 2D array of encoded frames information for each stream and
+ * for each frame.
+ */
+ protected ArrayList<ArrayList<MediaCodec.BufferInfo>> encodeSimulcast(
+ int srcFrameWidth,
+ int srcFrameHeight,
+ ArrayList<EncoderOutputStreamParameters> encodingParams) throws Exception {
+ int numEncoders = encodingParams.size();
+
+ // Create arrays of input/output, formats, bitrates etc
+ ArrayList<ArrayList<MediaCodec.BufferInfo>> bufferInfos =
+ new ArrayList<ArrayList<MediaCodec.BufferInfo>>(numEncoders);
+ InputStream yuvStream[] = new InputStream[numEncoders];
+ IvfWriter[] ivf = new IvfWriter[numEncoders];
+ FileOutputStream[] yuvScaled = new FileOutputStream[numEncoders];
+ MediaFormat[] format = new MediaFormat[numEncoders];
+ MediaEncoderAsync[] codec = new MediaEncoderAsync[numEncoders];
+ int[] inputFrameIndex = new int[numEncoders];
+ boolean[] sawInputEOS = new boolean[numEncoders];
+ boolean[] consumedInputEOS = new boolean[numEncoders];
+ boolean[] inputConsumed = new boolean[numEncoders];
+ boolean[] bufferConsumed = new boolean[numEncoders];
+ boolean[] sawOutputEOS = new boolean[numEncoders];
+ byte[][] srcFrame = new byte[numEncoders][];
+ boolean sawOutputEOSTotal = false;
+ boolean bufferConsumedTotal = false;
+ CodecProperties[] codecProperties = new CodecProperties[numEncoders];
+
+ for (int i = 0; i < numEncoders; i++) {
+ EncoderOutputStreamParameters params = encodingParams.get(i);
+ CodecProperties properties = getVp8CodecProperties(true, params.forceSwEncoder);
+
+ // Check if scaled image was created
+ int scale = params.frameWidth / srcFrameWidth;
+ if (!mScaledImages.contains(scale)) {
+ // resize image
+ cacheScaledImage(params.inputYuvFilename, params.inputResourceId,
+ srcFrameWidth, srcFrameHeight,
+ params.scaledYuvFilename, params.frameWidth, params.frameHeight);
+ mScaledImages.add(scale);
+ }
+
+ // Create buffer info storage
+ bufferInfos.add(new ArrayList<MediaCodec.BufferInfo>());
+
+ // Create YUV reader
+ yuvStream[i] = new FileInputStream(params.scaledYuvFilename);
+
+ // Create IVF writer
+ ivf[i] = new IvfWriter(params.outputIvfFilename, params.frameWidth, params.frameHeight);
+
+ // Frame buffer
+ int frameSize = params.frameWidth * params.frameHeight * 3 / 2;
+ srcFrame[i] = new byte[frameSize];
+
+ // Create a media format signifying desired output.
+ int bitrate = params.bitrateSet[0];
+ format[i] = MediaFormat.createVideoFormat(VP8_MIME,
+ params.frameWidth, params.frameHeight);
+ format[i].setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
+ if (params.bitrateType == VIDEO_ControlRateConstant) {
+ format[i].setInteger("bitrate-mode", VIDEO_ControlRateConstant); // set CBR
+ }
+ if (params.temporalLayers > 0) {
+ format[i].setInteger("ts-layers", params.temporalLayers); // 1 temporal layer
+ }
+ format[i].setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
+ format[i].setInteger(MediaFormat.KEY_FRAME_RATE, params.frameRate);
+ int syncFrameInterval = (params.syncFrameInterval + params.frameRate/2) /
+ params.frameRate; // in sec
+ format[i].setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, syncFrameInterval);
+ // Create encoder
+ Log.d(TAG, "Creating encoder #" + i +" : " + properties.codecName +
+ ". Color format: 0x" + Integer.toHexString(properties.colorFormat)+ " : " +
+ params.frameWidth + " x " + params.frameHeight +
+ ". Bitrate: " + bitrate + " Bitrate type: " + params.bitrateType +
+ ". Fps:" + params.frameRate + ". TS Layers: " + params.temporalLayers +
+ ". Key frame:" + syncFrameInterval * params.frameRate +
+ ". Force keyFrame: " + params.syncForceFrameInterval);
+ Log.d(TAG, " Format: " + format[i]);
+ Log.d(TAG, " Output ivf:" + params.outputIvfFilename);
+
+ // Create encoder
+ codec[i] = new MediaEncoderAsync();
+ codec[i].createCodec(i, properties.codecName, format[i],
+ params.timeoutDequeue, params.runInLooperThread);
+ codecProperties[i] = new CodecProperties(properties.codecName, properties.colorFormat);
+
+ inputConsumed[i] = true;
+ }
+
+ while (!sawOutputEOSTotal) {
+ // Feed input buffer to all encoders
+ for (int i = 0; i < numEncoders; i++) {
+ bufferConsumed[i] = false;
+ if (consumedInputEOS[i]) {
+ continue;
+ }
+
+ EncoderOutputStreamParameters params = encodingParams.get(i);
+ // Read new input buffers - if previous input was consumed and no EOS
+ if (inputConsumed[i] && !sawInputEOS[i]) {
+ int bytesRead = yuvStream[i].read(srcFrame[i]);
+
+ // Check EOS
+ if (params.frameCount > 0 && inputFrameIndex[i] >= params.frameCount) {
+ sawInputEOS[i] = true;
+ Log.d(TAG, "---Enc" + i +
+ ". Sending EOS empty frame for frame # " + inputFrameIndex[i]);
+ }
+
+ if (!sawInputEOS[i] && bytesRead == -1) {
+ if (params.frameCount == 0) {
+ sawInputEOS[i] = true;
+ Log.d(TAG, "---Enc" + i +
+ ". Sending EOS empty frame for frame # " + inputFrameIndex[i]);
+ } else {
+ yuvStream[i].close();
+ yuvStream[i] = new FileInputStream(params.scaledYuvFilename);
+ bytesRead = yuvStream[i].read(srcFrame[i]);
+ }
+ }
+
+ // Convert YUV420 to NV12 if necessary
+ if (codecProperties[i].colorFormat !=
+ CodecCapabilities.COLOR_FormatYUV420Planar) {
+ srcFrame[i] = YUV420ToNV(params.frameWidth, params.frameHeight, srcFrame[i],
+ codecProperties[i].isGoogleSwCodec());
+ }
+ }
+
+ inputConsumed[i] = codec[i].feedInput(srcFrame[i], sawInputEOS[i]);
+ if (inputConsumed[i]) {
+ inputFrameIndex[i]++;
+ consumedInputEOS[i] = sawInputEOS[i];
+ bufferConsumed[i] = true;
+ }
+
+ }
+
+ // Get output from all encoders
+ for (int i = 0; i < numEncoders; i++) {
+ if (sawOutputEOS[i]) {
+ continue;
+ }
+
+ MediaEncoderOutput out = codec[i].getOutput();
+ if (out.outputGenerated) {
+ bufferConsumed[i] = true;
+ // Detect output EOS
+ if ((out.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+ Log.d(TAG, "----Enc" + i + ". Output EOS ");
+ sawOutputEOS[i] = true;
+ }
+
+ if (out.buffer.length > 0) {
+ // Save frame
+ ivf[i].writeFrame(out.buffer, out.outPresentationTimeUs);
+
+ // Update statistics - store presentation time delay in offset
+ long presentationTimeUsDelta = out.inPresentationTimeUs -
+ out.outPresentationTimeUs;
+ MediaCodec.BufferInfo bufferInfoCopy = new MediaCodec.BufferInfo();
+ bufferInfoCopy.set((int)presentationTimeUsDelta, out.buffer.length,
+ out.outPresentationTimeUs, out.flags);
+ bufferInfos.get(i).add(bufferInfoCopy);
+ }
+ }
+ }
+
+ // If codec is not ready to accept input/output - wait for buffer ready callback
+ bufferConsumedTotal = false;
+ for (boolean bufferConsumedCurrent : bufferConsumed) {
+ bufferConsumedTotal |= bufferConsumedCurrent;
+ }
+ if (!bufferConsumedTotal) {
+ // Pick the encoder to wait for
+ for (int i = 0; i < numEncoders; i++) {
+ if (!bufferConsumed[i] && !sawOutputEOS[i]) {
+ codec[i].waitForBufferEvent();
+ break;
+ }
+ }
+ }
+
+ // Check if EOS happened for all encoders
+ sawOutputEOSTotal = true;
+ for (boolean sawOutputEOSStream : sawOutputEOS) {
+ sawOutputEOSTotal &= sawOutputEOSStream;
+ }
+ }
+
+ for (int i = 0; i < numEncoders; i++) {
+ codec[i].deleteCodec();
+ ivf[i].close();
+ yuvStream[i].close();
+ if (yuvScaled[i] != null) {
+ yuvScaled[i].close();
+ }
+ }
+
+ return bufferInfos;
+ }
+
+ /**
+ * Some encoding statistics.
+ */
+ protected class Vp8EncodingStatistics {
+ Vp8EncodingStatistics() {
+ mBitrates = new ArrayList<Integer>();
+ mFrames = new ArrayList<Integer>();
+ mKeyFrames = new ArrayList<Integer>();
+ mMinimumKeyFrameInterval = Integer.MAX_VALUE;
+ }
+
+ public ArrayList<Integer> mBitrates;// Bitrate values for each second of the encoded stream.
+ public ArrayList<Integer> mFrames; // Number of frames in each second of the encoded stream.
+ public int mAverageBitrate; // Average stream bitrate.
+ public ArrayList<Integer> mKeyFrames;// Stores the position of key frames in a stream.
+ public int mAverageKeyFrameInterval; // Average key frame interval.
+ public int mMaximumKeyFrameInterval; // Maximum key frame interval.
+ public int mMinimumKeyFrameInterval; // Minimum key frame interval.
+ }
+
+ /**
+ * Calculates average bitrate and key frame interval for the encoded streams.
+ * Output mBitrates field will contain bitrate values for every second
+ * of the encoded stream.
+ * Average stream bitrate will be stored in mAverageBitrate field.
+ * mKeyFrames array will contain the position of key frames in the encoded stream and
+ * mKeyFrameInterval - average key frame interval.
+ */
+ protected Vp8EncodingStatistics computeEncodingStatistics(int encoderId,
+ ArrayList<MediaCodec.BufferInfo> bufferInfos ) {
+ Vp8EncodingStatistics statistics = new Vp8EncodingStatistics();
+
+ int totalSize = 0;
+ int frames = 0;
+ int framesPerSecond = 0;
+ int totalFrameSizePerSecond = 0;
+ int maxFrameSize = 0;
+ int currentSecond;
+ int nextSecond = 0;
+ String keyFrameList = " IFrame List: ";
+ String bitrateList = " Bitrate list: ";
+ String framesList = " FPS list: ";
+
+
+ for (int j = 0; j < bufferInfos.size(); j++) {
+ MediaCodec.BufferInfo info = bufferInfos.get(j);
+ currentSecond = (int)(info.presentationTimeUs / 1000000);
+ boolean lastFrame = (j == bufferInfos.size() - 1);
+ if (!lastFrame) {
+ nextSecond = (int)(bufferInfos.get(j+1).presentationTimeUs / 1000000);
+ }
+
+ totalSize += info.size;
+ totalFrameSizePerSecond += info.size;
+ maxFrameSize = Math.max(maxFrameSize, info.size);
+ framesPerSecond++;
+ frames++;
+
+ // Update the bitrate statistics if the next frame will
+ // be for the next second
+ if (lastFrame || nextSecond > currentSecond) {
+ int currentBitrate = totalFrameSizePerSecond * 8;
+ bitrateList += (currentBitrate + " ");
+ framesList += (framesPerSecond + " ");
+ statistics.mBitrates.add(currentBitrate);
+ statistics.mFrames.add(framesPerSecond);
+ totalFrameSizePerSecond = 0;
+ framesPerSecond = 0;
+ }
+
+ // Update key frame statistics.
+ if ((info.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0) {
+ statistics.mKeyFrames.add(j);
+ keyFrameList += (j + " ");
+ }
+ }
+ int duration = (int)(bufferInfos.get(bufferInfos.size() - 1).presentationTimeUs / 1000);
+ duration = (duration + 500) / 1000;
+ statistics.mAverageBitrate = (int)(((long)totalSize * 8) / duration);
+ Log.d(TAG, "Statistics for encoder # " + encoderId);
+ // Calculate average key frame interval in frames.
+ int keyFrames = statistics.mKeyFrames.size();
+ if (keyFrames > 1) {
+ statistics.mAverageKeyFrameInterval =
+ statistics.mKeyFrames.get(keyFrames - 1) - statistics.mKeyFrames.get(0);
+ statistics.mAverageKeyFrameInterval =
+ Math.round((float)statistics.mAverageKeyFrameInterval / (keyFrames - 1));
+ for (int j = 1; j < keyFrames; j++) {
+ int keyFrameInterval =
+ statistics.mKeyFrames.get(j) - statistics.mKeyFrames.get(j - 1);
+ statistics.mMaximumKeyFrameInterval =
+ Math.max(statistics.mMaximumKeyFrameInterval, keyFrameInterval);
+ statistics.mMinimumKeyFrameInterval =
+ Math.min(statistics.mMinimumKeyFrameInterval, keyFrameInterval);
+ }
+ Log.d(TAG, " Key frame intervals: Max: " + statistics.mMaximumKeyFrameInterval +
+ ". Min: " + statistics.mMinimumKeyFrameInterval +
+ ". Avg: " + statistics.mAverageKeyFrameInterval);
+ }
+ Log.d(TAG, " Frames: " + frames + ". Duration: " + duration +
+ ". Total size: " + totalSize + ". Key frames: " + keyFrames);
+ Log.d(TAG, keyFrameList);
+ Log.d(TAG, bitrateList);
+ Log.d(TAG, framesList);
+ Log.d(TAG, " Bitrate average: " + statistics.mAverageBitrate);
+ Log.d(TAG, " Maximum frame size: " + maxFrameSize);
+
+ return statistics;
+ }
+
+ protected Vp8EncodingStatistics computeEncodingStatistics(
+ ArrayList<MediaCodec.BufferInfo> bufferInfos ) {
+ return computeEncodingStatistics(0, bufferInfos);
+ }
+
+ protected ArrayList<Vp8EncodingStatistics> computeSimulcastEncodingStatistics(
+ ArrayList<ArrayList<MediaCodec.BufferInfo>> bufferInfos) {
+ int numCodecs = bufferInfos.size();
+ ArrayList<Vp8EncodingStatistics> statistics = new ArrayList<Vp8EncodingStatistics>();
+
+ for (int i = 0; i < numCodecs; i++) {
+ Vp8EncodingStatistics currentStatistics =
+ computeEncodingStatistics(i, bufferInfos.get(i));
+ statistics.add(currentStatistics);
+ }
+ return statistics;
+ }
+
+ /**
+ * Calculates maximum latency for encoder/decoder based on buffer info array
+ * generated either by encoder or decoder.
+ */
+ protected int maxPresentationTimeDifference(ArrayList<MediaCodec.BufferInfo> bufferInfos) {
+ int maxValue = 0;
+ for (MediaCodec.BufferInfo bufferInfo : bufferInfos) {
+ maxValue = Math.max(maxValue, bufferInfo.offset);
+ }
+ maxValue = (maxValue + 500) / 1000; // mcs -> ms
+ return maxValue;
+ }
+
+ /**
+ * Decoding PSNR statistics.
+ */
+ protected class Vp8DecodingStatistics {
+ Vp8DecodingStatistics() {
+ mMinimumPSNR = Integer.MAX_VALUE;
+ }
+ public double mAveragePSNR;
+ public double mMinimumPSNR;
+ }
+
+ /**
+ * Calculates PSNR value between two video frames.
+ */
+ private double computePSNR(byte[] data0, byte[] data1) {
+ long squareError = 0;
+ assertTrue(data0.length == data1.length);
+ int length = data0.length;
+ for (int i = 0 ; i < length; i++) {
+ int diff = ((int)data0[i] & 0xff) - ((int)data1[i] & 0xff);
+ squareError += diff * diff;
+ }
+ double meanSquareError = (double)squareError / length;
+ double psnr = 10 * Math.log10((double)255 * 255 / meanSquareError);
+ return psnr;
+ }
+
+ /**
+ * Calculates average and minimum PSNR values between
+ * set of reference and decoded video frames.
+ * Runs PSNR calculation for the full duration of the decoded data.
+ */
+ protected Vp8DecodingStatistics computeDecodingStatistics(
+ String referenceYuvFilename,
+ int referenceYuvRawId,
+ String decodedYuvFilename,
+ int width,
+ int height) throws Exception {
+ Vp8DecodingStatistics statistics = new Vp8DecodingStatistics();
+ InputStream referenceStream =
+ OpenFileOrResourceId(referenceYuvFilename, referenceYuvRawId);
+ InputStream decodedStream = new FileInputStream(decodedYuvFilename);
+
+ int ySize = width * height;
+ int uvSize = width * height / 4;
+ byte[] yRef = new byte[ySize];
+ byte[] yDec = new byte[ySize];
+ byte[] uvRef = new byte[uvSize];
+ byte[] uvDec = new byte[uvSize];
+
+ int frames = 0;
+ double averageYPSNR = 0;
+ double averageUPSNR = 0;
+ double averageVPSNR = 0;
+ double minimumYPSNR = Integer.MAX_VALUE;
+ double minimumUPSNR = Integer.MAX_VALUE;
+ double minimumVPSNR = Integer.MAX_VALUE;
+ int minimumPSNRFrameIndex = 0;
+
+ while (true) {
+ // Calculate Y PSNR.
+ int bytesReadRef = referenceStream.read(yRef);
+ int bytesReadDec = decodedStream.read(yDec);
+ if (bytesReadDec == -1) {
+ break;
+ }
+ if (bytesReadRef == -1) {
+ // Reference file wrapping up
+ referenceStream.close();
+ referenceStream =
+ OpenFileOrResourceId(referenceYuvFilename, referenceYuvRawId);
+ bytesReadRef = referenceStream.read(yRef);
+ }
+ double curYPSNR = computePSNR(yRef, yDec);
+ averageYPSNR += curYPSNR;
+ minimumYPSNR = Math.min(minimumYPSNR, curYPSNR);
+ double curMinimumPSNR = curYPSNR;
+
+ // Calculate U PSNR.
+ bytesReadRef = referenceStream.read(uvRef);
+ bytesReadDec = decodedStream.read(uvDec);
+ double curUPSNR = computePSNR(uvRef, uvDec);
+ averageUPSNR += curUPSNR;
+ minimumUPSNR = Math.min(minimumUPSNR, curUPSNR);
+ curMinimumPSNR = Math.min(curMinimumPSNR, curUPSNR);
+
+ // Calculate V PSNR.
+ bytesReadRef = referenceStream.read(uvRef);
+ bytesReadDec = decodedStream.read(uvDec);
+ double curVPSNR = computePSNR(uvRef, uvDec);
+ averageVPSNR += curVPSNR;
+ minimumVPSNR = Math.min(minimumVPSNR, curVPSNR);
+ curMinimumPSNR = Math.min(curMinimumPSNR, curVPSNR);
+
+ // Frame index for minimum PSNR value - help to detect possible distortions
+ if (curMinimumPSNR < statistics.mMinimumPSNR) {
+ statistics.mMinimumPSNR = curMinimumPSNR;
+ minimumPSNRFrameIndex = frames;
+ }
+
+ String logStr = String.format(Locale.US, "PSNR #%d: Y: %.2f. U: %.2f. V: %.2f",
+ frames, curYPSNR, curUPSNR, curVPSNR);
+ Log.v(TAG, logStr);
+
+ frames++;
+ }
+
+ averageYPSNR /= frames;
+ averageUPSNR /= frames;
+ averageVPSNR /= frames;
+ statistics.mAveragePSNR = (4 * averageYPSNR + averageUPSNR + averageVPSNR) / 6;
+
+ Log.d(TAG, "PSNR statistics for " + frames + " frames.");
+ String logStr = String.format(Locale.US,
+ "Average PSNR: Y: %.1f. U: %.1f. V: %.1f. Average: %.1f",
+ averageYPSNR, averageUPSNR, averageVPSNR, statistics.mAveragePSNR);
+ Log.d(TAG, logStr);
+ logStr = String.format(Locale.US,
+ "Minimum PSNR: Y: %.1f. U: %.1f. V: %.1f. Overall: %.1f at frame %d",
+ minimumYPSNR, minimumUPSNR, minimumVPSNR,
+ statistics.mMinimumPSNR, minimumPSNRFrameIndex);
+ Log.d(TAG, logStr);
+
+ referenceStream.close();
+ decodedStream.close();
+ return statistics;
+ }
+}
+
diff --git a/tests/tests/media/src/android/media/cts/Vp8EncoderTest.java b/tests/tests/media/src/android/media/cts/Vp8EncoderTest.java
index 14e4055..7f51a64 100644
--- a/tests/tests/media/src/android/media/cts/Vp8EncoderTest.java
+++ b/tests/tests/media/src/android/media/cts/Vp8EncoderTest.java
@@ -16,594 +16,335 @@
package android.media.cts;
-import android.content.Context;
-import android.content.res.Resources;
import android.media.MediaCodec;
-import android.media.MediaCodecInfo.CodecCapabilities;
-import android.media.MediaFormat;
-import android.os.Bundle;
-import android.test.AndroidTestCase;
import android.util.Log;
-
import com.android.cts.media.R;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
/**
- * Basic verification test for vp8 encoder.
+ * Verification test for vp8 encoder and decoder.
*
- * A raw yv12 stream is encoded and written to an IVF
- * file, which is later decoded by vp8 decoder to verify
- * frames are at least decodable.
+ * A raw yv12 stream is encoded at various settings and written to an IVF
+ * file. Encoded stream bitrate and key frame interval are checked against target values.
+ * The stream is later decoded by vp8 decoder to verify frames are decodable and to
+ * calculate PSNR values for various bitrates.
*/
-public class Vp8EncoderTest extends AndroidTestCase {
+public class Vp8EncoderTest extends Vp8CodecTestBase {
- private static final String TAG = "VP8EncoderTest";
- private static final String VP8_MIME = "video/x-vnd.on2.vp8";
- private static final String VPX_DECODER_NAME = "OMX.google.vp8.decoder";
- private static final String VPX_ENCODER_NAME = "OMX.google.vp8.encoder";
- private static final String BASIC_IVF = "video_176x144_vp8_basic.ivf";
- private static final long DEFAULT_TIMEOUT_US = 5000;
+ private static final String ENCODED_IVF_BASE = "football";
+ private static final String INPUT_YUV = null;
+ private static final String OUTPUT_YUV = SDCARD_DIR + File.separator +
+ ENCODED_IVF_BASE + "_out.yuv";
- private Resources mResources;
- private MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo();
- private ByteBuffer[] mInputBuffers;
- private ByteBuffer[] mOutputBuffers;
-
- @Override
- public void setContext(Context context) {
- super.setContext(context);
- mResources = mContext.getResources();
- }
+ // YUV stream properties.
+ private static final int WIDTH = 320;
+ private static final int HEIGHT = 240;
+ private static final int FPS = 30;
+ // Default encoding bitrate.
+ private static final int BITRATE = 400000;
+ // Default encoding bitrate mode
+ private static final int BITRATE_MODE = VIDEO_ControlRateVariable;
+ // List of bitrates used in quality and basic bitrate tests.
+ private static final int[] TEST_BITRATES_SET = { 300000, 500000, 700000, 900000 };
+ // Maximum allowed bitrate variation from the target value.
+ private static final double MAX_BITRATE_VARIATION = 0.2;
+ // Average PSNR values for reference SW VP8 codec for the above bitrates.
+ private static final double[] REFERENCE_AVERAGE_PSNR = { 33.1, 35.2, 36.6, 37.8 };
+ // Minimum PSNR values for reference SW VP8 codec for the above bitrates.
+ private static final double[] REFERENCE_MINIMUM_PSNR = { 25.9, 27.5, 28.4, 30.3 };
+ // Maximum allowed average PSNR difference of HW encoder comparing to reference SW encoder.
+ private static final double MAX_AVERAGE_PSNR_DIFFERENCE = 2;
+ // Maximum allowed minimum PSNR difference of HW encoder comparing to reference SW encoder.
+ private static final double MAX_MINIMUM_PSNR_DIFFERENCE = 4;
+ // Maximum allowed average PSNR difference of the encoder running in a looper thread with 0 ms
+ // buffer dequeue timeout comparing to the encoder running in a callee's thread with 100 ms
+ // buffer dequeue timeout.
+ private static final double MAX_ASYNC_AVERAGE_PSNR_DIFFERENCE = 0.5;
+ // Maximum allowed minimum PSNR difference of the encoder running in a looper thread
+ // comparing to the encoder running in a callee's thread.
+ private static final double MAX_ASYNC_MINIMUM_PSNR_DIFFERENCE = 2;
+ // Maximum allowed average key frame interval variation from the target value.
+ private static final int MAX_AVERAGE_KEYFRAME_INTERVAL_VARIATION = 1;
+ // Maximum allowed key frame interval variation from the target value.
+ private static final int MAX_KEYFRAME_INTERVAL_VARIATION = 3;
/**
* A basic test for VP8 encoder.
*
- * Encodes a raw stream with default configuration options,
+ * Encodes 9 seconds of raw stream with default configuration options,
* and then decodes it to verify the bitstream.
+ * Also checks the average bitrate is within MAX_BITRATE_VARIATION of the target value.
*/
public void testBasic() throws Exception {
- encode(BASIC_IVF,
- R.raw.video_176x144_yv12,
- 176, // width
- 144, // height
- 30); // framerate
- decode(BASIC_IVF);
+ int encodeSeconds = 9;
+
+ for (int targetBitrate : TEST_BITRATES_SET) {
+ EncoderOutputStreamParameters params = getDefaultEncodingParameters(
+ INPUT_YUV,
+ ENCODED_IVF_BASE,
+ encodeSeconds,
+ WIDTH,
+ HEIGHT,
+ FPS,
+ BITRATE_MODE,
+ targetBitrate,
+ true);
+ ArrayList<MediaCodec.BufferInfo> bufInfo = encode(params);
+ Vp8EncodingStatistics statistics = computeEncodingStatistics(bufInfo);
+
+ assertEquals("Stream bitrate " + statistics.mAverageBitrate +
+ " is different from the target " + targetBitrate,
+ targetBitrate, statistics.mAverageBitrate,
+ MAX_BITRATE_VARIATION * targetBitrate);
+
+ decode(params.outputIvfFilename, null, FPS, params.forceSwEncoder);
+ }
+ }
+
+ /**
+ * Asynchronous encoding test for VP8 encoder.
+ *
+ * Encodes 9 seconds of raw stream using synchronous and asynchronous calls.
+ * Checks the PSNR difference between the encoded and decoded output and reference yuv input
+ * does not change much for two different ways of the encoder call.
+ */
+ public void FIXME_testAsyncEncoding() throws Exception {
+ int encodeSeconds = 9;
+
+ // First test the encoder running in a looper thread with buffer callbacks enabled.
+ boolean syncEncoding = false;
+ EncoderOutputStreamParameters params = getDefaultEncodingParameters(
+ INPUT_YUV,
+ ENCODED_IVF_BASE,
+ encodeSeconds,
+ WIDTH,
+ HEIGHT,
+ FPS,
+ BITRATE_MODE,
+ BITRATE,
+ syncEncoding);
+ ArrayList<MediaCodec.BufferInfo> bufInfos = encode(params);
+ computeEncodingStatistics(bufInfos);
+ decode(params.outputIvfFilename, OUTPUT_YUV, FPS, params.forceSwEncoder);
+ Vp8DecodingStatistics statisticsAsync = computeDecodingStatistics(
+ params.inputYuvFilename, R.raw.football_qvga, OUTPUT_YUV,
+ params.frameWidth, params.frameHeight);
+
+
+ // Test the encoder running in a callee's thread.
+ syncEncoding = true;
+ params = getDefaultEncodingParameters(
+ INPUT_YUV,
+ ENCODED_IVF_BASE,
+ encodeSeconds,
+ WIDTH,
+ HEIGHT,
+ FPS,
+ BITRATE_MODE,
+ BITRATE,
+ syncEncoding);
+ bufInfos = encode(params);
+ computeEncodingStatistics(bufInfos);
+ decode(params.outputIvfFilename, OUTPUT_YUV, FPS, params.forceSwEncoder);
+ Vp8DecodingStatistics statisticsSync = computeDecodingStatistics(
+ params.inputYuvFilename, R.raw.football_qvga, OUTPUT_YUV,
+ params.frameWidth, params.frameHeight);
+
+ // Check PSNR difference.
+ Log.d(TAG, "PSNR Average: Async: " + statisticsAsync.mAveragePSNR +
+ ". Sync: " + statisticsSync.mAveragePSNR);
+ Log.d(TAG, "PSNR Minimum: Async: " + statisticsAsync.mMinimumPSNR +
+ ". Sync: " + statisticsSync.mMinimumPSNR);
+ if ((Math.abs(statisticsAsync.mAveragePSNR - statisticsSync.mAveragePSNR) >
+ MAX_ASYNC_AVERAGE_PSNR_DIFFERENCE) ||
+ (Math.abs(statisticsAsync.mMinimumPSNR - statisticsSync.mMinimumPSNR) >
+ MAX_ASYNC_MINIMUM_PSNR_DIFFERENCE)) {
+ throw new RuntimeException("Difference between PSNRs for async and sync encoders");
+ }
}
/**
* Check if MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME is honored.
*
- * At frame 15, request a sync frame. If one does not occur by EOF the
- * encoder fails. The test does not verify the output stream.
+ * Encodes 9 seconds of raw stream and requests a sync frame every second (30 frames).
+ * The test does not verify the output stream.
*/
public void testSyncFrame() throws Exception {
- encodeSyncFrame(R.raw.video_176x144_yv12,
- 176, // width
- 144, // height
- 30); // framerate
+ int encodeSeconds = 9;
+
+ EncoderOutputStreamParameters params = getDefaultEncodingParameters(
+ INPUT_YUV,
+ ENCODED_IVF_BASE,
+ encodeSeconds,
+ WIDTH,
+ HEIGHT,
+ FPS,
+ BITRATE_MODE,
+ BITRATE,
+ true);
+ params.syncFrameInterval = encodeSeconds * FPS;
+ params.syncForceFrameInterval = FPS;
+ ArrayList<MediaCodec.BufferInfo> bufInfo = encode(params);
+ Vp8EncodingStatistics statistics = computeEncodingStatistics(bufInfo);
+
+ // First check if we got expected number of key frames.
+ int actualKeyFrames = statistics.mKeyFrames.size();
+ if (actualKeyFrames != encodeSeconds) {
+ throw new RuntimeException("Number of key frames " + actualKeyFrames +
+ " is different from the expected " + encodeSeconds);
+ }
+
+ // Check key frame intervals:
+ // Average value should be within +/- 1 frame of the target value,
+ // maximum value should not be greater than target value + 3,
+ // and minimum value should not be less that target value - 3.
+ if (Math.abs(statistics.mAverageKeyFrameInterval - FPS) >
+ MAX_AVERAGE_KEYFRAME_INTERVAL_VARIATION ||
+ (statistics.mMaximumKeyFrameInterval - FPS > MAX_KEYFRAME_INTERVAL_VARIATION) ||
+ (FPS - statistics.mMinimumKeyFrameInterval > MAX_KEYFRAME_INTERVAL_VARIATION)) {
+ throw new RuntimeException(
+ "Key frame intervals are different from the expected " + FPS);
+ }
}
/**
* Check if MediaCodec.PARAMETER_KEY_VIDEO_BITRATE is honored.
*
- * Run the sample multiple times. Request periodic changes to the
- * bitrate and ensure the encoder responds.
+ * Run the the encoder for 12 seconds. Request changes to the
+ * bitrate after 6 seconds and ensure the encoder responds.
*/
- public void testVariableBitrate() throws Exception {
- encodeVariableBitrate(R.raw.video_176x144_yv12,
- 176, // width
- 144, // height
- 30); // framerate
- }
+ public void testDynamicBitrateChange() throws Exception {
+ int encodeSeconds = 12; // Encoding sequence duration in seconds.
+ int[] bitrateTargetValues = { 400000, 800000 }; // List of bitrates to test.
- /**
- * A basic check if an encoded stream is decodable.
- *
- * The most basic confirmation we can get about a frame
- * being properly encoded is trying to decode it.
- * (Especially in realtime mode encode output is non-
- * deterministic, therefore a more thorough check like
- * md5 sum comparison wouldn't work.)
- *
- * Indeed, MediaCodec will raise an IllegalStateException
- * whenever vp8 decoder fails to decode a frame, and
- * this test uses that fact to verify the bitstream.
- *
- * @param filename The name of the IVF file containing encoded bitsream.
- */
- private void decode(String filename) throws Exception {
- IvfReader ivf = null;
- try {
- ivf = new IvfReader(filename);
- int frameWidth = ivf.getWidth();
- int frameHeight = ivf.getHeight();
- int frameCount = ivf.getFrameCount();
+ EncoderOutputStreamParameters params = getDefaultEncodingParameters(
+ INPUT_YUV,
+ ENCODED_IVF_BASE,
+ encodeSeconds,
+ WIDTH,
+ HEIGHT,
+ FPS,
+ BITRATE_MODE,
+ bitrateTargetValues[0],
+ true);
- assertTrue(frameWidth > 0);
- assertTrue(frameHeight > 0);
- assertTrue(frameCount > 0);
+ // Number of seconds for each bitrate
+ int stepSeconds = encodeSeconds / bitrateTargetValues.length;
+ // Fill the bitrates values.
+ params.bitrateSet = new int[encodeSeconds * FPS];
+ for (int i = 0; i < bitrateTargetValues.length ; i++) {
+ Arrays.fill(params.bitrateSet,
+ i * encodeSeconds * FPS / bitrateTargetValues.length,
+ (i + 1) * encodeSeconds * FPS / bitrateTargetValues.length,
+ bitrateTargetValues[i]);
+ }
- MediaFormat format = MediaFormat.createVideoFormat(VP8_MIME,
- ivf.getWidth(),
- ivf.getHeight());
+ ArrayList<MediaCodec.BufferInfo> bufInfo = encode(params);
+ Vp8EncodingStatistics statistics = computeEncodingStatistics(bufInfo);
- Log.d(TAG, "Creating decoder");
- MediaCodec decoder = MediaCodec.createByCodecName(VPX_DECODER_NAME);
- decoder.configure(format,
- null, // surface
- null, // crypto
- 0); // flags
- decoder.start();
-
- mInputBuffers = decoder.getInputBuffers();
- mOutputBuffers = decoder.getOutputBuffers();
-
- // decode loop
- int frameIndex = 0;
- boolean sawOutputEOS = false;
- boolean sawInputEOS = false;
-
- while (!sawOutputEOS) {
- if (!sawInputEOS) {
- int inputBufIndex = decoder.dequeueInputBuffer(DEFAULT_TIMEOUT_US);
- if (inputBufIndex >= 0) {
- byte[] frame = ivf.readFrame(frameIndex);
-
- if (frameIndex == frameCount - 1) {
- sawInputEOS = true;
- }
-
- mInputBuffers[inputBufIndex].clear();
- mInputBuffers[inputBufIndex].put(frame);
- mInputBuffers[inputBufIndex].rewind();
-
- Log.d(TAG, "Decoding frame at index " + frameIndex);
- try {
- decoder.queueInputBuffer(
- inputBufIndex,
- 0, // offset
- frame.length,
- frameIndex,
- sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
- } catch (IllegalStateException ise) {
- //That is all what is passed from MediaCodec in case of
- //decode failure.
- fail("Failed to decode frame at index " + frameIndex);
- }
- frameIndex++;
- }
- }
-
- int result = decoder.dequeueOutputBuffer(mBufferInfo, DEFAULT_TIMEOUT_US);
- if (result >= 0) {
- int outputBufIndex = result;
- if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
- sawOutputEOS = true;
- }
- decoder.releaseOutputBuffer(outputBufIndex, false);
- } else if (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
- mOutputBuffers = decoder.getOutputBuffers();
- }
+ // Calculate actual average bitrates for every [stepSeconds] second.
+ int[] bitrateActualValues = new int[bitrateTargetValues.length];
+ for (int i = 0; i < bitrateTargetValues.length ; i++) {
+ bitrateActualValues[i] = 0;
+ for (int j = i * stepSeconds; j < (i + 1) * stepSeconds; j++) {
+ bitrateActualValues[i] += statistics.mBitrates.get(j);
}
- decoder.stop();
- decoder.release();
- } finally {
- if (ivf != null) {
- ivf.close();
+ bitrateActualValues[i] /= stepSeconds;
+ Log.d(TAG, "Actual bitrate for interval #" + i + " : " + bitrateActualValues[i] +
+ ". Target: " + bitrateTargetValues[i]);
+
+ // Compare actual bitrate values to make sure at least same increasing/decreasing
+ // order as the target bitrate values.
+ for (int j = 0; j < i; j++) {
+ long differenceTarget = bitrateTargetValues[i] - bitrateTargetValues[j];
+ long differenceActual = bitrateActualValues[i] - bitrateActualValues[j];
+ if (differenceTarget * differenceActual < 0) {
+ throw new RuntimeException("Target bitrates: " +
+ bitrateTargetValues[j] + " , " + bitrateTargetValues[i] +
+ ". Actual bitrates: "
+ + bitrateActualValues[j] + " , " + bitrateActualValues[i]);
+ }
}
}
}
/**
- * A basic vp8 encode loop.
+ * Check the encoder quality for various bitrates by calculating PSNR
*
- * MediaCodec will raise an IllegalStateException
- * whenever vp8 encoder fails to encode a frame.
- *
- * In addition to that written IVF file can be tested
- * to be decodable in order to verify the bitstream produced.
- *
- * Color format of input file should be YUV420, and frameWidth,
- * frameHeight should be supplied correctly as raw input file doesn't
- * include any header data.
- *
- * @param outputFilename The name of the IVF file to write encoded bitsream
- * @param rawInputFd File descriptor for the raw input file (YUV420)
- * @param frameWidth Frame width of input file
- * @param frameHeight Frame height of input file
- * @param frameRate Frame rate of input file in frames per second
+ * Run the the encoder for 9 seconds for each bitrate and calculate PSNR
+ * for each encoded stream.
+ * Video streams with higher bitrates should have higher PSNRs.
+ * Also compares average and minimum PSNR of HW codec with PSNR values of reference SW codec.
*/
- private void encode(String outputFilename, int rawInputFd,
- int frameWidth, int frameHeight, int frameRate) throws Exception {
- // Create a media format signifying desired output
- MediaFormat format = MediaFormat.createVideoFormat(VP8_MIME, frameWidth, frameHeight);
- format.setInteger(MediaFormat.KEY_BIT_RATE, 100000);
- format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
- CodecCapabilities.COLOR_FormatYUV420Planar);
- format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
+ public void testEncoderQuality() throws Exception {
+ int encodeSeconds = 9; // Encoding sequence duration in seconds for each bitrate.
+ double[] psnrPlatformCodecAverage = new double[TEST_BITRATES_SET.length];
+ double[] psnrPlatformCodecMin = new double[TEST_BITRATES_SET.length];
- Log.d(TAG, "Creating encoder");
- MediaCodec encoder;
- encoder = MediaCodec.createByCodecName(VPX_ENCODER_NAME);
- encoder.configure(format,
- null, // surface
- null, // crypto
- MediaCodec.CONFIGURE_FLAG_ENCODE);
- encoder.start();
+ // Run platform specific encoder for different bitrates
+ // and compare PSNR of hw codec with PSNR of reference sw codec.
+ for (int i = 0; i < TEST_BITRATES_SET.length; i++) {
+ EncoderOutputStreamParameters params = getDefaultEncodingParameters(
+ INPUT_YUV,
+ ENCODED_IVF_BASE,
+ encodeSeconds,
+ WIDTH,
+ HEIGHT,
+ FPS,
+ BITRATE_MODE,
+ TEST_BITRATES_SET[i],
+ true);
+ encode(params);
- mInputBuffers = encoder.getInputBuffers();
- mOutputBuffers = encoder.getOutputBuffers();
+ decode(params.outputIvfFilename, OUTPUT_YUV, FPS, params.forceSwEncoder);
+ Vp8DecodingStatistics statistics = computeDecodingStatistics(
+ params.inputYuvFilename, R.raw.football_qvga, OUTPUT_YUV,
+ params.frameWidth, params.frameHeight);
+ psnrPlatformCodecAverage[i] = statistics.mAveragePSNR;
+ psnrPlatformCodecMin[i] = statistics.mMinimumPSNR;
+ }
- InputStream rawStream = null;
- IvfWriter ivf = null;
-
- try {
- rawStream = mResources.openRawResource(rawInputFd);
- ivf = new IvfWriter(outputFilename, frameWidth, frameHeight);
- // encode loop
- long presentationTimeUs = 0;
- int inputFrameIndex = 0;
- int outputFrameIndex = 0;
- boolean sawInputEOS = false;
- boolean sawOutputEOS = false;
-
- while (!sawOutputEOS) {
- if (!sawInputEOS) {
- int inputBufIndex = encoder.dequeueInputBuffer(DEFAULT_TIMEOUT_US);
- if (inputBufIndex >= 0) {
- // YUV420 has 3 planes. Y is full size. U and V are each half size (1/4 the
- // pixels).
- int frameSize = frameWidth * frameHeight * 3 / 2;
-
- byte[] frame = new byte[frameSize];
- int bytesRead = rawStream.read(frame);
-
- if (bytesRead == -1) {
- sawInputEOS = true;
- bytesRead = 0;
- }
-
- mInputBuffers[inputBufIndex].clear();
- mInputBuffers[inputBufIndex].put(frame);
- mInputBuffers[inputBufIndex].rewind();
-
- presentationTimeUs = (inputFrameIndex * 1000000) / frameRate;
- Log.d(TAG, "Encoding frame at index " + inputFrameIndex);
- encoder.queueInputBuffer(
- inputBufIndex,
- 0, // offset
- bytesRead, // size
- presentationTimeUs,
- sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
-
- inputFrameIndex++;
- }
+ // First do a sanity check - higher bitrates should results in higher PSNR.
+ for (int i = 1; i < TEST_BITRATES_SET.length ; i++) {
+ for (int j = 0; j < i; j++) {
+ double differenceBitrate = TEST_BITRATES_SET[i] - TEST_BITRATES_SET[j];
+ double differencePSNR = psnrPlatformCodecAverage[i] - psnrPlatformCodecAverage[j];
+ if (differenceBitrate * differencePSNR < 0) {
+ throw new RuntimeException("Target bitrates: " +
+ TEST_BITRATES_SET[j] + ", " + TEST_BITRATES_SET[i] +
+ ". Actual PSNRs: "
+ + psnrPlatformCodecAverage[j] + ", " + psnrPlatformCodecAverage[i]);
}
-
- int result = encoder.dequeueOutputBuffer(mBufferInfo, DEFAULT_TIMEOUT_US);
- if (result >= 0) {
- int outputBufIndex = result;
- byte[] buffer = new byte[mBufferInfo.size];
- mOutputBuffers[outputBufIndex].rewind();
- mOutputBuffers[outputBufIndex].get(buffer, 0, mBufferInfo.size);
-
- if ((outputFrameIndex == 0)
- && ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) == 0)) {
- throw new RuntimeException("First frame is not a sync frame.");
-
- }
-
- if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
- sawOutputEOS = true;
- } else {
- ivf.writeFrame(buffer, mBufferInfo.presentationTimeUs);
- }
- encoder.releaseOutputBuffer(outputBufIndex,
- false); // render
-
- outputFrameIndex++;
- } else if (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
- mOutputBuffers = encoder.getOutputBuffers();
- }
- }
-
- encoder.stop();
- encoder.release();
- } finally {
- if (ivf != null) {
- ivf.close();
- }
-
- if (rawStream != null) {
- rawStream.close();
}
}
- }
-
- /**
- * Request Sync Frames
- *
- * MediaCodec will raise an IllegalStateException
- * whenever vp8 encoder fails to encode a frame.
- *
- * This presumes a file with 28 frames. Under normal circumstances there
- * would only be one sync frame: the first one. This test will request an
- * additional sync frame at 15 and ensure that it occurs by EOF.
- *
- * Color format of input file should be YUV420, and frameWidth,
- * frameHeight should be supplied correctly as raw input file doesn't
- * include any header data.
- *
- * @param rawInputFd File descriptor for the raw input file (YUV420)
- * @param frameWidth Frame width of input file
- * @param frameHeight Frame height of input file
- * @param frameRate Frame rate of input file in frames per second
- */
- private void encodeSyncFrame(int rawInputFd, int frameWidth,
- int frameHeight, int frameRate) throws Exception {
- // Create a media format signifying desired output
- MediaFormat format = MediaFormat.createVideoFormat(VP8_MIME, frameWidth, frameHeight);
- format.setInteger(MediaFormat.KEY_BIT_RATE, 100000);
- format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
- CodecCapabilities.COLOR_FormatYUV420Planar);
- format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
-
- Log.d(TAG, "Creating encoder");
- MediaCodec encoder;
- encoder = MediaCodec.createByCodecName(VPX_ENCODER_NAME);
- encoder.configure(format,
- null, // surface
- null, // crypto
- MediaCodec.CONFIGURE_FLAG_ENCODE);
- encoder.start();
-
- mInputBuffers = encoder.getInputBuffers();
- mOutputBuffers = encoder.getOutputBuffers();
-
- InputStream rawStream = null;
-
- try {
- rawStream = mResources.openRawResource(rawInputFd);
- // encode loop
- long presentationTimeUs = 0;
- int inputFrameIndex = 0;
- boolean sawInputEOS = false;
- boolean sawOutputEOS = false;
- boolean syncFrameRequested = false;
- boolean matchedSyncFrame = false;
-
- while (!sawOutputEOS) {
- if (!sawInputEOS) {
- int inputBufIndex = encoder.dequeueInputBuffer(DEFAULT_TIMEOUT_US);
- if (inputBufIndex >= 0) {
- int frameSize = frameWidth * frameHeight * 3 / 2;
-
- byte[] frame = new byte[frameSize];
- int bytesRead = rawStream.read(frame);
-
- if (bytesRead == -1) {
- sawInputEOS = true;
- bytesRead = 0;
- }
-
- mInputBuffers[inputBufIndex].clear();
- mInputBuffers[inputBufIndex].put(frame);
- mInputBuffers[inputBufIndex].rewind();
-
- if (inputFrameIndex == 15) {
- Log.d(TAG, "Requesting sync frame at index " + inputFrameIndex);
- Bundle syncFrame = new Bundle();
- syncFrame.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0);
- encoder.setParameters(syncFrame);
- syncFrameRequested = true;
- }
-
- presentationTimeUs = (inputFrameIndex * 1000000) / frameRate;
- encoder.queueInputBuffer(
- inputBufIndex,
- 0, // offset
- bytesRead, // size
- presentationTimeUs,
- sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
-
- inputFrameIndex++;
- }
- }
-
- int result = encoder.dequeueOutputBuffer(mBufferInfo, DEFAULT_TIMEOUT_US);
- if (result >= 0) {
- if (syncFrameRequested && ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0)) {
- Log.d(TAG, "Found sync frame");
- matchedSyncFrame = true;
- }
-
- if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
- sawOutputEOS = true;
- }
-
- encoder.releaseOutputBuffer(result,
- false); // render
-
- } else if (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
- mOutputBuffers = encoder.getOutputBuffers();
- }
+ // Then compare average and minimum PSNR of platform codec with reference sw codec -
+ // average PSNR for platform codec should be no more than 2 dB less than reference PSNR
+ // and minumum PSNR - no more than 4 dB less than reference minimum PSNR.
+ // These PSNR difference numbers are arbitrary for now, will need further estimation
+ // when more devices with hw VP8 codec will appear.
+ for (int i = 0; i < TEST_BITRATES_SET.length ; i++) {
+ Log.d(TAG, "Bitrate " + TEST_BITRATES_SET[i]);
+ Log.d(TAG, "Reference: Average: " + REFERENCE_AVERAGE_PSNR[i] + ". Minimum: " +
+ REFERENCE_MINIMUM_PSNR[i]);
+ Log.d(TAG, "Platform: Average: " + psnrPlatformCodecAverage[i] + ". Minimum: " +
+ psnrPlatformCodecMin[i]);
+ if (psnrPlatformCodecAverage[i] < REFERENCE_AVERAGE_PSNR[i] -
+ MAX_AVERAGE_PSNR_DIFFERENCE) {
+ throw new RuntimeException("Low average PSNR " + psnrPlatformCodecAverage[i] +
+ " comparing to reference PSNR " + REFERENCE_AVERAGE_PSNR[i] +
+ " for bitrate " + TEST_BITRATES_SET[i]);
}
-
- if (!matchedSyncFrame) {
- throw new RuntimeException("Requested sync frame did not occur");
- }
-
- encoder.stop();
- encoder.release();
- } finally {
- if (rawStream != null) {
- rawStream.close();
- }
- }
- }
-
-
- /**
- * Adjust bitrate
- *
- * MediaCodec will raise an IllegalStateException
- * whenever vp8 encoder fails to encode a frame.
- *
- * Encode the file three times: once at the initial bitrate, once at an
- * increased bitrate, and once at a decreased bitrate. Record the frame
- * sizes that are returned and verify a strict ordering.
- *
- * Color format of input file should be YUV420, and frameWidth,
- * frameHeight should be supplied correctly as raw input file doesn't
- * include any header data.
- *
- * @param rawInputFd File descriptor for the raw input file (YUV420)
- * @param frameWidth Frame width of input file
- * @param frameHeight Frame height of input file
- * @param frameRate Frame rate of input file in frames per second
- */
- private void encodeVariableBitrate(int rawInputFd, int frameWidth,
- int frameHeight, int frameRate) throws Exception {
- // Create a media format signifying desired output
- MediaFormat format = MediaFormat.createVideoFormat(VP8_MIME, frameWidth, frameHeight);
- format.setInteger(MediaFormat.KEY_BIT_RATE, 75000);
- format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
- CodecCapabilities.COLOR_FormatYUV420Planar);
- format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
-
- Log.d(TAG, "Creating encoder");
- MediaCodec encoder;
- encoder = MediaCodec.createByCodecName(VPX_ENCODER_NAME);
- encoder.configure(format,
- null, // surface
- null, // crypto
- MediaCodec.CONFIGURE_FLAG_ENCODE);
- encoder.start();
-
- mInputBuffers = encoder.getInputBuffers();
- mOutputBuffers = encoder.getOutputBuffers();
-
- InputStream rawStream = null;
-
- int iteration = 0;
- int[] bits = new int[100];
-
- try {
- rawStream = mResources.openRawResource(rawInputFd);
- /* Doc says this is not the default:
- * http://developer.android.com/reference/java/io/InputStream.html#markSupported()
- * but it returns true so using .reset() instead of close/open
- */
- if (rawStream.markSupported()) Log.d(TAG, "Stream marking supported");
- rawStream.mark(1000000);
-
- // encode loop
- long presentationTimeUs = 0;
- int inputFrameIndex = 0;
- int outputFrameIndex = 0;
- boolean sawInputEOS = false;
- boolean sawOutputEOS = false;
-
- while (!sawOutputEOS) {
- if (!sawInputEOS) {
- int inputBufIndex = encoder.dequeueInputBuffer(DEFAULT_TIMEOUT_US);
- if (inputBufIndex >= 0) {
- int frameSize = frameWidth * frameHeight * 3 / 2;
-
- byte[] frame = new byte[frameSize];
- int bytesRead = rawStream.read(frame);
-
- if (bytesRead == -1) {
- if (iteration < 2) {
- rawStream.reset();
- Bundle bitrate = new Bundle();
- if (iteration == 0) {
- bitrate.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, 150000);
- Log.d(TAG, "Setting bitrate to 150000");
- } else {
- bitrate.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, 25000);
- Log.d(TAG, "Setting bitrate to 25000");
- }
- encoder.setParameters(bitrate);
-
- iteration++;
- continue;
- } else {
- sawInputEOS = true;
- bytesRead = 0;
- }
- }
-
- mInputBuffers[inputBufIndex].clear();
- mInputBuffers[inputBufIndex].put(frame);
- mInputBuffers[inputBufIndex].rewind();
-
- presentationTimeUs = (inputFrameIndex * 1000000) / frameRate;
- encoder.queueInputBuffer(
- inputBufIndex,
- 0, // offset
- bytesRead, // size
- presentationTimeUs,
- sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
-
- inputFrameIndex++;
- }
- }
-
- int result = encoder.dequeueOutputBuffer(mBufferInfo, DEFAULT_TIMEOUT_US);
- if (result >= 0) {
-
- bits[outputFrameIndex] = mBufferInfo.size;
-
- if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
- sawOutputEOS = true;
- }
-
- encoder.releaseOutputBuffer(result,
- false); // render
-
- outputFrameIndex++;
-
- } else if (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
- mOutputBuffers = encoder.getOutputBuffers();
- }
- }
-
- // 29 frames per run
- int i;
- int sum = 0;
- int frames = 29;
- for(i = 0; i < frames; i++)
- sum += bits[i];
- int midBitrateAvg = sum / frames;
-
- sum = 0;
- for(; i < frames * 2; i++)
- sum += bits[i];
- int highBitrateAvg = sum / frames;
-
- sum = 0;
- for(; i < frames * 3; i++)
- sum += bits[i];
- int lowBitrateAvg = sum / frames;
-
- // For the given bitrates we expect mid ~= 350, high ~= 575 and low ~= 150
- // bytes per frame
- if ((midBitrateAvg + 100) > highBitrateAvg)
- throw new RuntimeException("Bitrate did not increase when requesting higher bitrate");
- if ((lowBitrateAvg + 100) > midBitrateAvg)
- throw new RuntimeException("Bitrate did not decrease when requesting lower bitrate");
-
-
- encoder.stop();
- encoder.release();
- } finally {
- if (rawStream != null) {
- rawStream.close();
+ if (psnrPlatformCodecMin[i] < REFERENCE_MINIMUM_PSNR[i] -
+ MAX_MINIMUM_PSNR_DIFFERENCE) {
+ throw new RuntimeException("Low minimum PSNR " + psnrPlatformCodecMin[i] +
+ " comparing to sw PSNR " + REFERENCE_MINIMUM_PSNR[i] +
+ " for bitrate " + TEST_BITRATES_SET[i]);
}
}
}
}
+
diff --git a/tests/tests/mediastress/Android.mk b/tests/tests/mediastress/Android.mk
index 9f43597..5c4930b 100644
--- a/tests/tests/mediastress/Android.mk
+++ b/tests/tests/mediastress/Android.mk
@@ -20,8 +20,6 @@
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner ctsdeviceutil
LOCAL_JNI_SHARED_LIBRARIES := libctsmediastress_jni
diff --git a/tests/tests/mediastress/AndroidManifest.xml b/tests/tests/mediastress/AndroidManifest.xml
index 931a774..9d48c8c 100644
--- a/tests/tests/mediastress/AndroidManifest.xml
+++ b/tests/tests/mediastress/AndroidManifest.xml
@@ -39,8 +39,11 @@
<activity android:name="android.mediastress.cts.NativeMediaActivity"
android:label="NativeMedia" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.mediastress"
- android:label="Media stress tests InstrumentationRunner" />
+ android:label="Media stress tests InstrumentationRunner" >
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/tests/nativeopengl/Android.mk b/tests/tests/nativeopengl/Android.mk
index dd19548..672eb5c 100644
--- a/tests/tests/nativeopengl/Android.mk
+++ b/tests/tests/nativeopengl/Android.mk
@@ -24,9 +24,6 @@
# When built, explicitly put it in the data partition.
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-# All tests should include android.test.runner.
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner ctswrappedgtest
LOCAL_JNI_SHARED_LIBRARIES := libnativeopengltests
diff --git a/tests/tests/ndef/Android.mk b/tests/tests/ndef/Android.mk
index 70853d9..ba78f29 100644
--- a/tests/tests/ndef/Android.mk
+++ b/tests/tests/ndef/Android.mk
@@ -24,9 +24,6 @@
# When built, explicitly put it in the data partition.
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-# All tests should include android.test.runner.
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/ndef/AndroidManifest.xml b/tests/tests/ndef/AndroidManifest.xml
index a7ebb6e..e0244e1 100644
--- a/tests/tests/ndef/AndroidManifest.xml
+++ b/tests/tests/ndef/AndroidManifest.xml
@@ -23,9 +23,12 @@
</application>
<!-- This is a self-instrumenting test package. -->
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.ndef"
- android:label="CTS tests of NDEF data classes"/>
+ android:label="CTS tests of NDEF data classes">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/tests/net/Android.mk b/tests/tests/net/Android.mk
index 82abd62..da19a4d 100644
--- a/tests/tests/net/Android.mk
+++ b/tests/tests/net/Android.mk
@@ -21,7 +21,7 @@
# and when built explicitly put it in the data partition
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_JAVA_LIBRARIES := android.test.runner voip-common
+LOCAL_JAVA_LIBRARIES := voip-common conscrypt
LOCAL_JNI_SHARED_LIBRARIES := libnativedns_jni
@@ -33,7 +33,7 @@
LOCAL_STATIC_JAVA_LIBRARIES := ctstestserver ctsdeviceutil ctstestrunner \
core-tests-support
-# uncomment when dalvik.annotation.Test* are removed or part of SDK
+# uncomment when b/13249961 is fixed
#LOCAL_SDK_VERSION := current
include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/net/AndroidManifest.xml b/tests/tests/net/AndroidManifest.xml
index ade6728..652262d 100644
--- a/tests/tests/net/AndroidManifest.xml
+++ b/tests/tests/net/AndroidManifest.xml
@@ -32,9 +32,12 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.net"
- android:label="CTS tests of android.net"/>
+ android:label="CTS tests of android.net">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/tests/net/src/android/net/cts/SSLCertificateSocketFactoryTest.java b/tests/tests/net/src/android/net/cts/SSLCertificateSocketFactoryTest.java
index cb8aeaf..6175923 100644
--- a/tests/tests/net/src/android/net/cts/SSLCertificateSocketFactoryTest.java
+++ b/tests/tests/net/src/android/net/cts/SSLCertificateSocketFactoryTest.java
@@ -26,8 +26,6 @@
import android.net.SSLCertificateSocketFactory;
import android.test.AndroidTestCase;
-import dalvik.annotation.BrokenTest;
-
import libcore.javax.net.ssl.SSLDefaultConfigurationAsserts;
public class SSLCertificateSocketFactoryTest extends AndroidTestCase {
diff --git a/tests/tests/net/src/android/net/http/cts/X509TrustManagerExtensionsTest.java b/tests/tests/net/src/android/net/http/cts/X509TrustManagerExtensionsTest.java
new file mode 100644
index 0000000..9c0d774
--- /dev/null
+++ b/tests/tests/net/src/android/net/http/cts/X509TrustManagerExtensionsTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.http.cts;
+
+import android.net.http.X509TrustManagerExtensions;
+import android.util.Base64;
+
+import java.io.File;
+import java.io.ByteArrayInputStream;
+
+import java.security.KeyStore;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+
+import junit.framework.TestCase;
+
+import com.android.org.conscrypt.TrustedCertificateStore;
+import com.android.org.conscrypt.TrustManagerImpl;
+
+public class X509TrustManagerExtensionsTest extends TestCase {
+
+ public void testIsUserAddedCert() throws Exception {
+ final String testCert =
+ "MIICfjCCAeegAwIBAgIJAMefIzKHY5H4MA0GCSqGSIb3DQEBBQUAMFgxCzAJBgNV" +
+ "BAYTAlVTMQswCQYDVQQIDAJDQTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEPMA0G" +
+ "A1UECgwGR2V3Z3VsMRMwEQYDVQQDDApnZXdndWwuY29tMB4XDTEzMTEwNTAwNDE0" +
+ "MFoXDTEzMTIwNTAwNDE0MFowWDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYw" +
+ "FAYDVQQHDA1Nb3VudGFpbiBWaWV3MQ8wDQYDVQQKDAZHZXdndWwxEzARBgNVBAMM" +
+ "Cmdld2d1bC5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKpc/I0Ss4sm" +
+ "yV2iX5xRMM7+XXAhiWrceGair4MpvDrGIa1kFj2phtx4IqTfDnNU7AhRJYkDYmJQ" +
+ "fUJ8i6F+I08uNiGVO4DtPJbZcBXg9ME9EMaJCslm995ueeNWSw1Ky8zM0tt4p+94" +
+ "BcXJ7PC3N2WgkvtE8xwNbaeUfhGPzJKXAgMBAAGjUDBOMB0GA1UdDgQWBBQQ/iW7" +
+ "JCkSI2sbn4nTBiZ9PSiO8zAfBgNVHSMEGDAWgBQQ/iW7JCkSI2sbn4nTBiZ9PSiO" +
+ "8zAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBABQBrUOWTCSIl3vkRR3w" +
+ "3bPzh3BpqDmxH9xe4rZr+MVKKjpGjY1z2m2EEtyNz3tbgVQym5+si00DUHFL0IP1" +
+ "SuRULmPyEpTBVbV+PA5Kc967ZcDgYt4JtdMcCeKbIFaU6r8oEYEL2PTlNZmgbunM" +
+ "pXktkhVvNxZeSa8yM9bPhXkN";
+
+ CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ X509Certificate cert = (X509Certificate)cf.generateCertificate(
+ new ByteArrayInputStream(Base64.decode(testCert, Base64.DEFAULT)));
+
+ // Test without adding cert to keystore.
+ KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
+ X509TrustManagerExtensions tmeNegative =
+ new X509TrustManagerExtensions(new TrustManagerImpl(keyStore));
+ assertEquals(false, tmeNegative.isUserAddedCertificate(cert));
+
+ // Test with cert added to keystore.
+ final File DIR_TEMP = new File(System.getProperty("java.io.tmpdir"));
+ final File DIR_TEST = new File(DIR_TEMP, "test");
+ final File system = new File(DIR_TEST, "system-test");
+ final File added = new File(DIR_TEST, "added-test");
+ final File deleted = new File(DIR_TEST, "deleted-test");
+
+ TrustedCertificateStore tcs = new TrustedCertificateStore(system, added, deleted);
+ added.mkdirs();
+ tcs.installCertificate(cert);
+ X509TrustManagerExtensions tmePositive =
+ new X509TrustManagerExtensions(new TrustManagerImpl(keyStore, null, tcs));
+ assertEquals(true, tmePositive.isUserAddedCertificate(cert));
+ }
+}
diff --git a/tests/tests/opengl/Android.mk b/tests/tests/opengl/Android.mk
index 98f11e9..a14ee7a 100644
--- a/tests/tests/opengl/Android.mk
+++ b/tests/tests/opengl/Android.mk
@@ -24,9 +24,6 @@
# When built, explicitly put it in the data partition.
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-# All tests should include android.test.runner.
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_JNI_SHARED_LIBRARIES := libopengltest_jni
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
diff --git a/tests/tests/opengl/AndroidManifest.xml b/tests/tests/opengl/AndroidManifest.xml
index 266216f..914b2d2 100644
--- a/tests/tests/opengl/AndroidManifest.xml
+++ b/tests/tests/opengl/AndroidManifest.xml
@@ -22,8 +22,11 @@
<uses-sdk android:minSdkVersion="14" />
<uses-feature android:glEsVersion="0x00020000"/>
<instrumentation
- android:name="android.test.InstrumentationCtsTestRunner"
- android:targetPackage="com.android.cts.opengl" />
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.cts.opengl" >
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
@@ -35,7 +38,7 @@
<activity
android:label="@string/app_name"
android:name="android.opengl.cts.OpenGLES20ActivityTwo">
- </activity>
+ </activity>
<uses-library android:name="android.test.runner" />
<activity
android:name="android.opengl.cts.OpenGLES20NativeActivityOne"
diff --git a/tests/tests/opengl/src/android/opengl/cts/FramebufferTest.java b/tests/tests/opengl/src/android/opengl/cts/FramebufferTest.java
new file mode 100644
index 0000000..4ca3a99
--- /dev/null
+++ b/tests/tests/opengl/src/android/opengl/cts/FramebufferTest.java
@@ -0,0 +1,653 @@
+/*
+ * 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 android.opengl.cts;
+
+import android.graphics.Bitmap;
+import android.graphics.SurfaceTexture;
+import android.opengl.EGL14;
+import android.opengl.EGLConfig;
+import android.opengl.EGLContext;
+import android.opengl.EGLDisplay;
+import android.opengl.EGLExt;
+import android.opengl.EGLSurface;
+import android.opengl.GLES20;
+import android.opengl.GLES30;
+import android.test.AndroidTestCase;
+import android.util.Log;
+import android.view.Surface;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
+/**
+ * Test some GLES framebuffer stuff.
+ */
+public class FramebufferTest extends AndroidTestCase {
+ private static final String TAG = "FramebufferTest";
+
+
+ /**
+ * Tests very basic glBlitFramebuffer() features by copying from one offscreen framebuffer
+ * to another.
+ * <p>
+ * Requires GLES3.
+ */
+ public void testBlitFramebuffer() throws Throwable {
+ final int WIDTH = 640;
+ final int HEIGHT = 480;
+ final int BYTES_PER_PIXEL = 4;
+ final int TEST_RED = 255;
+ final int TEST_GREEN = 0;
+ final int TEST_BLUE = 127;
+ final int TEST_ALPHA = 255;
+ final byte expectedBytes[] = new byte[] {
+ (byte) TEST_RED, (byte) TEST_GREEN, (byte) TEST_BLUE, (byte) TEST_ALPHA
+ };
+ EglCore eglCore = null;
+ OffscreenSurface surface1 = null;
+ OffscreenSurface surface2 = null;
+
+ try {
+ eglCore = new EglCore(null, EglCore.FLAG_TRY_GLES3);
+ if (eglCore.getGlVersion() < 3) {
+ Log.d(TAG, "GLES3 not available, skipping test");
+ return;
+ }
+
+ // Create two surfaces, and clear surface1
+ surface1 = new OffscreenSurface(eglCore, WIDTH, HEIGHT);
+ surface2 = new OffscreenSurface(eglCore, WIDTH, HEIGHT);
+ surface1.makeCurrent();
+ GLES30.glClearColor(TEST_RED / 255.0f, TEST_GREEN / 255.0f, TEST_BLUE / 255.0f,
+ TEST_ALPHA / 255.0f);
+ GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);
+ checkGlError("glClear");
+
+ // Set surface2 as "draw", surface1 as "read", and blit.
+ surface2.makeCurrentReadFrom(surface1);
+ GLES30.glBlitFramebuffer(0, 0, WIDTH, HEIGHT, 0, 0, WIDTH, HEIGHT,
+ GLES30.GL_COLOR_BUFFER_BIT, GLES30.GL_NEAREST);
+ checkGlError("glBlitFramebuffer");
+
+ ByteBuffer pixelBuf = ByteBuffer.allocateDirect(WIDTH * HEIGHT * BYTES_PER_PIXEL);
+ pixelBuf.order(ByteOrder.LITTLE_ENDIAN);
+ byte testBytes[] = new byte[4];
+
+ // Confirm that surface1 has the color by testing a pixel from the center.
+ surface1.makeCurrent();
+ pixelBuf.clear();
+ GLES30.glReadPixels(0, 0, WIDTH, HEIGHT, GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE,
+ pixelBuf);
+ checkGlError("glReadPixels");
+ pixelBuf.position((WIDTH * (HEIGHT / 2) + (WIDTH / 2)) * BYTES_PER_PIXEL);
+ pixelBuf.get(testBytes, 0, 4);
+ Log.v(TAG, "testBytes1 = " + Arrays.toString(testBytes));
+ assertTrue(Arrays.equals(testBytes, expectedBytes));
+
+ // Confirm that surface2 has the color.
+ surface2.makeCurrent();
+ pixelBuf.clear();
+ GLES30.glReadPixels(0, 0, WIDTH, HEIGHT, GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE,
+ pixelBuf);
+ checkGlError("glReadPixels");
+ pixelBuf.position((WIDTH * (HEIGHT / 2) + (WIDTH / 2)) * BYTES_PER_PIXEL);
+ pixelBuf.get(testBytes, 0, 4);
+ Log.v(TAG, "testBytes2 = " + Arrays.toString(testBytes));
+ assertTrue(Arrays.equals(testBytes, expectedBytes));
+ } finally {
+ if (surface1 != null) {
+ surface1.release();
+ }
+ if (surface2 != null) {
+ surface2.release();
+ }
+ if (eglCore != null) {
+ eglCore.release();
+ }
+ }
+ }
+
+ /**
+ * Checks to see if a GLES error has been raised.
+ */
+ private static void checkGlError(String op) {
+ int error = GLES20.glGetError();
+ if (error != GLES20.GL_NO_ERROR) {
+ String msg = op + ": glError 0x" + Integer.toHexString(error);
+ Log.e(TAG, msg);
+ throw new RuntimeException(msg);
+ }
+ }
+
+
+ /**
+ * Core EGL state (display, context, config).
+ */
+ private static final class EglCore {
+ /**
+ * Constructor flag: surface must be recordable. This discourages EGL from using a
+ * pixel format that cannot be converted efficiently to something usable by the video
+ * encoder.
+ */
+ public static final int FLAG_RECORDABLE = 0x01;
+
+ /**
+ * Constructor flag: ask for GLES3, fall back to GLES2 if not available. Without this
+ * flag, GLES2 is used.
+ */
+ public static final int FLAG_TRY_GLES3 = 0x02;
+
+ // Android-specific extension.
+ private static final int EGL_RECORDABLE_ANDROID = 0x3142;
+
+ private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY;
+ private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT;
+ private EGLConfig mEGLConfig = null;
+ private int mGlVersion = -1;
+
+
+ /**
+ * Prepares EGL display and context.
+ * <p>
+ * Equivalent to EglCore(null, 0).
+ */
+ public EglCore() {
+ this(null, 0);
+ }
+
+ /**
+ * Prepares EGL display and context.
+ * <p>
+ * @param sharedContext The context to share, or null if sharing is not desired.
+ * @param flags Configuration bit flags, e.g. FLAG_RECORDABLE.
+ */
+ public EglCore(EGLContext sharedContext, int flags) {
+ if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) {
+ throw new RuntimeException("EGL already set up");
+ }
+
+ if (sharedContext == null) {
+ sharedContext = EGL14.EGL_NO_CONTEXT;
+ }
+
+ mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
+ if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
+ throw new RuntimeException("unable to get EGL14 display");
+ }
+ int[] version = new int[2];
+ if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) {
+ mEGLDisplay = null;
+ throw new RuntimeException("unable to initialize EGL14");
+ }
+
+ // Try to get a GLES3 context, if requested.
+ if ((flags & FLAG_TRY_GLES3) != 0) {
+ //Log.d(TAG, "Trying GLES 3");
+ EGLConfig config = getConfig(flags, 3);
+ if (config != null) {
+ int[] attrib3_list = {
+ EGL14.EGL_CONTEXT_CLIENT_VERSION, 3,
+ EGL14.EGL_NONE
+ };
+ EGLContext context = EGL14.eglCreateContext(mEGLDisplay, config, sharedContext,
+ attrib3_list, 0);
+
+ if (EGL14.eglGetError() == EGL14.EGL_SUCCESS) {
+ //Log.d(TAG, "Got GLES 3 config");
+ mEGLConfig = config;
+ mEGLContext = context;
+ mGlVersion = 3;
+ }
+ }
+ }
+ if (mEGLContext == EGL14.EGL_NO_CONTEXT) { // GLES 2 only, or GLES 3 attempt failed
+ //Log.d(TAG, "Trying GLES 2");
+ EGLConfig config = getConfig(flags, 2);
+ if (config == null) {
+ throw new RuntimeException("Unable to find a suitable EGLConfig");
+ }
+ int[] attrib2_list = {
+ EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
+ EGL14.EGL_NONE
+ };
+ EGLContext context = EGL14.eglCreateContext(mEGLDisplay, config, sharedContext,
+ attrib2_list, 0);
+ checkEglError("eglCreateContext");
+ mEGLConfig = config;
+ mEGLContext = context;
+ mGlVersion = 2;
+ }
+
+ // Confirm with query.
+ int[] values = new int[1];
+ EGL14.eglQueryContext(mEGLDisplay, mEGLContext, EGL14.EGL_CONTEXT_CLIENT_VERSION,
+ values, 0);
+ Log.d(TAG, "EGLContext created, client version " + values[0]);
+ }
+
+ /**
+ * Finds a suitable EGLConfig.
+ *
+ * @param flags Bit flags from constructor.
+ * @param version Must be 2 or 3.
+ */
+ private EGLConfig getConfig(int flags, int version) {
+ int renderableType = EGL14.EGL_OPENGL_ES2_BIT;
+ if (version >= 3) {
+ renderableType |= EGLExt.EGL_OPENGL_ES3_BIT_KHR;
+ }
+
+ // The actual surface is generally RGBA or RGBX, so situationally omitting alpha
+ // doesn't really help. It can also lead to a huge performance hit on glReadPixels()
+ // when reading into a GL_RGBA buffer.
+ int[] attribList = {
+ EGL14.EGL_RED_SIZE, 8,
+ EGL14.EGL_GREEN_SIZE, 8,
+ EGL14.EGL_BLUE_SIZE, 8,
+ EGL14.EGL_ALPHA_SIZE, 8,
+ //EGL14.EGL_DEPTH_SIZE, 16,
+ //EGL14.EGL_STENCIL_SIZE, 8,
+ EGL14.EGL_RENDERABLE_TYPE, renderableType,
+ EGL14.EGL_NONE, 0, // placeholder for recordable [@-3]
+ EGL14.EGL_NONE
+ };
+ if ((flags & FLAG_RECORDABLE) != 0) {
+ attribList[attribList.length - 3] = EGL_RECORDABLE_ANDROID;
+ attribList[attribList.length - 2] = 1;
+ }
+ EGLConfig[] configs = new EGLConfig[1];
+ int[] numConfigs = new int[1];
+ if (!EGL14.eglChooseConfig(mEGLDisplay, attribList, 0, configs, 0, configs.length,
+ numConfigs, 0)) {
+ Log.w(TAG, "unable to find RGB8888 / " + version + " EGLConfig");
+ return null;
+ }
+ return configs[0];
+ }
+
+ /**
+ * Discard all resources held by this class, notably the EGL context.
+ */
+ public void release() {
+ if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) {
+ // Android is unusual in that it uses a reference-counted EGLDisplay. So for
+ // every eglInitialize() we need an eglTerminate().
+ EGL14.eglDestroyContext(mEGLDisplay, mEGLContext);
+ EGL14.eglReleaseThread();
+ EGL14.eglTerminate(mEGLDisplay);
+ }
+
+ mEGLDisplay = EGL14.EGL_NO_DISPLAY;
+ mEGLContext = EGL14.EGL_NO_CONTEXT;
+ mEGLConfig = null;
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) {
+ // We're limited here -- finalizers don't run on the thread that holds
+ // the EGL state, so if a surface or context is still current on another
+ // thread we can't fully release it here. Exceptions thrown from here
+ // are quietly discarded. Complain in the log file.
+ Log.w(TAG, "WARNING: EglCore was not explicitly released -- state may be leaked");
+ release();
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Destroys the specified surface. Note the EGLSurface won't actually be destroyed if it's
+ * still current in a context.
+ */
+ public void releaseSurface(EGLSurface eglSurface) {
+ EGL14.eglDestroySurface(mEGLDisplay, eglSurface);
+ }
+
+ /**
+ * Creates an EGL surface associated with a Surface.
+ * <p>
+ * If this is destined for MediaCodec, the EGLConfig should have the "recordable" attribute.
+ */
+ public EGLSurface createWindowSurface(Object surface) {
+ if (!(surface instanceof Surface) && !(surface instanceof SurfaceTexture)) {
+ throw new RuntimeException("invalid surface: " + surface);
+ }
+
+ // Create a window surface, and attach it to the Surface we received.
+ int[] surfaceAttribs = {
+ EGL14.EGL_NONE
+ };
+ EGLSurface eglSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, mEGLConfig, surface,
+ surfaceAttribs, 0);
+ checkEglError("eglCreateWindowSurface");
+ if (eglSurface == null) {
+ throw new RuntimeException("surface was null");
+ }
+ return eglSurface;
+ }
+
+ /**
+ * Creates an EGL surface associated with an offscreen buffer.
+ */
+ public EGLSurface createOffscreenSurface(int width, int height) {
+ int[] surfaceAttribs = {
+ EGL14.EGL_WIDTH, width,
+ EGL14.EGL_HEIGHT, height,
+ EGL14.EGL_NONE
+ };
+ EGLSurface eglSurface = EGL14.eglCreatePbufferSurface(mEGLDisplay, mEGLConfig,
+ surfaceAttribs, 0);
+ checkEglError("eglCreatePbufferSurface");
+ if (eglSurface == null) {
+ throw new RuntimeException("surface was null");
+ }
+ return eglSurface;
+ }
+
+ /**
+ * Makes our EGL context current, using the supplied surface for both "draw" and "read".
+ */
+ public void makeCurrent(EGLSurface eglSurface) {
+ if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
+ // called makeCurrent() before create?
+ Log.d(TAG, "NOTE: makeCurrent w/o display");
+ }
+ if (!EGL14.eglMakeCurrent(mEGLDisplay, eglSurface, eglSurface, mEGLContext)) {
+ throw new RuntimeException("eglMakeCurrent failed");
+ }
+ }
+
+ /**
+ * Makes our EGL context current, using the supplied "draw" and "read" surfaces.
+ */
+ public void makeCurrent(EGLSurface drawSurface, EGLSurface readSurface) {
+ if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
+ // called makeCurrent() before create?
+ Log.d(TAG, "NOTE: makeCurrent w/o display");
+ }
+ if (!EGL14.eglMakeCurrent(mEGLDisplay, drawSurface, readSurface, mEGLContext)) {
+ throw new RuntimeException("eglMakeCurrent(draw,read) failed");
+ }
+ }
+
+ /**
+ * Makes no context current.
+ */
+ public void makeNothingCurrent() {
+ if (!EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
+ EGL14.EGL_NO_CONTEXT)) {
+ throw new RuntimeException("eglMakeCurrent failed");
+ }
+ }
+
+ /**
+ * Calls eglSwapBuffers. Use this to "publish" the current frame.
+ *
+ * @return false on failure
+ */
+ public boolean swapBuffers(EGLSurface eglSurface) {
+ return EGL14.eglSwapBuffers(mEGLDisplay, eglSurface);
+ }
+
+ /**
+ * Sends the presentation time stamp to EGL. Time is expressed in nanoseconds.
+ */
+ public void setPresentationTime(EGLSurface eglSurface, long nsecs) {
+ EGLExt.eglPresentationTimeANDROID(mEGLDisplay, eglSurface, nsecs);
+ }
+
+ /**
+ * Returns true if our context and the specified surface are current.
+ */
+ public boolean isCurrent(EGLSurface eglSurface) {
+ return mEGLContext.equals(EGL14.eglGetCurrentContext()) &&
+ eglSurface.equals(EGL14.eglGetCurrentSurface(EGL14.EGL_DRAW));
+ }
+
+ /**
+ * Performs a simple surface query.
+ */
+ public int querySurface(EGLSurface eglSurface, int what) {
+ int[] value = new int[1];
+ EGL14.eglQuerySurface(mEGLDisplay, eglSurface, what, value, 0);
+ return value[0];
+ }
+
+ /**
+ * Returns the GLES version this context is configured for (2 or 3).
+ */
+ public int getGlVersion() {
+ return mGlVersion;
+ }
+
+ /**
+ * Writes the current display, context, and surface to the log.
+ */
+ public static void logCurrent(String msg) {
+ EGLDisplay display;
+ EGLContext context;
+ EGLSurface surface;
+
+ display = EGL14.eglGetCurrentDisplay();
+ context = EGL14.eglGetCurrentContext();
+ surface = EGL14.eglGetCurrentSurface(EGL14.EGL_DRAW);
+ Log.i(TAG, "Current EGL (" + msg + "): display=" + display + ", context=" + context +
+ ", surface=" + surface);
+ }
+
+ /**
+ * Checks for EGL errors.
+ */
+ private void checkEglError(String msg) {
+ int error;
+ if ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) {
+ throw new RuntimeException(msg + ": EGL error: 0x" + Integer.toHexString(error));
+ }
+ }
+ }
+
+
+ /**
+ * Common base class for EGL surfaces.
+ * <p>
+ * There can be multiple surfaces associated with a single context.
+ */
+ private static class EglSurfaceBase {
+ // EglCore object we're associated with. It may be associated with multiple surfaces.
+ protected EglCore mEglCore;
+
+ private EGLSurface mEGLSurface = EGL14.EGL_NO_SURFACE;
+ private int mWidth = -1;
+ private int mHeight = -1;
+
+ protected EglSurfaceBase(EglCore eglCore) {
+ mEglCore = eglCore;
+ }
+
+ /**
+ * Creates a window surface.
+ * <p>
+ * @param surface May be a Surface or SurfaceTexture.
+ */
+ public void createWindowSurface(Object surface) {
+ if (mEGLSurface != EGL14.EGL_NO_SURFACE) {
+ throw new IllegalStateException("surface already created");
+ }
+ mEGLSurface = mEglCore.createWindowSurface(surface);
+ mWidth = mEglCore.querySurface(mEGLSurface, EGL14.EGL_WIDTH);
+ mHeight = mEglCore.querySurface(mEGLSurface, EGL14.EGL_HEIGHT);
+ }
+
+ /**
+ * Creates an off-screen surface.
+ */
+ public void createOffscreenSurface(int width, int height) {
+ if (mEGLSurface != EGL14.EGL_NO_SURFACE) {
+ throw new IllegalStateException("surface already created");
+ }
+ mEGLSurface = mEglCore.createOffscreenSurface(width, height);
+ mWidth = width;
+ mHeight = height;
+ }
+
+ /**
+ * Returns the surface's width, in pixels.
+ */
+ public int getWidth() {
+ return mWidth;
+ }
+
+ /**
+ * Returns the surface's height, in pixels.
+ */
+ public int getHeight() {
+ return mHeight;
+ }
+
+ /**
+ * Release the EGL surface.
+ */
+ public void releaseEglSurface() {
+ mEglCore.releaseSurface(mEGLSurface);
+ mEGLSurface = EGL14.EGL_NO_SURFACE;
+ mWidth = mHeight = -1;
+ }
+
+ /**
+ * Makes our EGL context and surface current.
+ */
+ public void makeCurrent() {
+ mEglCore.makeCurrent(mEGLSurface);
+ }
+
+ /**
+ * Makes our EGL context and surface current for drawing, using the supplied surface
+ * for reading.
+ */
+ public void makeCurrentReadFrom(EglSurfaceBase readSurface) {
+ mEglCore.makeCurrent(mEGLSurface, readSurface.mEGLSurface);
+ }
+
+ /**
+ * Calls eglSwapBuffers. Use this to "publish" the current frame.
+ *
+ * @return false on failure
+ */
+ public boolean swapBuffers() {
+ boolean result = mEglCore.swapBuffers(mEGLSurface);
+ if (!result) {
+ Log.d(TAG, "WARNING: swapBuffers() failed");
+ }
+ return result;
+ }
+
+ /**
+ * Sends the presentation time stamp to EGL.
+ *
+ * @param nsecs Timestamp, in nanoseconds.
+ */
+ public void setPresentationTime(long nsecs) {
+ mEglCore.setPresentationTime(mEGLSurface, nsecs);
+ }
+
+ /**
+ * Saves the EGL surface to a file.
+ * <p>
+ * Expects that this object's EGL surface is current.
+ */
+ public void saveFrame(File file) throws IOException {
+ if (!mEglCore.isCurrent(mEGLSurface)) {
+ throw new RuntimeException("Expected EGL context/surface is not current");
+ }
+
+ // glReadPixels gives us a ByteBuffer filled with what is essentially big-endian RGBA
+ // data (i.e. a byte of red, followed by a byte of green...). We need an int[] filled
+ // with little-endian ARGB data to feed to Bitmap.
+ //
+ // If we implement this as a series of buf.get() calls, we can spend 2.5 seconds just
+ // copying data around for a 720p frame. It's better to do a bulk get() and then
+ // rearrange the data in memory. (For comparison, the PNG compress takes about 500ms
+ // for a trivial frame.)
+ //
+ // So... we set the ByteBuffer to little-endian, which should turn the bulk IntBuffer
+ // get() into a straight memcpy on most Android devices. Our ints will hold ABGR data.
+ // Swapping B and R gives us ARGB.
+ //
+ // Making this even more interesting is the upside-down nature of GL, which means
+ // our output will look upside-down relative to what appears on screen if the
+ // typical GL conventions are used.
+
+ String filename = file.toString();
+
+ ByteBuffer buf = ByteBuffer.allocateDirect(mWidth * mHeight * 4);
+ buf.order(ByteOrder.LITTLE_ENDIAN);
+ GLES20.glReadPixels(0, 0, mWidth, mHeight,
+ GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buf);
+ checkGlError("glReadPixels");
+ buf.rewind();
+
+ int pixelCount = mWidth * mHeight;
+ int[] colors = new int[pixelCount];
+ buf.asIntBuffer().get(colors);
+ for (int i = 0; i < pixelCount; i++) {
+ int c = colors[i];
+ colors[i] = (c & 0xff00ff00) | ((c & 0x00ff0000) >> 16) | ((c & 0x000000ff) << 16);
+ }
+
+ BufferedOutputStream bos = null;
+ try {
+ bos = new BufferedOutputStream(new FileOutputStream(filename));
+ Bitmap bmp = Bitmap.createBitmap(colors, mWidth, mHeight, Bitmap.Config.ARGB_8888);
+ bmp.compress(Bitmap.CompressFormat.PNG, 90, bos);
+ bmp.recycle();
+ } finally {
+ if (bos != null) bos.close();
+ }
+ Log.d(TAG, "Saved " + mWidth + "x" + mHeight + " frame as '" + filename + "'");
+ }
+ }
+
+ /**
+ * Off-screen EGL surface (pbuffer).
+ * <p>
+ * It's good practice to explicitly release() the surface, preferably from a "finally" block.
+ */
+ private static class OffscreenSurface extends EglSurfaceBase {
+ /**
+ * Creates an off-screen surface with the specified width and height.
+ */
+ public OffscreenSurface(EglCore eglCore, int width, int height) {
+ super(eglCore);
+ createOffscreenSurface(width, height);
+ }
+
+ /**
+ * Releases any resources associated with the surface.
+ */
+ public void release() {
+ releaseEglSurface();
+ }
+ }
+}
diff --git a/tests/tests/openglperf/Android.mk b/tests/tests/openglperf/Android.mk
index 55c39f2..1d57263 100644
--- a/tests/tests/openglperf/Android.mk
+++ b/tests/tests/openglperf/Android.mk
@@ -21,8 +21,6 @@
# and when built explicitly put it in the data partition
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner ctsdeviceutil
LOCAL_JNI_SHARED_LIBRARIES := libctsopenglperf_jni
diff --git a/tests/tests/openglperf/AndroidManifest.xml b/tests/tests/openglperf/AndroidManifest.xml
index 1934f35..a213e51 100644
--- a/tests/tests/openglperf/AndroidManifest.xml
+++ b/tests/tests/openglperf/AndroidManifest.xml
@@ -27,10 +27,16 @@
<!-- Two activities are used -->
<instrumentation
android:targetPackage="com.replica.replicaisland"
- android:name="android.test.InstrumentationCtsTestRunner" />
+ android:name="android.support.test.runner.AndroidJUnitRunner" >
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
<instrumentation
android:targetPackage="com.android.cts.openglperf"
- android:name="android.test.InstrumentationCtsTestRunner" />
+ android:name="android.support.test.runner.AndroidJUnitRunner">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
<application>
<uses-library android:name="android.test.runner" />
diff --git a/tests/tests/os/Android.mk b/tests/tests/os/Android.mk
index f43043b..0007a54 100644
--- a/tests/tests/os/Android.mk
+++ b/tests/tests/os/Android.mk
@@ -23,8 +23,6 @@
LOCAL_PROGUARD_ENABLED := disabled
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner guava
LOCAL_SRC_FILES := $(call all-java-files-under, src) \
@@ -34,7 +32,8 @@
LOCAL_INSTRUMENTATION_FOR := CtsTestStubs
-# uncomment when dalvik.annotation.Test* are removed or part of SDK
+# uncomment when b/13282254 is fixed
#LOCAL_SDK_VERSION := current
+LOCAL_JAVA_LIBRARIES += android.test.runner
include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/os/AndroidManifest.xml b/tests/tests/os/AndroidManifest.xml
index 2418132..155e772 100644
--- a/tests/tests/os/AndroidManifest.xml
+++ b/tests/tests/os/AndroidManifest.xml
@@ -34,8 +34,11 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.stub"
- android:label="CTS tests of android.os"/>
+ android:label="CTS tests of android.os">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/tests/os/src/android/os/cts/MessageTest.java b/tests/tests/os/src/android/os/cts/MessageTest.java
index dc56a23..cc45c4b 100644
--- a/tests/tests/os/src/android/os/cts/MessageTest.java
+++ b/tests/tests/os/src/android/os/cts/MessageTest.java
@@ -231,6 +231,70 @@
assertFalse(message.isAsynchronous());
}
+ public void testRecycleThrowsIfMessageAlreadyRecycled() {
+ Message message = Message.obtain();
+ message.recycle();
+
+ try {
+ message.recycle();
+ fail("should throw IllegalStateException");
+ } catch (IllegalStateException ex) {
+ // expected
+ }
+ }
+
+ public void testSendMessageThrowsIfMessageAlreadyRecycled() {
+ Message message = Message.obtain();
+ message.recycle();
+
+ try {
+ mHandler.sendMessage(message);
+ fail("should throw IllegalStateException");
+ } catch (IllegalStateException ex) {
+ // expected
+ }
+ }
+
+ public void testRecycleThrowsIfMessageIsBeingDelivered() {
+ final Exception[] caught = new Exception[1];
+ Handler handler = new Handler(mHandler.getLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ try {
+ msg.recycle();
+ } catch (IllegalStateException ex) {
+ caught[0] = ex; // expected
+ }
+ }
+ };
+ handler.sendEmptyMessage(WHAT);
+ sleep(SLEEP_TIME);
+
+ if (caught[0] == null) {
+ fail("should throw IllegalStateException");
+ }
+ }
+
+ public void testSendMessageThrowsIfMessageIsBeingDelivered() {
+ final Exception[] caught = new Exception[1];
+ Handler handler = new Handler(mHandler.getLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ try {
+ mHandler.sendMessage(msg);
+ } catch (IllegalStateException ex) {
+ caught[0] = ex; // expected
+ }
+ }
+ };
+ handler.sendEmptyMessage(WHAT);
+ sleep(SLEEP_TIME);
+
+ if (caught[0] == null) {
+ fail("should throw IllegalStateException");
+ }
+ }
+
private void sleep(long time) {
try {
Thread.sleep(time);
diff --git a/tests/tests/permission/Android.mk b/tests/tests/permission/Android.mk
index 07f20d8..2e88006 100644
--- a/tests/tests/permission/Android.mk
+++ b/tests/tests/permission/Android.mk
@@ -19,9 +19,9 @@
LOCAL_MODULE_TAGS := tests
-LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common
+LOCAL_JAVA_LIBRARIES := telephony-common
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner guava
LOCAL_JNI_SHARED_LIBRARIES := libctspermission_jni
@@ -29,8 +29,9 @@
LOCAL_PACKAGE_NAME := CtsPermissionTestCases
-# uncomment when dalvik test annotations are removed or part of SDK
+# uncomment when b/13249777 is fixed
#LOCAL_SDK_VERSION := current
+LOCAL_JAVA_LIBRARIES += android.test.runner
include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/permission/AndroidManifest.xml b/tests/tests/permission/AndroidManifest.xml
index 945a303..fa03335 100644
--- a/tests/tests/permission/AndroidManifest.xml
+++ b/tests/tests/permission/AndroidManifest.xml
@@ -40,9 +40,12 @@
package. That runner cannot be added to this package either, since it
relies on hidden APIs.
-->
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.permission"
- android:label="CTS tests of com.android.cts.permission"/>
+ android:label="CTS tests of com.android.cts.permission">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/tests/permission/jni/android_permission_cts_FileUtils.cpp b/tests/tests/permission/jni/android_permission_cts_FileUtils.cpp
index 272bbdc..8f32027 100644
--- a/tests/tests/permission/jni/android_permission_cts_FileUtils.cpp
+++ b/tests/tests/permission/jni/android_permission_cts_FileUtils.cpp
@@ -25,6 +25,9 @@
#include <grp.h>
#include <pwd.h>
#include <string.h>
+#include <ScopedLocalRef.h>
+#include <ScopedPrimitiveArray.h>
+#include <ScopedUtfChars.h>
static jfieldID gFileStatusDevFieldID;
static jfieldID gFileStatusInoFieldID;
@@ -46,14 +49,15 @@
* Copied from hidden API: frameworks/base/core/jni/android_os_FileUtils.cpp
*/
-jboolean android_permission_cts_FileUtils_getFileStatus(JNIEnv* env, jobject thiz,
- jstring path, jobject fileStatus, jboolean statLinks)
+jboolean android_permission_cts_FileUtils_getFileStatus(JNIEnv* env,
+ jobject /* thiz */, jstring path, jobject fileStatus, jboolean statLinks)
{
- const char* pathStr = env->GetStringUTFChars(path, NULL);
+ ScopedUtfChars cPath(env, path);
jboolean ret = false;
struct stat s;
- int res = statLinks == true ? lstat(pathStr, &s) : stat(pathStr, &s);
+ int res = statLinks == true ? lstat(cPath.c_str(), &s)
+ : stat(cPath.c_str(), &s);
if (res == 0) {
ret = true;
@@ -73,20 +77,18 @@
}
}
- env->ReleaseStringUTFChars(path, pathStr);
-
return ret;
}
-jstring android_permission_cts_FileUtils_getUserName(JNIEnv* env, jobject thiz,
- jint uid)
+jstring android_permission_cts_FileUtils_getUserName(JNIEnv* env,
+ jobject /* thiz */, jint uid)
{
struct passwd *pwd = getpwuid(uid);
return env->NewStringUTF(pwd->pw_name);
}
-jstring android_permission_cts_FileUtils_getGroupName(JNIEnv* env, jobject thiz,
- jint gid)
+jstring android_permission_cts_FileUtils_getGroupName(JNIEnv* env,
+ jobject /* thiz */, jint gid)
{
struct group *grp = getgrgid(gid);
return env->NewStringUTF(grp->gr_name);
@@ -94,42 +96,106 @@
static jboolean isPermittedCapBitSet(JNIEnv* env, jstring path, size_t capId)
{
- const char* pathStr = env->GetStringUTFChars(path, NULL);
- jboolean ret = false;
-
struct vfs_cap_data capData;
memset(&capData, 0, sizeof(capData));
- ssize_t result = getxattr(pathStr, XATTR_NAME_CAPS, &capData,
+ ScopedUtfChars cPath(env, path);
+ ssize_t result = getxattr(cPath.c_str(), XATTR_NAME_CAPS, &capData,
sizeof(capData));
- if (result > 0) {
- ret = (capData.data[CAP_TO_INDEX(capId)].permitted &
- CAP_TO_MASK(capId)) != 0;
- ALOGD("isPermittedCapBitSet(): getxattr(\"%s\") call succeeded, "
- "cap bit %u %s",
- pathStr, capId, ret ? "set" : "unset");
- } else {
- ALOGD("isPermittedCapBitSet(): getxattr(\"%s\") call failed: "
- "return %d (error: %s (%d))\n",
- pathStr, result, strerror(errno), errno);
+ if (result <= 0)
+ {
+ ALOGD("isPermittedCapBitSet(): getxattr(\"%s\") call failed: "
+ "return %d (error: %s (%d))\n",
+ cPath.c_str(), result, strerror(errno), errno);
+ return false;
}
- env->ReleaseStringUTFChars(path, pathStr);
- return ret;
+ return (capData.data[CAP_TO_INDEX(capId)].permitted &
+ CAP_TO_MASK(capId)) != 0;
}
jboolean android_permission_cts_FileUtils_hasSetUidCapability(JNIEnv* env,
- jobject clazz, jstring path)
+ jobject /* clazz */, jstring path)
{
return isPermittedCapBitSet(env, path, CAP_SETUID);
}
jboolean android_permission_cts_FileUtils_hasSetGidCapability(JNIEnv* env,
- jobject clazz, jstring path)
+ jobject /* clazz */, jstring path)
{
return isPermittedCapBitSet(env, path, CAP_SETGID);
}
+static bool throwNamedException(JNIEnv* env, const char* className,
+ const char* message)
+{
+ ScopedLocalRef<jclass> eClazz(env, env->FindClass(className));
+ if (eClazz.get() == NULL)
+ {
+ ALOGE("throwNamedException(): failed to find class %s, cannot throw",
+ className);
+ return false;
+ }
+
+ env->ThrowNew(eClazz.get(), message);
+ return true;
+}
+
+// fill vfs_cap_data's permitted caps given a Java int[] of cap ids
+static bool fillPermittedCaps(vfs_cap_data* capData, JNIEnv* env, jintArray capIds)
+{
+ ScopedIntArrayRO cCapIds(env, capIds);
+ const size_t capCount = cCapIds.size();
+
+ for (size_t i = 0; i < capCount; ++i)
+ {
+ const jint capId = cCapIds[i];
+ if (!cap_valid(capId))
+ {
+ char message[64];
+ snprintf(message, sizeof(message),
+ "capability id %d out of valid range", capId);
+ throwNamedException(env, "java/lang/IllegalArgumentException",
+ message);
+
+ return false;
+ }
+ capData->data[CAP_TO_INDEX(capId)].permitted |= CAP_TO_MASK(capId);
+ }
+ return true;
+}
+
+jboolean android_permission_cts_FileUtils_CapabilitySet_fileHasOnly(JNIEnv* env,
+ jobject /* clazz */, jstring path, jintArray capIds)
+{
+ struct vfs_cap_data expectedCapData;
+ memset(&expectedCapData, 0, sizeof(expectedCapData));
+
+ expectedCapData.magic_etc = VFS_CAP_REVISION | VFS_CAP_FLAGS_EFFECTIVE;
+ if (!fillPermittedCaps(&expectedCapData, env, capIds))
+ {
+ // exception thrown
+ return false;
+ }
+
+ struct vfs_cap_data actualCapData;
+ memset(&actualCapData, 0, sizeof(actualCapData));
+
+ ScopedUtfChars cPath(env, path);
+ ssize_t result = getxattr(cPath.c_str(), XATTR_NAME_CAPS, &actualCapData,
+ sizeof(actualCapData));
+ if (result <= 0)
+ {
+ ALOGD("fileHasOnly(): getxattr(\"%s\") call failed: "
+ "return %d (error: %s (%d))\n",
+ cPath.c_str(), result, strerror(errno), errno);
+ return false;
+ }
+
+ return (memcmp(&expectedCapData, &actualCapData,
+ sizeof(struct vfs_cap_data)) == 0);
+}
+
static JNINativeMethod gMethods[] = {
{ "getFileStatus", "(Ljava/lang/String;Landroid/permission/cts/FileUtils$FileStatus;Z)Z",
(void *) android_permission_cts_FileUtils_getFileStatus },
@@ -143,6 +209,11 @@
(void *) android_permission_cts_FileUtils_hasSetGidCapability },
};
+static JNINativeMethod gCapabilitySetMethods[] = {
+ { "fileHasOnly", "(Ljava/lang/String;[I)Z",
+ (void *) android_permission_cts_FileUtils_CapabilitySet_fileHasOnly },
+};
+
int register_android_permission_cts_FileUtils(JNIEnv* env)
{
jclass clazz = env->FindClass("android/permission/cts/FileUtils");
@@ -161,6 +232,16 @@
gFileStatusMtimeFieldID = env->GetFieldID(fileStatusClass, "mtime", "J");
gFileStatusCtimeFieldID = env->GetFieldID(fileStatusClass, "ctime", "J");
- return env->RegisterNatives(clazz, gMethods,
- sizeof(gMethods) / sizeof(JNINativeMethod));
+ jint result = env->RegisterNatives(clazz, gMethods,
+ sizeof(gMethods) / sizeof(JNINativeMethod));
+ if (result)
+ {
+ return result;
+ }
+
+ // register FileUtils.CapabilitySet native methods
+ jclass capClazz = env->FindClass("android/permission/cts/FileUtils$CapabilitySet");
+
+ return env->RegisterNatives(capClazz, gCapabilitySetMethods,
+ sizeof(gCapabilitySetMethods) / sizeof(JNINativeMethod));
}
diff --git a/tests/tests/permission/src/android/permission/cts/DevicePowerPermissionTest.java b/tests/tests/permission/src/android/permission/cts/DevicePowerPermissionTest.java
index c5f8ea5..006fb6d 100644
--- a/tests/tests/permission/src/android/permission/cts/DevicePowerPermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/DevicePowerPermissionTest.java
@@ -20,7 +20,6 @@
import android.os.PowerManager;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.LargeTest;
-import dalvik.annotation.KnownFailure;
/**
* Verify that various PowerManagement functionality requires Permission.
diff --git a/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java b/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
index 85af555..61998e7 100755
--- a/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
@@ -19,6 +19,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Environment;
+import android.system.OsConstants;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.MediumTest;
import android.test.suitebuilder.annotation.LargeTest;
@@ -237,6 +238,17 @@
assertFileOwnedByGroup(f, "net_bw_stats");
}
+ @MediumTest
+ public void testTcpDefaultRwndSane() throws Exception {
+ File f = new File("/proc/sys/net/ipv4/tcp_default_init_rwnd");
+ assertTrue(f.canRead());
+ assertFalse(f.canWrite());
+ assertFalse(f.canExecute());
+
+ assertFileOwnedBy(f, "root");
+ assertFileOwnedByGroup(f, "root");
+ }
+
/**
* Assert that a file is owned by a specific owner. This is a noop if the
* file does not exist.
@@ -838,6 +850,30 @@
assertFileOwnedByGroup(f, "system");
}
+ public void testFileHasOnlyCapsThrowsOnInvalidCaps() throws Exception {
+ try {
+ // Ensure negative cap id fails.
+ new FileUtils.CapabilitySet()
+ .add(-1)
+ .fileHasOnly("/system/bin/run-as");
+ fail();
+ }
+ catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ try {
+ // Ensure too-large cap throws.
+ new FileUtils.CapabilitySet()
+ .add(OsConstants.CAP_LAST_CAP + 1)
+ .fileHasOnly("/system/bin/run-as");
+ fail();
+ }
+ catch (IllegalArgumentException e) {
+ // expected
+ }
+ }
+
/**
* Test that the /system/bin/run-as command has setuid and setgid
* attributes set on the file. If these calls fail, debugger
@@ -860,6 +896,12 @@
// ensure file has setuid/setgid enabled
assertTrue(FileUtils.hasSetUidCapability(filename));
assertTrue(FileUtils.hasSetGidCapability(filename));
+
+ // ensure file has *only* setuid/setgid attributes enabled
+ assertTrue(new FileUtils.CapabilitySet()
+ .add(OsConstants.CAP_SETUID)
+ .add(OsConstants.CAP_SETGID)
+ .fileHasOnly("/system/bin/run-as"));
}
private static Set<File>
diff --git a/tests/tests/permission/src/android/permission/cts/FileUtils.java b/tests/tests/permission/src/android/permission/cts/FileUtils.java
index 9cd4999..af44a1c 100644
--- a/tests/tests/permission/src/android/permission/cts/FileUtils.java
+++ b/tests/tests/permission/src/android/permission/cts/FileUtils.java
@@ -16,6 +16,13 @@
* limitations under the License.
*/
+import com.google.common.primitives.Ints;
+
+import android.system.OsConstants;
+
+import java.util.HashSet;
+import java.util.Set;
+
/** Bits and pieces copied from hidden API of android.os.FileUtils. */
public class FileUtils {
@@ -82,6 +89,27 @@
}
}
+ public static class CapabilitySet {
+
+ private final Set<Integer> mCapabilities = new HashSet<Integer>();
+
+ public CapabilitySet add(int capability) {
+ if ((capability < 0) || (capability > OsConstants.CAP_LAST_CAP)) {
+ throw new IllegalArgumentException(String.format(
+ "capability id %d out of valid range", capability));
+ }
+ mCapabilities.add(capability);
+ return this;
+ }
+
+ private native static boolean fileHasOnly(String path,
+ int[] capabilities);
+
+ public boolean fileHasOnly(String path) {
+ return fileHasOnly(path, Ints.toArray(mCapabilities));
+ }
+ }
+
/**
* @param path of the file to stat
* @param status object to set the fields on
diff --git a/tests/tests/permission2/Android.mk b/tests/tests/permission2/Android.mk
index 86a8bc7..5a7f5b7 100755
--- a/tests/tests/permission2/Android.mk
+++ b/tests/tests/permission2/Android.mk
@@ -19,7 +19,7 @@
LOCAL_MODULE_TAGS := tests
-LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common mms-common
+LOCAL_JAVA_LIBRARIES := telephony-common mms-common
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
diff --git a/tests/tests/permission2/AndroidManifest.xml b/tests/tests/permission2/AndroidManifest.xml
index 1c1a0d8..c0b78c4 100755
--- a/tests/tests/permission2/AndroidManifest.xml
+++ b/tests/tests/permission2/AndroidManifest.xml
@@ -56,9 +56,12 @@
android:name="android.permission.FLASHLIGHT"
android:maxSdkVersion="9000" />
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.permission2"
- android:label="More CTS tests for permissions"/>
+ android:label="More CTS tests for permissions">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/tests/preference/Android.mk b/tests/tests/preference/Android.mk
index cc2b210..5860406 100644
--- a/tests/tests/preference/Android.mk
+++ b/tests/tests/preference/Android.mk
@@ -20,8 +20,6 @@
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/preference/AndroidManifest.xml b/tests/tests/preference/AndroidManifest.xml
index 3477192..e4c6b52 100644
--- a/tests/tests/preference/AndroidManifest.xml
+++ b/tests/tests/preference/AndroidManifest.xml
@@ -23,9 +23,12 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.stub"
- android:label="CTS tests of android.os"/>
+ android:label="CTS tests of android.os">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/tests/preference2/Android.mk b/tests/tests/preference2/Android.mk
index 47b081d..59fedc8 100644
--- a/tests/tests/preference2/Android.mk
+++ b/tests/tests/preference2/Android.mk
@@ -22,8 +22,6 @@
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/preference2/AndroidManifest.xml b/tests/tests/preference2/AndroidManifest.xml
index 23b085d..2dbd53a 100644
--- a/tests/tests/preference2/AndroidManifest.xml
+++ b/tests/tests/preference2/AndroidManifest.xml
@@ -35,9 +35,12 @@
</application>
<!-- This is a self-instrumenting test package. -->
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.preference2"
- android:label="CTS Test Cases for android.preference"/>
+ android:label="CTS Test Cases for android.preference">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/tests/print/Android.mk b/tests/tests/print/Android.mk
new file mode 100644
index 0000000..516f6a0
--- /dev/null
+++ b/tests/tests/print/Android.mk
@@ -0,0 +1,33 @@
+# 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_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+ src/android/print/cts/IPrivilegedOperations.aidl
+
+LOCAL_PACKAGE_NAME := CtsPrintTestCases
+
+LOCAL_STATIC_JAVA_LIBRARIES := mockito-target ctstestrunner ub-uiautomator
+
+# This test runner sets up/cleans up the device before/after running the tests.
+LOCAL_CTS_TEST_RUNNER := com.android.cts.tradefed.testtype.PrintTestRunner
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/print/AndroidManifest.xml b/tests/tests/print/AndroidManifest.xml
new file mode 100644
index 0000000..4c94fd5
--- /dev/null
+++ b/tests/tests/print/AndroidManifest.xml
@@ -0,0 +1,76 @@
+<?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.print">
+
+ <application android:allowBackup="false" >
+
+ <uses-library android:name="android.test.runner"/>
+
+ <activity android:name="android.print.cts.PrintDocumentActivity"/>
+
+ <service
+ android:name="android.print.cts.services.FirstPrintService"
+ android:permission="android.permission.BIND_PRINT_SERVICE">
+ <intent-filter>
+ <action android:name="android.printservice.PrintService" />
+ </intent-filter>
+ <meta-data
+ android:name="android.printservice"
+ android:resource="@xml/printservice">
+ </meta-data>
+ </service>
+
+ <service
+ android:name="android.print.cts.services.SecondPrintService"
+ android:permission="android.permission.BIND_PRINT_SERVICE">
+ <intent-filter>
+ <action android:name="android.printservice.PrintService" />
+ </intent-filter>
+ <meta-data
+ android:name="android.printservice"
+ android:resource="@xml/printservice">
+ </meta-data>
+ </service>
+
+ <activity
+ android:name="android.print.cts.services.SettingsActivity"
+ android:permission="android.permission.START_PRINT_SERVICE_CONFIG_ACTIVITY"
+ android:exported="true">
+ </activity>
+
+ <activity
+ android:name="android.print.cts.services.AddPrintersActivity"
+ android:permission="android.permission.START_PRINT_SERVICE_CONFIG_ACTIVITY"
+ android:exported="true">
+ </activity>
+
+ <activity
+ android:name="android.print.cts.services.CustomPrintOptionsActivity"
+ android:permission="android.permission.START_PRINT_SERVICE_CONFIG_ACTIVITY"
+ android:exported="true">
+ </activity>
+
+ </application>
+
+ <instrumentation android:name="android.support.test.uiautomator.UiAutomatorInstrumentationTestRunner"
+ android:targetPackage="com.android.cts.print"
+ android:label="Tests for the print APIs."/>
+
+</manifest>
diff --git a/tests/tests/print/res/values/strings.xml b/tests/tests/print/res/values/strings.xml
new file mode 100644
index 0000000..6d869e9
--- /dev/null
+++ b/tests/tests/print/res/values/strings.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+
+<resources>
+
+ <string name="resolution_200x200">200x200</string>
+ <string name="resolution_300x300">300x300</string>
+ <string name="resolution_600x600">600x600</string>
+
+</resources>
diff --git a/tests/tests/print/res/xml/printservice.xml b/tests/tests/print/res/xml/printservice.xml
new file mode 100644
index 0000000..5579b81
--- /dev/null
+++ b/tests/tests/print/res/xml/printservice.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+
+<print-service xmlns:android="http://schemas.android.com/apk/res/android"
+ android:settingsActivity="android.print.services.SettingsActivity"
+ android:addPrintersActivity="android.print.services.AddPrintersActivity"
+ android:advancedPrintOptionsActivity="android.print.services.CustomPrintOptionsActivity"/>
diff --git a/tests/tests/print/src/android/print/cts/BasePrintTest.java b/tests/tests/print/src/android/print/cts/BasePrintTest.java
new file mode 100644
index 0000000..d193bb0
--- /dev/null
+++ b/tests/tests/print/src/android/print/cts/BasePrintTest.java
@@ -0,0 +1,382 @@
+/*
+ * 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 android.print.cts;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doCallRealMethod;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
+import android.print.PageRange;
+import android.print.PrintAttributes;
+import android.print.PrintDocumentAdapter;
+import android.print.PrintDocumentAdapter.LayoutResultCallback;
+import android.print.PrintDocumentAdapter.WriteResultCallback;
+import android.print.PrintManager;
+import android.print.PrinterId;
+import android.print.cts.services.PrintServiceCallbacks;
+import android.print.cts.services.PrinterDiscoverySessionCallbacks;
+import android.print.cts.services.StubbablePrinterDiscoverySession;
+import android.printservice.PrintJob;
+import android.printservice.PrintService;
+import android.support.test.uiautomator.UiAutomatorTestCase;
+import android.support.test.uiautomator.UiObject;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.UiSelector;
+import android.util.DisplayMetrics;
+
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.mockito.InOrder;
+import org.mockito.stubbing.Answer;
+
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * This is the base class for print tests.
+ */
+public abstract class BasePrintTest extends UiAutomatorTestCase {
+
+ private static final long OPERATION_TIMEOUT = 10000;
+
+ private static final String ARG_PRIVILEGED_OPS = "ARG_PRIVILEGED_OPS";
+
+ private static final String PRINT_SPOOLER_PACKAGE_NAME = "com.android.printspooler";
+
+ protected static final String PRINT_JOB_NAME = "Test";
+
+ private PrintDocumentActivity mActivity;
+
+ private Locale mOldLocale;
+
+ private CallCounter mLayoutCallCounter;
+ private CallCounter mWriteCallCounter;
+ private CallCounter mFinishCallCounter;
+ private CallCounter mPrintJobQueuedCallCounter;
+ private CallCounter mDestroySessionCallCounter;
+
+ @Override
+ public void setUp() throws Exception {
+ // Make sure we start with a clean slate.
+ clearPrintSpoolerData();
+
+ // Workaround for dexmaker bug: https://code.google.com/p/dexmaker/issues/detail?id=2
+ // Dexmaker is used by mockito.
+ System.setProperty("dexmaker.dexcache", getInstrumentation()
+ .getTargetContext().getCacheDir().getPath());
+
+ // Set to US locale.
+ Resources resources = getInstrumentation().getTargetContext().getResources();
+ Configuration oldConfiguration = resources.getConfiguration();
+ if (!oldConfiguration.locale.equals(Locale.US)) {
+ mOldLocale = oldConfiguration.locale;
+ DisplayMetrics displayMetrics = resources.getDisplayMetrics();
+ Configuration newConfiguration = new Configuration(oldConfiguration);
+ newConfiguration.locale = Locale.US;
+ resources.updateConfiguration(newConfiguration, displayMetrics);
+ }
+
+ // Initialize the latches.
+ mLayoutCallCounter = new CallCounter();
+ mFinishCallCounter = new CallCounter();
+ mWriteCallCounter = new CallCounter();
+ mFinishCallCounter = new CallCounter();
+ mPrintJobQueuedCallCounter = new CallCounter();
+ mDestroySessionCallCounter = new CallCounter();
+
+ // Create the activity for the right locale.
+ createActivity();
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ // Done with the activity.
+ getActivity().finish();
+
+ // Restore the locale if needed.
+ if (mOldLocale != null) {
+ Resources resources = getInstrumentation().getTargetContext().getResources();
+ DisplayMetrics displayMetrics = resources.getDisplayMetrics();
+ Configuration newConfiguration = new Configuration(resources.getConfiguration());
+ newConfiguration.locale = mOldLocale;
+ mOldLocale = null;
+ resources.updateConfiguration(newConfiguration, displayMetrics);
+ }
+
+ // Make sure the spooler is cleaned.
+ clearPrintSpoolerData();
+ }
+
+ protected void print(final PrintDocumentAdapter adapter) {
+ // Initiate printing as if coming from the app.
+ getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ PrintManager printManager = (PrintManager) getActivity()
+ .getSystemService(Context.PRINT_SERVICE);
+ printManager.print("Print job", adapter, null);
+ }
+ });
+ }
+
+ protected void onLayoutCalled() {
+ mLayoutCallCounter.call();
+ }
+
+ protected void onWriteCalled() {
+ mWriteCallCounter.call();
+ }
+
+ protected void onFinishCalled() {
+ mFinishCallCounter.call();
+ }
+
+ protected void onPrintJobQueuedCalled() {
+ mPrintJobQueuedCallCounter.call();
+ }
+
+ protected void onPrinterDiscoverySessionDestroyCalled() {
+ mDestroySessionCallCounter.call();
+ }
+
+ protected void waitForPrinterDiscoverySessionDestroyCallbackCalled() {
+ waitForCallbackCallCount(mDestroySessionCallCounter, 1,
+ "Did not get expected call to onDestroyPrinterDiscoverySession.");
+ }
+
+ protected void waitForServiceOnPrintJobQueuedCallbackCalled() {
+ waitForCallbackCallCount(mPrintJobQueuedCallCounter, 1,
+ "Did not get expected call to onPrintJobQueued.");
+ }
+
+ protected void waitForAdapterFinishCallbackCalled() {
+ waitForCallbackCallCount(mFinishCallCounter, 1,
+ "Did not get expected call to finish.");
+ }
+
+ protected void waitForLayoutAdapterCallbackCount(int count) {
+ waitForCallbackCallCount(mLayoutCallCounter, count,
+ "Did not get expected call to layout.");
+ }
+
+ protected void waitForWriteForAdapterCallback() {
+ waitForCallbackCallCount(mWriteCallCounter, 1, "Did not get expected call to write.");
+ }
+
+ private void waitForCallbackCallCount(CallCounter counter, int count, String message) {
+ try {
+ counter.waitForCount(count, OPERATION_TIMEOUT);
+ } catch (TimeoutException te) {
+ fail(message);
+ }
+ }
+
+ protected void selectPrinter(String printerName) throws UiObjectNotFoundException {
+ UiObject destinationSpinner = new UiObject(new UiSelector().resourceId(
+ "com.android.printspooler:id/destination_spinner"));
+ destinationSpinner.click();
+ UiObject printerOption = new UiObject(new UiSelector().text(printerName));
+ printerOption.click();
+ }
+
+ protected void changeOrientation(String orientation) throws UiObjectNotFoundException {
+ UiObject orientationSpinner = new UiObject(new UiSelector().resourceId(
+ "com.android.printspooler:id/orientation_spinner"));
+ orientationSpinner.click();
+ UiObject orientationOption = new UiObject(new UiSelector().text(orientation));
+ orientationOption.click();
+ }
+
+ protected void changeMediaSize(String mediaSize) throws UiObjectNotFoundException {
+ UiObject mediaSizeSpinner = new UiObject(new UiSelector().resourceId(
+ "com.android.printspooler:id/paper_size_spinner"));
+ mediaSizeSpinner.click();
+ UiObject mediaSizeOption = new UiObject(new UiSelector().text(mediaSize));
+ mediaSizeOption.click();
+ }
+
+ protected void changeColor(String color) throws UiObjectNotFoundException {
+ UiObject colorSpinner = new UiObject(new UiSelector().resourceId(
+ "com.android.printspooler:id/color_spinner"));
+ colorSpinner.click();
+ UiObject colorOption = new UiObject(new UiSelector().text(color));
+ colorOption.click();
+ }
+
+ protected void clickPrintButton() throws UiObjectNotFoundException {
+ UiObject printButton = new UiObject(new UiSelector().resourceId(
+ "com.android.printspooler:id/print_button"));
+ printButton.click();
+ }
+
+ protected PrintDocumentActivity getActivity() {
+ return mActivity;
+ }
+
+ private void createActivity() {
+ mActivity = launchActivity(
+ getInstrumentation().getTargetContext().getPackageName(),
+ PrintDocumentActivity.class, null);
+ }
+
+ protected void clearPrintSpoolerData() throws Exception {
+ IPrivilegedOperations privilegedOps = IPrivilegedOperations.Stub.asInterface(
+ getParams().getBinder(ARG_PRIVILEGED_OPS));
+ privilegedOps.clearApplicationUserData(PRINT_SPOOLER_PACKAGE_NAME);
+ }
+
+ protected void verifyLayoutCall(InOrder inOrder, PrintDocumentAdapter mock,
+ PrintAttributes oldAttributes, PrintAttributes newAttributes,
+ final boolean forPreview) {
+ inOrder.verify(mock).onLayout(eq(oldAttributes), eq(newAttributes),
+ any(CancellationSignal.class), any(LayoutResultCallback.class), argThat(
+ new BaseMatcher<Bundle>() {
+ @Override
+ public boolean matches(Object item) {
+ Bundle bundle = (Bundle) item;
+ return forPreview == bundle.getBoolean(
+ PrintDocumentAdapter.EXTRA_PRINT_PREVIEW);
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ /* do nothing */
+ }
+ }));
+ }
+
+ protected PrintDocumentAdapter createMockPrintDocumentAdapter(Answer<Void> layoutAnswer,
+ Answer<Void> writeAnswer, Answer<Void> finishAnswer) {
+ // Create a mock print adapter.
+ PrintDocumentAdapter adapter = mock(PrintDocumentAdapter.class);
+ if (layoutAnswer != null) {
+ doAnswer(layoutAnswer).when(adapter).onLayout(any(PrintAttributes.class),
+ any(PrintAttributes.class), any(CancellationSignal.class),
+ any(LayoutResultCallback.class), any(Bundle.class));
+ }
+ if (writeAnswer != null) {
+ doAnswer(writeAnswer).when(adapter).onWrite(any(PageRange[].class),
+ any(ParcelFileDescriptor.class), any(CancellationSignal.class),
+ any(WriteResultCallback.class));
+ }
+ if (finishAnswer != null) {
+ doAnswer(finishAnswer).when(adapter).onFinish();
+ }
+ return adapter;
+ }
+
+ @SuppressWarnings("unchecked")
+ protected PrinterDiscoverySessionCallbacks createMockPrinterDiscoverySessionCallbacks(
+ Answer<Void> onStartPrinterDiscovery, Answer<Void> onStopPrinterDiscovery,
+ Answer<Void> onValidatePrinters, Answer<Void> onStartPrinterStateTracking,
+ Answer<Void> onStopPrinterStateTracking, Answer<Void> onDestroy) {
+ PrinterDiscoverySessionCallbacks callbacks = mock(PrinterDiscoverySessionCallbacks.class);
+
+ doCallRealMethod().when(callbacks).setSession(any(StubbablePrinterDiscoverySession.class));
+ when(callbacks.getSession()).thenCallRealMethod();
+
+ if (onStartPrinterDiscovery != null) {
+ doAnswer(onStartPrinterDiscovery).when(callbacks).onStartPrinterDiscovery(
+ any(List.class));
+ }
+ if (onStopPrinterDiscovery != null) {
+ doAnswer(onStopPrinterDiscovery).when(callbacks).onStopPrinterDiscovery();
+ }
+ if (onValidatePrinters != null) {
+ doAnswer(onValidatePrinters).when(callbacks).onValidatePrinters(
+ any(List.class));
+ }
+ if (onStartPrinterStateTracking != null) {
+ doAnswer(onStartPrinterStateTracking).when(callbacks).onStartPrinterStateTracking(
+ any(PrinterId.class));
+ }
+ if (onStopPrinterStateTracking != null) {
+ doAnswer(onStopPrinterStateTracking).when(callbacks).onStopPrinterStateTracking(
+ any(PrinterId.class));
+ }
+ if (onDestroy != null) {
+ doAnswer(onDestroy).when(callbacks).onDestroy();
+ }
+
+ return callbacks;
+ }
+
+ protected PrintServiceCallbacks createMockPrintServiceCallbacks(
+ Answer<PrinterDiscoverySessionCallbacks> onCreatePrinterDiscoverySessionCallbacks,
+ Answer<Void> onPrintJobQueued, Answer<Void> onRequestCancelPrintJob) {
+ final PrintServiceCallbacks service = mock(PrintServiceCallbacks.class);
+
+ doCallRealMethod().when(service).setService(any(PrintService.class));
+ when(service.getService()).thenCallRealMethod();
+
+ if (onCreatePrinterDiscoverySessionCallbacks != null) {
+ doAnswer(onCreatePrinterDiscoverySessionCallbacks).when(service)
+ .onCreatePrinterDiscoverySessionCallbacks();
+ }
+ if (onPrintJobQueued != null) {
+ doAnswer(onPrintJobQueued).when(service).onPrintJobQueued(any(PrintJob.class));
+ }
+ if (onRequestCancelPrintJob != null) {
+ doAnswer(onRequestCancelPrintJob).when(service).onRequestCancelPrintJob(
+ any(PrintJob.class));
+ }
+
+ return service;
+ }
+
+ protected final class CallCounter {
+ private final Object mLock = new Object();
+
+ private int mCallCount;
+
+ public void call() {
+ synchronized (mLock) {
+ mCallCount++;
+ }
+ }
+
+ public void waitForCount(int count, long timeoutMIllis) throws TimeoutException {
+ synchronized (mLock) {
+ final long startTimeMillis = SystemClock.uptimeMillis();
+ while (mCallCount < count) {
+ try {
+ final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
+ final long remainingTimeMillis = timeoutMIllis - elapsedTimeMillis;
+ if (remainingTimeMillis <= 0) {
+ throw new TimeoutException();
+ }
+ mLock.wait(timeoutMIllis);
+ } catch (InterruptedException ie) {
+ /* ignore */
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/tests/tests/print/src/android/print/cts/IPrivilegedOperations.aidl b/tests/tests/print/src/android/print/cts/IPrivilegedOperations.aidl
new file mode 100644
index 0000000..93c8c3e
--- /dev/null
+++ b/tests/tests/print/src/android/print/cts/IPrivilegedOperations.aidl
@@ -0,0 +1,21 @@
+/*
+ * 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 android.print.cts;
+
+interface IPrivilegedOperations {
+ boolean clearApplicationUserData(String packageName);
+}
diff --git a/tests/tests/print/src/android/print/cts/PageRangeAdjustmentTest.java b/tests/tests/print/src/android/print/cts/PageRangeAdjustmentTest.java
new file mode 100644
index 0000000..6b1bb3f
--- /dev/null
+++ b/tests/tests/print/src/android/print/cts/PageRangeAdjustmentTest.java
@@ -0,0 +1,509 @@
+/*
+ * 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 android.print.cts;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+
+import android.os.ParcelFileDescriptor;
+import android.print.PageRange;
+import android.print.PrintAttributes;
+import android.print.PrintAttributes.Margins;
+import android.print.PrintAttributes.MediaSize;
+import android.print.PrintAttributes.Resolution;
+import android.print.PrintDocumentAdapter;
+import android.print.PrintDocumentAdapter.LayoutResultCallback;
+import android.print.PrintDocumentAdapter.WriteResultCallback;
+import android.print.PrintDocumentInfo;
+import android.print.PrinterCapabilitiesInfo;
+import android.print.PrinterId;
+import android.print.PrinterInfo;
+import android.print.cts.services.FirstPrintService;
+import android.print.cts.services.PrintServiceCallbacks;
+import android.print.cts.services.PrinterDiscoverySessionCallbacks;
+import android.print.cts.services.SecondPrintService;
+import android.print.cts.services.StubbablePrinterDiscoverySession;
+import android.printservice.PrintJob;
+import android.printservice.PrintService;
+import android.support.test.uiautomator.UiObject;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.UiSelector;
+
+import org.mockito.InOrder;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This test verifies that the system correctly adjust the
+ * page ranges to be printed depending whether the app gave
+ * the requested pages, more pages, etc.
+ */
+public class PageRangeAdjustmentTest extends BasePrintTest {
+
+ private static final String FIRST_PRINTER = "First printer";
+
+ public void testAllPagesWantedAndAllPagesWritten() throws Exception {
+ // Create a callback for the target print service.
+ PrintServiceCallbacks firstServiceCallbacks = createMockPrintServiceCallbacks(
+ new Answer<PrinterDiscoverySessionCallbacks>() {
+ @Override
+ public PrinterDiscoverySessionCallbacks answer(InvocationOnMock invocation) {
+ return createMockFirstPrinterDiscoverySessionCallbacks();
+ }
+ },
+ new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) {
+ PrintJob printJob = (PrintJob) invocation.getArguments()[0];
+ PageRange[] pages = printJob.getInfo().getPages();
+ assert(pages.length == 1 && PageRange.ALL_PAGES.equals(pages[0]));
+ printJob.complete();
+ onPrintJobQueuedCalled();
+ return null;
+ }
+ }, null);
+
+ // Configure the print services.
+ FirstPrintService.setCallbacks(firstServiceCallbacks);
+ SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
+
+ // Create a mock print adapter.
+ final PrintDocumentAdapter adapter = createMockPrintDocumentAdapter(
+ new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ LayoutResultCallback callback = (LayoutResultCallback) invocation.getArguments()[3];
+ PrintDocumentInfo info = new PrintDocumentInfo.Builder(PRINT_JOB_NAME)
+ .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
+ .setPageCount(3)
+ .build();
+ callback.onLayoutFinished(info, false);
+ // Mark layout was called.
+ onLayoutCalled();
+ return null;
+ }
+ }, new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ Object[] args = invocation.getArguments();
+ PageRange[] pages = (PageRange[]) args[0];
+ ParcelFileDescriptor fd = (ParcelFileDescriptor) args[1];
+ WriteResultCallback callback = (WriteResultCallback) args[3];
+ fd.close();
+ callback.onWriteFinished(pages);
+ // Mark write was called.
+ onWriteCalled();
+ return null;
+ }
+ }, new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ // Mark finish was called.
+ onFinishCalled();
+ return null;
+ }
+ });
+
+ // Start printing.
+ print(adapter);
+
+ // Wait for write.
+ waitForWriteForAdapterCallback();
+
+ // Select the first printer.
+ selectPrinter(FIRST_PRINTER);
+
+ // Wait for layout as the printer has different capabilities.
+ waitForLayoutAdapterCallbackCount(2);
+
+ // Click the print button.
+ clickPrintButton();
+
+ // Wait for finish.
+ waitForAdapterFinishCallbackCalled();
+
+ // Wait for the print job.
+ waitForServiceOnPrintJobQueuedCallbackCalled();
+
+ // Verify the expected calls.
+ InOrder inOrder = inOrder(firstServiceCallbacks);
+
+ // We create a new session first.
+ inOrder.verify(firstServiceCallbacks)
+ .onCreatePrinterDiscoverySessionCallbacks();
+
+ // Next we wait for a call with the print job.
+ inOrder.verify(firstServiceCallbacks).onPrintJobQueued(
+ any(PrintJob.class));
+ }
+
+ public void testSomePagesWantedAndAllPagesWritten() throws Exception {
+ // Create a callback for the target print service.
+ PrintServiceCallbacks firstServiceCallbacks = createMockPrintServiceCallbacks(
+ new Answer<PrinterDiscoverySessionCallbacks>() {
+ @Override
+ public PrinterDiscoverySessionCallbacks answer(InvocationOnMock invocation) {
+ return createMockFirstPrinterDiscoverySessionCallbacks();
+ }
+ },
+ new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) {
+ PrintJob printJob = (PrintJob) invocation.getArguments()[0];
+ PageRange[] pages = printJob.getInfo().getPages();
+ // We always as for the first page for preview and in this
+ // case we write all, i.e. more that needed.
+ assertTrue(pages.length == 1 && pages[0].getStart() == 1
+ && pages[0].getEnd() == 1);
+ printJob.complete();
+ onPrintJobQueuedCalled();
+ return null;
+ }
+ }, null);
+
+ // Configure the print services.
+ FirstPrintService.setCallbacks(firstServiceCallbacks);
+ SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
+
+ // Create a mock print adapter.
+ final PrintDocumentAdapter adapter = createMockPrintDocumentAdapter(
+ new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ LayoutResultCallback callback = (LayoutResultCallback) invocation.getArguments()[3];
+ PrintDocumentInfo info = new PrintDocumentInfo.Builder(PRINT_JOB_NAME)
+ .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
+ .setPageCount(3)
+ .build();
+ callback.onLayoutFinished(info, false);
+ // Mark layout was called.
+ onLayoutCalled();
+ return null;
+ }
+ }, new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ Object[] args = invocation.getArguments();
+ ParcelFileDescriptor fd = (ParcelFileDescriptor) args[1];
+ WriteResultCallback callback = (WriteResultCallback) args[3];
+ fd.close();
+ callback.onWriteFinished(new PageRange[] {PageRange.ALL_PAGES});
+ // Mark write was called.
+ onWriteCalled();
+ return null;
+ }
+ }, new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ // Mark finish was called.
+ onFinishCalled();
+ return null;
+ }
+ });
+
+ // Start printing.
+ print(adapter);
+
+ // Wait for write.
+ waitForWriteForAdapterCallback();
+
+ // Select the first printer.
+ selectPrinter(FIRST_PRINTER);
+
+ // Wait for layout as the printer has different capabilities.
+ waitForLayoutAdapterCallbackCount(2);
+
+ // Select only the second page.
+ selectPages("2");
+
+ // Click the print button.
+ clickPrintButton();
+
+ // Wait for finish.
+ waitForAdapterFinishCallbackCalled();
+
+ // Wait for the print job.
+ waitForServiceOnPrintJobQueuedCallbackCalled();
+
+ // Verify the expected calls.
+ InOrder inOrder = inOrder(firstServiceCallbacks);
+
+ // We create a new session first.
+ inOrder.verify(firstServiceCallbacks)
+ .onCreatePrinterDiscoverySessionCallbacks();
+
+ // Next we wait for a call with the print job.
+ inOrder.verify(firstServiceCallbacks).onPrintJobQueued(
+ any(PrintJob.class));
+ }
+
+ public void testSomePagesWantedAndSomeMorePagesWritten() throws Exception {
+ // Create a callback for the target print service.
+ PrintServiceCallbacks firstServiceCallbacks = createMockPrintServiceCallbacks(
+ new Answer<PrinterDiscoverySessionCallbacks>() {
+ @Override
+ public PrinterDiscoverySessionCallbacks answer(InvocationOnMock invocation) {
+ return createMockFirstPrinterDiscoverySessionCallbacks();
+ }
+ },
+ new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) {
+ PrintJob printJob = (PrintJob) invocation.getArguments()[0];
+ PageRange[] pages = printJob.getInfo().getPages();
+ assert(pages.length == 1 && pages[0].getStart() == 1
+ && pages[0].getEnd() == 2);
+ printJob.complete();
+ onPrintJobQueuedCalled();
+ return null;
+ }
+ }, null);
+
+ // Configure the print services.
+ FirstPrintService.setCallbacks(firstServiceCallbacks);
+ SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
+
+ // Create a mock print adapter.
+ final PrintDocumentAdapter adapter = createMockPrintDocumentAdapter(
+ new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ LayoutResultCallback callback = (LayoutResultCallback) invocation.getArguments()[3];
+ PrintDocumentInfo info = new PrintDocumentInfo.Builder(PRINT_JOB_NAME)
+ .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
+ .setPageCount(3)
+ .build();
+ callback.onLayoutFinished(info, false);
+ // Mark layout was called.
+ onLayoutCalled();
+ return null;
+ }
+ }, new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ Object[] args = invocation.getArguments();
+ PageRange[] pages = (PageRange[]) args[0];
+ ParcelFileDescriptor fd = (ParcelFileDescriptor) args[1];
+ WriteResultCallback callback = (WriteResultCallback) args[3];
+ // We expect a single range as it is either the first page
+ // or the page we selected in the UI.
+ assertSame(pages.length, 1);
+ fd.close();
+
+ PageRange reqeustedPages = pages[0];
+ if (reqeustedPages.getStart() == reqeustedPages.getEnd()
+ && reqeustedPages.getEnd() == 0) {
+ // If asked for the first page, which is for preview
+ // then write it...
+ callback.onWriteFinished(pages);
+ } else {
+ // otherwise write a page more that the one we selected.
+ callback.onWriteFinished(new PageRange[] {new PageRange(2, 3)});
+ }
+ // Mark write was called.
+ onWriteCalled();
+ return null;
+ }
+ }, new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ // Mark finish was called.
+ onFinishCalled();
+ return null;
+ }
+ });
+
+ // Start printing.
+ print(adapter);
+
+ // Wait for write.
+ waitForWriteForAdapterCallback();
+
+ // Select the first printer.
+ selectPrinter(FIRST_PRINTER);
+
+ // Wait for layout as the printer has different capabilities.
+ waitForLayoutAdapterCallbackCount(2);
+
+ // Select only the third page.
+ selectPages("3");
+
+ // Click the print button.
+ clickPrintButton();
+
+ // Wait for finish.
+ waitForAdapterFinishCallbackCalled();
+
+ // Wait for the print job.
+ waitForServiceOnPrintJobQueuedCallbackCalled();
+
+ // Verify the expected calls.
+ InOrder inOrder = inOrder(firstServiceCallbacks);
+
+ // We create a new session first.
+ inOrder.verify(firstServiceCallbacks)
+ .onCreatePrinterDiscoverySessionCallbacks();
+
+ // Next we wait for a call with the print job.
+ inOrder.verify(firstServiceCallbacks).onPrintJobQueued(
+ any(PrintJob.class));
+ }
+
+ public void testSomePagesWantedAndNotWritten() throws Exception {
+ // Create a callback for the target print service.
+ PrintServiceCallbacks firstServiceCallbacks = createMockPrintServiceCallbacks(
+ new Answer<PrinterDiscoverySessionCallbacks>() {
+ @Override
+ public PrinterDiscoverySessionCallbacks answer(InvocationOnMock invocation) {
+ return createMockFirstPrinterDiscoverySessionCallbacks();
+ }
+ },
+ null, null);
+
+ // Configure the print services.
+ FirstPrintService.setCallbacks(firstServiceCallbacks);
+ SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
+
+ // Create a mock print adapter.
+ final PrintDocumentAdapter adapter = createMockPrintDocumentAdapter(
+ new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ LayoutResultCallback callback = (LayoutResultCallback) invocation.getArguments()[3];
+ PrintDocumentInfo info = new PrintDocumentInfo.Builder(PRINT_JOB_NAME)
+ .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
+ .setPageCount(3)
+ .build();
+ callback.onLayoutFinished(info, false);
+ // Mark layout was called.
+ onLayoutCalled();
+ return null;
+ }
+ }, new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ Object[] args = invocation.getArguments();
+ PageRange[] pages = (PageRange[]) args[0];
+ ParcelFileDescriptor fd = (ParcelFileDescriptor) args[1];
+ WriteResultCallback callback = (WriteResultCallback) args[3];
+ assertSame(pages.length, 1);
+ fd.close();
+
+ // We should be asked for the first page...
+ assertSame(pages[0].getStart(), 0);
+ assertSame(pages[0].getEnd(), 0);
+
+ // ...just write a the wring page.
+ callback.onWriteFinished(new PageRange[] {new PageRange(1, 1)});
+
+ // Mark write was called.
+ onWriteCalled();
+ return null;
+ }
+ }, new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ // Mark finish was called.
+ onFinishCalled();
+ return null;
+ }
+ });
+
+ // Start printing.
+ print(adapter);
+
+ // Wait for write.
+ waitForWriteForAdapterCallback();
+
+ // Cancel printing.
+ getUiDevice().pressBack(); // wakes up the device.
+ getUiDevice().pressBack();
+
+ // Wait for finish.
+ waitForAdapterFinishCallbackCalled();
+
+ // Verify the expected calls.
+ InOrder inOrder = inOrder(firstServiceCallbacks);
+
+ // We create a new session first.
+ inOrder.verify(firstServiceCallbacks)
+ .onCreatePrinterDiscoverySessionCallbacks();
+
+ // We should not receive a print job callback.
+ inOrder.verify(firstServiceCallbacks, never()).onPrintJobQueued(
+ any(PrintJob.class));
+ }
+
+ private void selectPages(String pages) throws UiObjectNotFoundException {
+ UiObject pagesSpinner = getUiDevice().findObject(new UiSelector().resourceId(
+ "com.android.printspooler:id/range_options_spinner"));
+ pagesSpinner.click();
+
+ UiObject rangeOption = getUiDevice().findObject(new UiSelector().text("Range"));
+ rangeOption.click();
+
+ UiObject pagesEditText = getUiDevice().findObject(new UiSelector().resourceId(
+ "com.android.printspooler:id/page_range_edittext"));
+ pagesEditText.setText(pages);
+ }
+
+ private PrinterDiscoverySessionCallbacks createMockFirstPrinterDiscoverySessionCallbacks() {
+ return createMockPrinterDiscoverySessionCallbacks(new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) {
+ PrinterDiscoverySessionCallbacks mock = (PrinterDiscoverySessionCallbacks)
+ invocation.getMock();
+
+ StubbablePrinterDiscoverySession session = mock.getSession();
+ PrintService service = session.getService();
+
+ if (session.getPrinters().isEmpty()) {
+ List<PrinterInfo> printers = new ArrayList<PrinterInfo>();
+
+ // Add one printer.
+ PrinterId firstPrinterId = service.generatePrinterId("first_printer");
+ PrinterCapabilitiesInfo firstCapabilities =
+ new PrinterCapabilitiesInfo.Builder(firstPrinterId)
+ .setMinMargins(new Margins(200, 200, 200, 200))
+ .addMediaSize(MediaSize.ISO_A4, true)
+ .addMediaSize(MediaSize.ISO_A5, false)
+ .addResolution(new Resolution("300x300", "300x300", 300, 300), true)
+ .setColorModes(PrintAttributes.COLOR_MODE_COLOR,
+ PrintAttributes.COLOR_MODE_COLOR)
+ .build();
+ PrinterInfo firstPrinter = new PrinterInfo.Builder(firstPrinterId,
+ FIRST_PRINTER, PrinterInfo.STATUS_IDLE)
+ .setCapabilities(firstCapabilities)
+ .build();
+ printers.add(firstPrinter);
+
+ session.addPrinters(printers);
+ }
+
+ return null;
+ }
+ }, null, null, null, null, null);
+ }
+
+ private PrintServiceCallbacks createSecondMockPrintServiceCallbacks() {
+ return createMockPrintServiceCallbacks(null, null, null);
+ }
+}
diff --git a/tests/tests/print/src/android/print/cts/PrintDocumentActivity.java b/tests/tests/print/src/android/print/cts/PrintDocumentActivity.java
new file mode 100644
index 0000000..6a191a6
--- /dev/null
+++ b/tests/tests/print/src/android/print/cts/PrintDocumentActivity.java
@@ -0,0 +1,28 @@
+/*
+ * 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 android.print.cts;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class PrintDocumentActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+}
diff --git a/tests/tests/print/src/android/print/cts/PrintDocumentAdapterContractTest.java b/tests/tests/print/src/android/print/cts/PrintDocumentAdapterContractTest.java
new file mode 100644
index 0000000..d445692
--- /dev/null
+++ b/tests/tests/print/src/android/print/cts/PrintDocumentAdapterContractTest.java
@@ -0,0 +1,1481 @@
+/*
+ * 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 android.print.cts;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import android.os.CancellationSignal;
+import android.os.CancellationSignal.OnCancelListener;
+import android.os.ParcelFileDescriptor;
+import android.print.PageRange;
+import android.print.PrintAttributes;
+import android.print.PrintAttributes.Margins;
+import android.print.PrintAttributes.MediaSize;
+import android.print.PrintAttributes.Resolution;
+import android.print.PrintDocumentAdapter;
+import android.print.PrintDocumentAdapter.LayoutResultCallback;
+import android.print.PrintDocumentAdapter.WriteResultCallback;
+import android.print.PrintDocumentInfo;
+import android.print.PrinterCapabilitiesInfo;
+import android.print.PrinterId;
+import android.print.PrinterInfo;
+import android.print.cts.services.FirstPrintService;
+import android.print.cts.services.PrintServiceCallbacks;
+import android.print.cts.services.PrinterDiscoverySessionCallbacks;
+import android.print.cts.services.SecondPrintService;
+import android.print.cts.services.StubbablePrinterDiscoverySession;
+import android.printservice.PrintJob;
+import android.printservice.PrintService;
+
+import org.mockito.InOrder;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This test verifies that the system respects the {@link PrintDocumentAdapter}
+ * contract and invokes all callbacks as expected.
+ */
+public class PrintDocumentAdapterContractTest extends BasePrintTest {
+
+ public void testNoPrintOptionsOrPrinterChange() throws Exception {
+ // Configure the print services.
+ FirstPrintService.setCallbacks(createFirstMockPrintServiceCallbacks());
+ SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
+
+ // Create a mock print adapter.
+ final PrintDocumentAdapter adapter = createMockPrintDocumentAdapter(
+ new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ LayoutResultCallback callback = (LayoutResultCallback) invocation.getArguments()[3];
+ PrintDocumentInfo info = new PrintDocumentInfo.Builder(PRINT_JOB_NAME)
+ .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT).setPageCount(1)
+ .build();
+ callback.onLayoutFinished(info, false);
+ // Mark layout was called.
+ onLayoutCalled();
+ return null;
+ }
+ }, new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ Object[] args = invocation.getArguments();
+ PageRange[] pages = (PageRange[]) args[0];
+ ParcelFileDescriptor fd = (ParcelFileDescriptor) args[1];
+ WriteResultCallback callback = (WriteResultCallback) args[3];
+ fd.close();
+ callback.onWriteFinished(pages);
+ // Mark write was called.
+ onWriteCalled();
+ return null;
+ }
+ }, new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ // Mark finish was called.
+ onFinishCalled();
+ return null;
+ }
+ });
+
+ // Start printing.
+ print(adapter);
+
+ // Wait for write.
+ waitForWriteForAdapterCallback();
+
+ // Select the second printer.
+ selectPrinter("Second printer");
+
+ // Wait for layout.
+ waitForLayoutAdapterCallbackCount(2);
+
+ // Click the print button.
+ clickPrintButton();
+
+ // Wait for finish.
+ waitForAdapterFinishCallbackCalled();
+
+ // Verify the expected calls.
+ InOrder inOrder = inOrder(adapter);
+
+ // Start is always called first.
+ inOrder.verify(adapter).onStart();
+
+ // Start is always followed by a layout. The PDF printer is selected if
+ // there are other printers but none of them was used.
+ PrintAttributes firstOldAttributes = new PrintAttributes.Builder().build();
+ PrintAttributes firstNewAttributes = new PrintAttributes.Builder()
+ .setMediaSize(MediaSize.NA_LETTER)
+ .setResolution(new Resolution("PDF resolution", "PDF resolution", 300, 300))
+ .setMinMargins(Margins.NO_MARGINS)
+ .setColorMode(PrintAttributes.COLOR_MODE_COLOR)
+ .build();
+ verifyLayoutCall(inOrder, adapter, firstOldAttributes, firstNewAttributes, true);
+
+ // We always ask for the first page for preview.
+ PageRange[] firstPages = new PageRange[] {new PageRange(0, 0)};
+ inOrder.verify(adapter).onWrite(eq(firstPages), any(ParcelFileDescriptor.class),
+ any(CancellationSignal.class), any(WriteResultCallback.class));
+
+ // We selected the second printer which does not support the media
+ // size that was selected, so a new layout happens as the size changed.
+ // Since we passed false to the layout callback meaning that the content
+ // didn't change, there shouldn't be a next call to write.
+ PrintAttributes secondOldAttributes = firstNewAttributes;
+ PrintAttributes secondNewAttributes = new PrintAttributes.Builder()
+ .setMediaSize(MediaSize.ISO_A3)
+ .setResolution(new Resolution("300x300", "300x300", 300, 300))
+ .setMinMargins(Margins.NO_MARGINS)
+ .setColorMode(PrintAttributes.COLOR_MODE_COLOR)
+ .build();
+ verifyLayoutCall(inOrder, adapter, secondOldAttributes, secondNewAttributes, true);
+
+ // When print is pressed we ask for a layout which is *not* for preview.
+ verifyLayoutCall(inOrder, adapter, secondNewAttributes, secondNewAttributes, false);
+
+ // When print is pressed we ask for all selected pages.
+ PageRange[] secondPages = new PageRange[] {PageRange.ALL_PAGES};
+ inOrder.verify(adapter).onWrite(eq(secondPages), any(ParcelFileDescriptor.class),
+ any(CancellationSignal.class), any(WriteResultCallback.class));
+
+ // Finish is always called last.
+ inOrder.verify(adapter).onFinish();
+
+ // No other call are expected.
+ verifyNoMoreInteractions(adapter);
+ }
+
+ public void testNoPrintOptionsOrPrinterChangeCanceled() throws Exception {
+ // Configure the print services.
+ FirstPrintService.setCallbacks(createFirstMockPrintServiceCallbacks());
+ SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
+
+ // Create a mock print adapter.
+ final PrintDocumentAdapter adapter = createMockPrintDocumentAdapter(
+ new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ LayoutResultCallback callback = (LayoutResultCallback)
+ invocation.getArguments()[3];
+ PrintDocumentInfo info = new PrintDocumentInfo.Builder(PRINT_JOB_NAME)
+ .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
+ .setPageCount(1)
+ .build();
+ callback.onLayoutFinished(info, false);
+ return null;
+ }
+ }, new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ Object[] args = invocation.getArguments();
+ PageRange[] pages = (PageRange[]) args[0];
+ ParcelFileDescriptor fd = (ParcelFileDescriptor) args[1];
+ WriteResultCallback callback = (WriteResultCallback) args[3];
+ fd.close();
+ callback.onWriteFinished(pages);
+ // Mark write was called.
+ onWriteCalled();
+ return null;
+ }
+ }, new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ // Mark finish was called.
+ onFinishCalled();
+ return null;
+ }
+ });
+
+ // Start printing.
+ print(adapter);
+
+ // Wait for write.
+ waitForWriteForAdapterCallback();
+
+ // Cancel the printing.
+ getUiDevice().pressBack(); // wakes up the device.
+ getUiDevice().pressBack();
+
+ // Wait for finish.
+ waitForAdapterFinishCallbackCalled();
+
+ // Verify the expected calls.
+ InOrder inOrder = inOrder(adapter);
+
+ // Start is always called first.
+ inOrder.verify(adapter).onStart();
+
+ // Start is always followed by a layout. The PDF printer is selected if
+ // there are other printers but none of them was used.
+ PrintAttributes firstOldAttributes = new PrintAttributes.Builder().build();
+ PrintAttributes firstNewAttributes = new PrintAttributes.Builder()
+ .setMediaSize(MediaSize.NA_LETTER)
+ .setResolution(new Resolution("PDF resolution", "PDF resolution", 300, 300))
+ .setMinMargins(Margins.NO_MARGINS)
+ .setColorMode(PrintAttributes.COLOR_MODE_COLOR)
+ .build();
+ verifyLayoutCall(inOrder, adapter, firstOldAttributes, firstNewAttributes, true);
+
+ // We always ask for the first page for preview.
+ PageRange[] firstPages = new PageRange[] {new PageRange(0, 0)};
+ inOrder.verify(adapter).onWrite(eq(firstPages), any(ParcelFileDescriptor.class),
+ any(CancellationSignal.class), any(WriteResultCallback.class));
+
+ // Finish is always called last.
+ inOrder.verify(adapter).onFinish();
+
+ // No other call are expected.
+ verifyNoMoreInteractions(adapter);
+ }
+
+ public void testPrintOptionsChangeAndNoPrinterChange() throws Exception {
+ // Configure the print services.
+ FirstPrintService.setCallbacks(createFirstMockPrintServiceCallbacks());
+ SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
+
+ // Create a mock print adapter.
+ final PrintDocumentAdapter adapter = createMockPrintDocumentAdapter(
+ new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ LayoutResultCallback callback = (LayoutResultCallback)
+ invocation.getArguments()[3];
+ PrintDocumentInfo info = new PrintDocumentInfo.Builder(PRINT_JOB_NAME)
+ .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
+ .setPageCount(1)
+ .build();
+ callback.onLayoutFinished(info, false);
+ // Mark layout was called.
+ onLayoutCalled();
+ return null;
+ }
+ }, new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ Object[] args = invocation.getArguments();
+ PageRange[] pages = (PageRange[]) args[0];
+ ParcelFileDescriptor fd = (ParcelFileDescriptor) args[1];
+ WriteResultCallback callback = (WriteResultCallback) args[3];
+ fd.close();
+ callback.onWriteFinished(pages);
+ // Mark write was called.
+ onWriteCalled();
+ return null;
+ }
+ }, new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ // Mark finish was called.
+ onFinishCalled();
+ return null;
+ }
+ });
+
+ // Start printing.
+ print(adapter);
+
+ // Wait for write.
+ waitForWriteForAdapterCallback();
+
+ // Select the second printer.
+ selectPrinter("Second printer");
+
+ // Wait for layout.
+ waitForLayoutAdapterCallbackCount(2);
+
+ // Change the orientation.
+ changeOrientation("Landscape");
+
+ // Wait for layout.
+ waitForLayoutAdapterCallbackCount(3);
+
+ // Change the media size.
+ changeMediaSize("ISO A4");
+
+ // Wait for layout.
+ waitForLayoutAdapterCallbackCount(4);
+
+ // Change the color.
+ changeColor("Black & White");
+
+ // Wait for layout.
+ waitForLayoutAdapterCallbackCount(5);
+
+ // Click the print button.
+ clickPrintButton();
+
+ // Wait for a finish.
+ waitForAdapterFinishCallbackCalled();
+
+ // Verify the expected calls.
+ InOrder inOrder = inOrder(adapter);
+
+ // Start is always called first.
+ inOrder.verify(adapter).onStart();
+
+ // Start is always followed by a layout. The PDF printer is selected if
+ // there are other printers but none of them was used.
+ PrintAttributes firstOldAttributes = new PrintAttributes.Builder().build();
+ PrintAttributes firstNewAttributes = new PrintAttributes.Builder()
+ .setMediaSize(MediaSize.NA_LETTER)
+ .setResolution(new Resolution("PDF resolution", "PDF resolution", 300, 300))
+ .setMinMargins(Margins.NO_MARGINS)
+ .setColorMode(PrintAttributes.COLOR_MODE_COLOR)
+ .build();
+ verifyLayoutCall(inOrder, adapter, firstOldAttributes, firstNewAttributes, true);
+
+ // We always ask for the first page for preview.
+ PageRange[] firstPages = new PageRange[] {new PageRange(0, 0)};
+ inOrder.verify(adapter).onWrite(eq(firstPages), any(ParcelFileDescriptor.class),
+ any(CancellationSignal.class), any(WriteResultCallback.class));
+
+ // We selected the second printer which does not support the media
+ // size that was selected, so a new layout happens as the size changed.
+ // Since we passed false to the layout callback meaning that the content
+ // didn't change, there shouldn't be a next call to write.
+ PrintAttributes secondOldAttributes = firstNewAttributes;
+ PrintAttributes secondNewAttributes = new PrintAttributes.Builder()
+ .setMediaSize(MediaSize.ISO_A3)
+ .setResolution(new Resolution("300x300", "300x300", 300, 300))
+ .setMinMargins(Margins.NO_MARGINS)
+ .setColorMode(PrintAttributes.COLOR_MODE_COLOR)
+ .build();
+ verifyLayoutCall(inOrder, adapter, secondOldAttributes, secondNewAttributes, true);
+
+ // We changed the orientation which triggers a layout. Since we passed
+ // false to the layout callback meaning that the content didn't change,
+ // there shouldn't be a next call to write.
+ PrintAttributes thirdOldAttributes = secondNewAttributes;
+ PrintAttributes thirdNewAttributes = new PrintAttributes.Builder()
+ .setMediaSize(MediaSize.ISO_A3.asLandscape())
+ .setResolution(new Resolution("300x300", "300x300", 300, 300))
+ .setMinMargins(Margins.NO_MARGINS)
+ .setColorMode(PrintAttributes.COLOR_MODE_COLOR)
+ .build();
+ verifyLayoutCall(inOrder, adapter, thirdOldAttributes, thirdNewAttributes, true);
+
+ // We changed the media size which triggers a layout. Since we passed
+ // false to the layout callback meaning that the content didn't change,
+ // there shouldn't be a next call to write.
+ PrintAttributes fourthOldAttributes = thirdNewAttributes;
+ PrintAttributes fourthNewAttributes = new PrintAttributes.Builder()
+ .setMediaSize(MediaSize.ISO_A4.asLandscape())
+ .setResolution(new Resolution("300x300", "300x300", 300, 300))
+ .setMinMargins(Margins.NO_MARGINS)
+ .setColorMode(PrintAttributes.COLOR_MODE_COLOR)
+ .build();
+ verifyLayoutCall(inOrder, adapter, fourthOldAttributes, fourthNewAttributes, true);
+
+ // We changed the color which triggers a layout. Since we passed
+ // false to the layout callback meaning that the content didn't change,
+ // there shouldn't be a next call to write.
+ PrintAttributes fifthOldAttributes = fourthNewAttributes;
+ PrintAttributes fifthNewAttributes = new PrintAttributes.Builder()
+ .setMediaSize(MediaSize.ISO_A4.asLandscape())
+ .setResolution(new Resolution("300x300", "300x300", 300, 300))
+ .setMinMargins(Margins.NO_MARGINS)
+ .setColorMode(PrintAttributes.COLOR_MODE_MONOCHROME)
+ .build();
+ verifyLayoutCall(inOrder, adapter, fifthOldAttributes, fifthNewAttributes, true);
+
+ // When print is pressed we ask for a layout which is *not* for preview.
+ verifyLayoutCall(inOrder, adapter, fifthNewAttributes, fifthNewAttributes, false);
+
+ // When print is pressed we ask for all selected pages.
+ PageRange[] secondPages = new PageRange[] {PageRange.ALL_PAGES};
+ inOrder.verify(adapter).onWrite(eq(secondPages), any(ParcelFileDescriptor.class),
+ any(CancellationSignal.class), any(WriteResultCallback.class));
+
+ // Finish is always called last.
+ inOrder.verify(adapter).onFinish();
+
+ // No other call are expected.
+ verifyNoMoreInteractions(adapter);
+ }
+
+ public void testPrintOptionsChangeAndPrinterChange() throws Exception {
+ // Configure the print services.
+ FirstPrintService.setCallbacks(createFirstMockPrintServiceCallbacks());
+ SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
+
+ // Create a mock print adapter.
+ final PrintDocumentAdapter adapter = createMockPrintDocumentAdapter(
+ new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ LayoutResultCallback callback = (LayoutResultCallback)
+ invocation.getArguments()[3];
+ PrintDocumentInfo info = new PrintDocumentInfo.Builder(PRINT_JOB_NAME)
+ .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
+ .setPageCount(1)
+ .build();
+ callback.onLayoutFinished(info, false);
+ // Mark layout was called.
+ onLayoutCalled();
+ return null;
+ }
+ }, new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ Object[] args = invocation.getArguments();
+ PageRange[] pages = (PageRange[]) args[0];
+ ParcelFileDescriptor fd = (ParcelFileDescriptor) args[1];
+ WriteResultCallback callback = (WriteResultCallback) args[3];
+ fd.close();
+ callback.onWriteFinished(pages);
+ // Mark write was called.
+ onWriteCalled();
+ return null;
+ }
+ }, new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ // Mark finish was called.
+ onFinishCalled();
+ return null;
+ }
+ });
+
+ // Start printing.
+ print(adapter);
+
+ // Wait for write.
+ waitForWriteForAdapterCallback();
+
+ // Select the second printer.
+ selectPrinter("Second printer");
+
+ // Wait for layout.
+ waitForLayoutAdapterCallbackCount(2);
+
+ // Change the color.
+ changeColor("Black & White");
+
+ // Wait for layout.
+ waitForLayoutAdapterCallbackCount(3);
+
+ // Change the printer to one which supports the current media size.
+ // Select the second printer.
+ selectPrinter("First printer");
+
+ // Wait for layout.
+ waitForLayoutAdapterCallbackCount(4);
+
+ // Click the print button.
+ clickPrintButton();
+
+ // Wait for a finish.
+ waitForAdapterFinishCallbackCalled();
+
+ // Verify the expected calls.
+ InOrder inOrder = inOrder(adapter);
+
+ // Start is always called first.
+ inOrder.verify(adapter).onStart();
+
+ // Start is always followed by a layout. The PDF printer is selected if
+ // there are other printers but none of them was used.
+ PrintAttributes firstOldAttributes = new PrintAttributes.Builder().build();
+ PrintAttributes firstNewAttributes = new PrintAttributes.Builder()
+ .setMediaSize(MediaSize.NA_LETTER)
+ .setResolution(new Resolution("PDF resolution", "PDF resolution", 300, 300))
+ .setMinMargins(Margins.NO_MARGINS).setColorMode(PrintAttributes.COLOR_MODE_COLOR)
+ .build();
+ verifyLayoutCall(inOrder, adapter, firstOldAttributes, firstNewAttributes, true);
+
+ // We always ask for the first page for preview.
+ PageRange[] firstPages = new PageRange[] {new PageRange(0, 0)};
+ inOrder.verify(adapter).onWrite(eq(firstPages), any(ParcelFileDescriptor.class),
+ any(CancellationSignal.class), any(WriteResultCallback.class));
+
+ // We changed the printer and the new printer does not support the
+ // selected media size in which case the default media size of the
+ // printer is used resulting in a layout pass. Same for margins.
+ PrintAttributes secondOldAttributes = firstNewAttributes;
+ PrintAttributes secondNewAttributes = new PrintAttributes.Builder()
+ .setMediaSize(MediaSize.ISO_A3)
+ .setResolution(new Resolution("PDF resolution", "PDF resolution", 300, 300))
+ .setMinMargins(new Margins(0, 0, 0, 0))
+ .setColorMode(PrintAttributes.COLOR_MODE_COLOR)
+ .build();
+ verifyLayoutCall(inOrder, adapter, secondOldAttributes, secondNewAttributes, true);
+
+ // We changed the printer and the new printer does not support the
+ // current color in which case the default color for the selected
+ // printer is used resulting in a layout pass.
+ PrintAttributes thirdOldAttributes = secondNewAttributes;
+ PrintAttributes thirdNewAttributes = new PrintAttributes.Builder()
+ .setMediaSize(MediaSize.ISO_A3)
+ .setResolution(new Resolution("PDF resolution", "PDF resolution", 300, 300))
+ .setMinMargins(new Margins(0, 0, 0, 0))
+ .setColorMode(PrintAttributes.COLOR_MODE_MONOCHROME)
+ .build();
+ verifyLayoutCall(inOrder, adapter, thirdOldAttributes, thirdNewAttributes, true);
+
+ // We changed the printer to one that does not support the current
+ // media size in which case we pick the default media size for the
+ // new printer which results in a layout pass. Same for color.
+ PrintAttributes fourthOldAttributes = thirdNewAttributes;
+ PrintAttributes fourthNewAttributes = new PrintAttributes.Builder()
+ .setMediaSize(MediaSize.ISO_A4)
+ .setResolution(new Resolution("PDF resolution", "PDF resolution", 300, 300))
+ .setMinMargins(new Margins(200, 200, 200, 200))
+ .setColorMode(PrintAttributes.COLOR_MODE_COLOR)
+ .build();
+ verifyLayoutCall(inOrder, adapter, fourthOldAttributes, fourthNewAttributes, true);
+
+ // When print is pressed we ask for a layout which is *not* for preview.
+ verifyLayoutCall(inOrder, adapter, fourthNewAttributes, fourthNewAttributes, false);
+
+ // When print is pressed we ask for all selected pages.
+ PageRange[] secondPages = new PageRange[] {PageRange.ALL_PAGES};
+ inOrder.verify(adapter).onWrite(eq(secondPages), any(ParcelFileDescriptor.class),
+ any(CancellationSignal.class), any(WriteResultCallback.class));
+
+ // Finish is always called last.
+ inOrder.verify(adapter).onFinish();
+
+ // No other call are expected.
+ verifyNoMoreInteractions(adapter);
+ }
+
+ public void testPrintOptionsChangeAndNoPrinterChangeAndContentChange()
+ throws Exception {
+ // Configure the print services.
+ FirstPrintService.setCallbacks(createFirstMockPrintServiceCallbacks());
+ SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
+
+ // Create a mock print adapter.
+ final PrintDocumentAdapter adapter = createMockPrintDocumentAdapter(
+ new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ LayoutResultCallback callback = (LayoutResultCallback) invocation.getArguments()[3];
+ PrintDocumentInfo info = new PrintDocumentInfo.Builder(PRINT_JOB_NAME)
+ .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT).setPageCount(1)
+ .build();
+ // The content changes after every layout.
+ callback.onLayoutFinished(info, true);
+ // Mark layout was called.
+ onLayoutCalled();
+ return null;
+ }
+ }, new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ Object[] args = invocation.getArguments();
+ PageRange[] pages = (PageRange[]) args[0];
+ ParcelFileDescriptor fd = (ParcelFileDescriptor) args[1];
+ WriteResultCallback callback = (WriteResultCallback) args[3];
+ fd.close();
+ callback.onWriteFinished(pages);
+ // Mark write was called.
+ onWriteCalled();
+ return null;
+ }
+ }, new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ // Mark finish was called.
+ onFinishCalled();
+ return null;
+ }
+ });
+
+ // Start printing.
+ print(adapter);
+
+ // Wait for write.
+ waitForWriteForAdapterCallback();
+
+ // Select the second printer.
+ selectPrinter("Second printer");
+
+ // Wait for layout.
+ waitForLayoutAdapterCallbackCount(2);
+
+ // Click the print button.
+ clickPrintButton();
+
+ // Wait for a finish.
+ waitForAdapterFinishCallbackCalled();
+
+ // Verify the expected calls.
+ InOrder inOrder = inOrder(adapter);
+
+ // Start is always called first.
+ inOrder.verify(adapter).onStart();
+
+ // Start is always followed by a layout. The PDF printer is selected if
+ // there are other printers but none of them was used.
+ PrintAttributes firstOldAttributes = new PrintAttributes.Builder().build();
+ PrintAttributes firstNewAttributes = new PrintAttributes.Builder()
+ .setMediaSize(MediaSize.NA_LETTER)
+ .setResolution(new Resolution("PDF resolution", "PDF resolution", 300, 300))
+ .setMinMargins(Margins.NO_MARGINS).setColorMode(PrintAttributes.COLOR_MODE_COLOR)
+ .build();
+ verifyLayoutCall(inOrder, adapter, firstOldAttributes, firstNewAttributes, true);
+
+ // We always ask for the first page for preview.
+ PageRange[] firstPages = new PageRange[] {new PageRange(0, 0)};
+ inOrder.verify(adapter).onWrite(eq(firstPages), any(ParcelFileDescriptor.class),
+ any(CancellationSignal.class), any(WriteResultCallback.class));
+
+ // We selected the second printer which does not support the media
+ // size that was selected, so a new layout happens as the size changed.
+ PrintAttributes secondOldAttributes = firstNewAttributes;
+ PrintAttributes secondNewAttributes = new PrintAttributes.Builder()
+ .setMediaSize(MediaSize.ISO_A3)
+ .setResolution(new Resolution("300x300", "300x300", 300, 300))
+ .setMinMargins(Margins.NO_MARGINS).setColorMode(PrintAttributes.COLOR_MODE_COLOR)
+ .build();
+ verifyLayoutCall(inOrder, adapter, secondOldAttributes, secondNewAttributes, true);
+
+ // In the layout callback we reported that the content changed,
+ // so the previously written page has to be written again.
+ PageRange[] secondPages = new PageRange[] {new PageRange(0, 0)};
+ inOrder.verify(adapter).onWrite(eq(secondPages), any(ParcelFileDescriptor.class),
+ any(CancellationSignal.class), any(WriteResultCallback.class));
+
+ // When print is pressed we ask for a layout which is *not* for preview.
+ verifyLayoutCall(inOrder, adapter, secondNewAttributes, secondNewAttributes, false);
+
+ // When print is pressed we ask for all selected pages.
+ PageRange[] thirdPages = new PageRange[] {PageRange.ALL_PAGES};
+ inOrder.verify(adapter).onWrite(eq(thirdPages), any(ParcelFileDescriptor.class),
+ any(CancellationSignal.class), any(WriteResultCallback.class));
+
+ // Finish is always called last.
+ inOrder.verify(adapter).onFinish();
+
+ // No other call are expected.
+ verifyNoMoreInteractions(adapter);
+ }
+
+ public void testNewPrinterSupportsSelectedPrintOptions() throws Exception {
+ // Configure the print services.
+ FirstPrintService.setCallbacks(createFirstMockPrintServiceCallbacks());
+ SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
+
+ // Create a mock print adapter.
+ final PrintDocumentAdapter adapter = createMockPrintDocumentAdapter(
+ new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ LayoutResultCallback callback = (LayoutResultCallback) invocation.getArguments()[3];
+ PrintDocumentInfo info = new PrintDocumentInfo.Builder(PRINT_JOB_NAME)
+ .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT).setPageCount(1)
+ .build();
+ // The content changes after every layout.
+ callback.onLayoutFinished(info, false);
+ return null;
+ }
+ }, new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ Object[] args = invocation.getArguments();
+ PageRange[] pages = (PageRange[]) args[0];
+ ParcelFileDescriptor fd = (ParcelFileDescriptor) args[1];
+ WriteResultCallback callback = (WriteResultCallback) args[3];
+ fd.close();
+ callback.onWriteFinished(pages);
+ // Mark write was called.
+ onWriteCalled();
+ return null;
+ }
+ }, new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ // Mark finish was called.
+ onFinishCalled();
+ return null;
+ }
+ });
+
+ // Start printing.
+ print(adapter);
+
+ // Wait for write.
+ waitForWriteForAdapterCallback();
+
+ // Select the third printer.
+ selectPrinter("Third printer");
+
+ // Click the print button.
+ clickPrintButton();
+
+ // Wait for a finish.
+ waitForAdapterFinishCallbackCalled();
+
+ // Verify the expected calls.
+ InOrder inOrder = inOrder(adapter);
+
+ // Start is always called first.
+ inOrder.verify(adapter).onStart();
+
+ // Start is always followed by a layout. The PDF printer is selected if
+ // there are other printers but none of them was used.
+ PrintAttributes firstOldAttributes = new PrintAttributes.Builder().build();
+ PrintAttributes firstNewAttributes = new PrintAttributes.Builder()
+ .setMediaSize(MediaSize.NA_LETTER)
+ .setResolution(new Resolution("PDF resolution", "PDF resolution", 300, 300))
+ .setMinMargins(Margins.NO_MARGINS).setColorMode(PrintAttributes.COLOR_MODE_COLOR)
+ .build();
+ verifyLayoutCall(inOrder, adapter, firstOldAttributes, firstNewAttributes, true);
+
+ // We always ask for the first page for preview.
+ PageRange[] firstPages = new PageRange[] {new PageRange(0, 0)};
+ inOrder.verify(adapter).onWrite(eq(firstPages), any(ParcelFileDescriptor.class),
+ any(CancellationSignal.class), any(WriteResultCallback.class));
+
+ // When print is pressed we ask for a layout which is *not* for preview.
+ verifyLayoutCall(inOrder, adapter, firstNewAttributes, firstNewAttributes, false);
+
+ // When print is pressed we ask for all selected pages.
+ PageRange[] thirdPages = new PageRange[] {PageRange.ALL_PAGES};
+ inOrder.verify(adapter).onWrite(eq(thirdPages), any(ParcelFileDescriptor.class),
+ any(CancellationSignal.class), any(WriteResultCallback.class));
+
+ // Finish is always called last.
+ inOrder.verify(adapter).onFinish();
+
+ // No other call are expected.
+ verifyNoMoreInteractions(adapter);
+ }
+
+ public void testNothingChangesAllPagesWrittenFirstTime() throws Exception {
+ // Configure the print services.
+ FirstPrintService.setCallbacks(createFirstMockPrintServiceCallbacks());
+ SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
+
+ // Create a mock print adapter.
+ final PrintDocumentAdapter adapter = createMockPrintDocumentAdapter(
+ new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ LayoutResultCallback callback = (LayoutResultCallback) invocation.getArguments()[3];
+ PrintDocumentInfo info = new PrintDocumentInfo.Builder(PRINT_JOB_NAME)
+ .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT).setPageCount(3)
+ .build();
+ callback.onLayoutFinished(info, false);
+ // Mark layout was called.
+ onLayoutCalled();
+ return null;
+ }
+ }, new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ Object[] args = invocation.getArguments();
+ ParcelFileDescriptor fd = (ParcelFileDescriptor) args[1];
+ WriteResultCallback callback = (WriteResultCallback) args[3];
+ fd.close();
+ callback.onWriteFinished(new PageRange[] {PageRange.ALL_PAGES});
+ // Mark write was called.
+ onWriteCalled();
+ return null;
+ }
+ }, new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ // Mark finish was called.
+ onFinishCalled();
+ return null;
+ }
+ });
+
+ // Start printing.
+ print(adapter);
+
+ // Wait for write.
+ waitForWriteForAdapterCallback();
+
+ // Select the second printer.
+ selectPrinter("Second printer");
+
+ // Wait for layout.
+ waitForLayoutAdapterCallbackCount(2);
+
+ // Click the print button.
+ clickPrintButton();
+
+ // Wait for a finish.
+ waitForAdapterFinishCallbackCalled();
+
+ // Verify the expected calls.
+ InOrder inOrder = inOrder(adapter);
+
+ // Start is always called first.
+ inOrder.verify(adapter).onStart();
+
+ // Start is always followed by a layout. The PDF printer is selected if
+ // there are other printers but none of them was used.
+ PrintAttributes firstOldAttributes = new PrintAttributes.Builder().build();
+ PrintAttributes firstNewAttributes = new PrintAttributes.Builder()
+ .setMediaSize(MediaSize.NA_LETTER)
+ .setResolution(new Resolution("PDF resolution", "PDF resolution", 300, 300))
+ .setMinMargins(Margins.NO_MARGINS).setColorMode(PrintAttributes.COLOR_MODE_COLOR)
+ .build();
+ verifyLayoutCall(inOrder, adapter, firstOldAttributes, firstNewAttributes, true);
+
+ // We always ask for the first page for preview.
+ PageRange[] firstPages = new PageRange[] {new PageRange(0, 0)};
+ inOrder.verify(adapter).onWrite(eq(firstPages), any(ParcelFileDescriptor.class),
+ any(CancellationSignal.class), any(WriteResultCallback.class));
+
+ // We selected the second printer which does not support the media
+ // size that was selected, so a new layout happens as the size changed.
+ PrintAttributes secondOldAttributes = firstNewAttributes;
+ PrintAttributes secondNewAttributes = new PrintAttributes.Builder()
+ .setMediaSize(MediaSize.ISO_A3)
+ .setResolution(new Resolution("300x300", "300x300", 300, 300))
+ .setMinMargins(Margins.NO_MARGINS).setColorMode(PrintAttributes.COLOR_MODE_COLOR)
+ .build();
+ verifyLayoutCall(inOrder, adapter, secondOldAttributes, secondNewAttributes, true);
+
+ // In the layout callback we reported that the content didn't change,
+ // and we wrote all pages in the write call while being asked only
+ // for the first page. Hence, all pages were written and they didn't
+ // change, therefore no subsequent write call should happen.
+
+ // When print is pressed we ask for a layout which is *not* for preview.
+ verifyLayoutCall(inOrder, adapter, secondNewAttributes, secondNewAttributes, false);
+
+ // In the layout callback we reported that the content didn't change,
+ // and we wrote all pages in the write call while being asked only
+ // for the first page. Hence, all pages were written and they didn't
+ // change, therefore no subsequent write call should happen.
+
+ // Finish is always called last.
+ inOrder.verify(adapter).onFinish();
+
+ // No other call are expected.
+ verifyNoMoreInteractions(adapter);
+ }
+
+ public void testCancelLongRunningLayout() throws Exception {
+ // Configure the print services.
+ FirstPrintService.setCallbacks(createFirstMockPrintServiceCallbacks());
+ SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
+
+ // Create a mock print adapter.
+ final PrintDocumentAdapter adapter = createMockPrintDocumentAdapter(
+ new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ CancellationSignal cancellation = (CancellationSignal) invocation.getArguments()[2];
+ final LayoutResultCallback callback = (LayoutResultCallback) invocation
+ .getArguments()[3];
+ cancellation.setOnCancelListener(new OnCancelListener() {
+ @Override
+ public void onCancel() {
+ callback.onLayoutCancelled();
+ }
+ });
+ onLayoutCalled();
+ return null;
+ }
+ }, null, new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ // Mark finish was called.
+ onFinishCalled();
+ return null;
+ }
+ });
+
+ // Start printing.
+ print(adapter);
+
+ // Wait for layout.
+ waitForLayoutAdapterCallbackCount(1);
+
+ // Cancel printing.
+ getUiDevice().pressBack(); // wakes up the device.
+ getUiDevice().pressBack();
+
+ // Wait for a finish.
+ waitForAdapterFinishCallbackCalled();
+
+ // Verify the expected calls.
+ InOrder inOrder = inOrder(adapter);
+
+ // Start is always called first.
+ inOrder.verify(adapter).onStart();
+
+ // Start is always followed by a layout. The PDF printer is selected if
+ // there are other printers but none of them was used.
+ PrintAttributes firstOldAttributes = new PrintAttributes.Builder().build();
+ PrintAttributes firstNewAttributes = new PrintAttributes.Builder()
+ .setMediaSize(MediaSize.NA_LETTER)
+ .setResolution(new Resolution("PDF resolution", "PDF resolution", 300, 300))
+ .setMinMargins(Margins.NO_MARGINS).setColorMode(PrintAttributes.COLOR_MODE_COLOR)
+ .build();
+ verifyLayoutCall(inOrder, adapter, firstOldAttributes, firstNewAttributes, true);
+
+ // Finish is always called last.
+ inOrder.verify(adapter).onFinish();
+
+ // No other call are expected.
+ verifyNoMoreInteractions(adapter);
+ }
+
+ public void testCancelLongRunningWrite() throws Exception {
+ // Configure the print services.
+ FirstPrintService.setCallbacks(createFirstMockPrintServiceCallbacks());
+ SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
+
+ // Create a mock print adapter.
+ final PrintDocumentAdapter adapter = createMockPrintDocumentAdapter(
+ new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ LayoutResultCallback callback = (LayoutResultCallback) invocation.getArguments()[3];
+ PrintDocumentInfo info = new PrintDocumentInfo.Builder(PRINT_JOB_NAME)
+ .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT).setPageCount(1)
+ .build();
+ callback.onLayoutFinished(info, false);
+ return null;
+ }
+ }, new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ Object[] args = invocation.getArguments();
+ final ParcelFileDescriptor fd = (ParcelFileDescriptor) args[1];
+ final CancellationSignal cancellation = (CancellationSignal) args[2];
+ final WriteResultCallback callback = (WriteResultCallback) args[3];
+ cancellation.setOnCancelListener(new OnCancelListener() {
+ @Override
+ public void onCancel() {
+ try {
+ fd.close();
+ } catch (IOException ioe) {
+ /* ignore */
+ }
+ callback.onWriteCancelled();
+ }
+ });
+ // Mark write was called.
+ onWriteCalled();
+ return null;
+ }
+ }, new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ // Mark finish was called.
+ onFinishCalled();
+ return null;
+ }
+ });
+
+ // Start printing.
+ print(adapter);
+
+ // Wait for write.
+ waitForWriteForAdapterCallback();
+
+ // Cancel printing.
+ getUiDevice().pressBack(); // wakes up the device.
+ getUiDevice().pressBack();
+
+ // Wait for a finish.
+ waitForAdapterFinishCallbackCalled();
+
+ // Verify the expected calls.
+ InOrder inOrder = inOrder(adapter);
+
+ // Start is always called first.
+ inOrder.verify(adapter).onStart();
+
+ // Start is always followed by a layout. The PDF printer is selected if
+ // there are other printers but none of them was used.
+ PrintAttributes firstOldAttributes = new PrintAttributes.Builder().build();
+ PrintAttributes firstNewAttributes = new PrintAttributes.Builder()
+ .setMediaSize(MediaSize.NA_LETTER)
+ .setResolution(new Resolution("PDF resolution", "PDF resolution", 300, 300))
+ .setMinMargins(Margins.NO_MARGINS).setColorMode(PrintAttributes.COLOR_MODE_COLOR)
+ .build();
+ verifyLayoutCall(inOrder, adapter, firstOldAttributes, firstNewAttributes, true);
+
+ // We always ask for the first page for preview.
+ PageRange[] firstPages = new PageRange[] {new PageRange(0, 0)};
+ inOrder.verify(adapter).onWrite(eq(firstPages), any(ParcelFileDescriptor.class),
+ any(CancellationSignal.class), any(WriteResultCallback.class));
+
+ // Finish is always called last.
+ inOrder.verify(adapter).onFinish();
+
+ // No other call are expected.
+ verifyNoMoreInteractions(adapter);
+ }
+
+ public void testFailedLayout() throws Exception {
+ // Configure the print services.
+ FirstPrintService.setCallbacks(createFirstMockPrintServiceCallbacks());
+ SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
+
+ // Create a mock print adapter.
+ final PrintDocumentAdapter adapter = createMockPrintDocumentAdapter(
+ new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ LayoutResultCallback callback = (LayoutResultCallback) invocation.getArguments()[3];
+ callback.onLayoutFailed(null);
+ // Mark layout was called.
+ onLayoutCalled();
+ return null;
+ }
+ }, null, new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ // Mark finish was called.
+ onFinishCalled();
+ return null;
+ }
+ });
+
+ // Start printing.
+ print(adapter);
+
+ // Wait for layout.
+ waitForLayoutAdapterCallbackCount(1);
+
+ // Cancel printing.
+ getUiDevice().pressBack(); // wakes up the device.
+ getUiDevice().pressBack();
+
+ // Wait for a finish.
+ waitForAdapterFinishCallbackCalled();
+
+ // Verify the expected calls.
+ InOrder inOrder = inOrder(adapter);
+
+ // Start is always called first.
+ inOrder.verify(adapter).onStart();
+
+ // Start is always followed by a layout. The PDF printer is selected if
+ // there are other printers but none of them was used.
+ PrintAttributes firstOldAttributes = new PrintAttributes.Builder().build();
+ PrintAttributes firstNewAttributes = new PrintAttributes.Builder()
+ .setMediaSize(MediaSize.NA_LETTER)
+ .setResolution(new Resolution("PDF resolution", "PDF resolution", 300, 300))
+ .setMinMargins(Margins.NO_MARGINS).setColorMode(PrintAttributes.COLOR_MODE_COLOR)
+ .build();
+ verifyLayoutCall(inOrder, adapter, firstOldAttributes, firstNewAttributes, true);
+
+ // No write as layout failed.
+
+ // Finish is always called last.
+ inOrder.verify(adapter).onFinish();
+
+ // No other call are expected.
+ verifyNoMoreInteractions(adapter);
+ }
+
+ public void testFailedWrite() throws Exception {
+ // Configure the print services.
+ FirstPrintService.setCallbacks(createFirstMockPrintServiceCallbacks());
+ SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
+
+ // Create a mock print adapter.
+ final PrintDocumentAdapter adapter = createMockPrintDocumentAdapter(
+ new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ LayoutResultCallback callback = (LayoutResultCallback) invocation.getArguments()[3];
+ PrintDocumentInfo info = new PrintDocumentInfo.Builder(PRINT_JOB_NAME)
+ .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT).setPageCount(1)
+ .build();
+ callback.onLayoutFinished(info, false);
+ return null;
+ }
+ }, new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ Object[] args = invocation.getArguments();
+ ParcelFileDescriptor fd = (ParcelFileDescriptor) args[1];
+ WriteResultCallback callback = (WriteResultCallback) args[3];
+ fd.close();
+ callback.onWriteFailed(null);
+ // Mark write was called.
+ onWriteCalled();
+ return null;
+ }
+ }, new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ // Mark finish was called.
+ onFinishCalled();
+ return null;
+ }
+ });
+
+ // Start printing.
+ print(adapter);
+
+ // Wait for write.
+ waitForWriteForAdapterCallback();
+
+ // Cancel printing.
+ getUiDevice().pressBack(); // wakes up the device.
+ getUiDevice().pressBack();
+
+ // Wait for a finish.
+ waitForAdapterFinishCallbackCalled();
+
+ // Verify the expected calls.
+ InOrder inOrder = inOrder(adapter);
+
+ // Start is always called first.
+ inOrder.verify(adapter).onStart();
+
+ // Start is always followed by a layout. The PDF printer is selected if
+ // there are other printers but none of them was used.
+ PrintAttributes firstOldAttributes = new PrintAttributes.Builder().build();
+ PrintAttributes firstNewAttributes = new PrintAttributes.Builder()
+ .setMediaSize(MediaSize.NA_LETTER)
+ .setResolution(new Resolution("PDF resolution", "PDF resolution", 300, 300))
+ .setMinMargins(Margins.NO_MARGINS).setColorMode(PrintAttributes.COLOR_MODE_COLOR)
+ .build();
+ verifyLayoutCall(inOrder, adapter, firstOldAttributes, firstNewAttributes, true);
+
+ // We always ask for the first page for preview.
+ PageRange[] firstPages = new PageRange[] {new PageRange(0, 0)};
+ inOrder.verify(adapter).onWrite(eq(firstPages), any(ParcelFileDescriptor.class),
+ any(CancellationSignal.class), any(WriteResultCallback.class));
+
+ // Finish is always called last.
+ inOrder.verify(adapter).onFinish();
+
+ // No other call are expected.
+ verifyNoMoreInteractions(adapter);
+ }
+
+ public void testRequestedPagesNotWritten() throws Exception {
+ // Configure the print services.
+ FirstPrintService.setCallbacks(createFirstMockPrintServiceCallbacks());
+ SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
+
+ // Create a mock print adapter.
+ final PrintDocumentAdapter adapter = createMockPrintDocumentAdapter(
+ new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ LayoutResultCallback callback = (LayoutResultCallback) invocation.getArguments()[3];
+ PrintDocumentInfo info = new PrintDocumentInfo.Builder(PRINT_JOB_NAME)
+ .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT).setPageCount(1)
+ .build();
+ callback.onLayoutFinished(info, false);
+ return null;
+ }
+ }, new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ Object[] args = invocation.getArguments();
+ ParcelFileDescriptor fd = (ParcelFileDescriptor) args[1];
+ WriteResultCallback callback = (WriteResultCallback) args[3];
+ fd.close();
+ // Write wrong pages.
+ callback.onWriteFinished(new PageRange[] {
+ new PageRange(Integer.MAX_VALUE,Integer.MAX_VALUE)});
+ // Mark write was called.
+ onWriteCalled();
+ return null;
+ }
+ }, new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ // Mark finish was called.
+ onFinishCalled();
+ return null;
+ }
+ });
+
+ // Start printing.
+ print(adapter);
+
+ // Wait for write.
+ waitForWriteForAdapterCallback();
+
+ // Cancel printing.
+ getUiDevice().pressBack(); // wakes up the device.
+ getUiDevice().pressBack();
+
+ // Wait for a finish.
+ waitForAdapterFinishCallbackCalled();
+
+ // Verify the expected calls.
+ InOrder inOrder = inOrder(adapter);
+
+ // Start is always called first.
+ inOrder.verify(adapter).onStart();
+
+ // Start is always followed by a layout. The PDF printer is selected if
+ // there are other printers but none of them was used.
+ PrintAttributes firstOldAttributes = new PrintAttributes.Builder().build();
+ PrintAttributes firstNewAttributes = new PrintAttributes.Builder()
+ .setMediaSize(MediaSize.NA_LETTER)
+ .setResolution(new Resolution("PDF resolution", "PDF resolution", 300, 300))
+ .setMinMargins(Margins.NO_MARGINS).setColorMode(PrintAttributes.COLOR_MODE_COLOR)
+ .build();
+ verifyLayoutCall(inOrder, adapter, firstOldAttributes, firstNewAttributes, true);
+
+ // We always ask for the first page for preview.
+ PageRange[] firstPages = new PageRange[] {new PageRange(0, 0)};
+ inOrder.verify(adapter).onWrite(eq(firstPages), any(ParcelFileDescriptor.class),
+ any(CancellationSignal.class), any(WriteResultCallback.class));
+
+ // Finish is always called last.
+ inOrder.verify(adapter).onFinish();
+
+ // No other call are expected.
+ verifyNoMoreInteractions(adapter);
+ }
+
+ public void testLayoutCallbackNotCalled() throws Exception {
+ // Configure the print services.
+ FirstPrintService.setCallbacks(createFirstMockPrintServiceCallbacks());
+ SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
+
+ // Create a mock print adapter.
+ final PrintDocumentAdapter adapter = createMockPrintDocumentAdapter(
+ new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ // Break the contract and never call the callback.
+ // Mark layout called.
+ onLayoutCalled();
+ return null;
+ }
+ }, null, new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ // Mark finish was called.
+ onFinishCalled();
+ return null;
+ }
+ });
+
+ // Start printing.
+ print(adapter);
+
+ // Wait for layout.
+ waitForLayoutAdapterCallbackCount(1);
+
+ // Cancel printing.
+ getUiDevice().pressBack(); // wakes up the device.
+ getUiDevice().pressBack();
+
+ // Wait for a finish.
+ waitForAdapterFinishCallbackCalled();
+
+ // Verify the expected calls.
+ InOrder inOrder = inOrder(adapter);
+
+ // Start is always called first.
+ inOrder.verify(adapter).onStart();
+
+ // Start is always followed by a layout. The PDF printer is selected if
+ // there are other printers but none of them was used.
+ PrintAttributes firstOldAttributes = new PrintAttributes.Builder().build();
+ PrintAttributes firstNewAttributes = new PrintAttributes.Builder()
+ .setMediaSize(MediaSize.NA_LETTER)
+ .setResolution(new Resolution("PDF resolution", "PDF resolution", 300, 300))
+ .setMinMargins(Margins.NO_MARGINS).setColorMode(PrintAttributes.COLOR_MODE_COLOR)
+ .build();
+ verifyLayoutCall(inOrder, adapter, firstOldAttributes, firstNewAttributes, true);
+
+ // Finish is always called last.
+ inOrder.verify(adapter).onFinish();
+
+ // No other call are expected.
+ verifyNoMoreInteractions(adapter);
+ }
+
+ public void testWriteCallbackNotCalled() throws Exception {
+ // Configure the print services.
+ FirstPrintService.setCallbacks(createFirstMockPrintServiceCallbacks());
+ SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
+
+ // Create a mock print adapter.
+ final PrintDocumentAdapter adapter = createMockPrintDocumentAdapter(
+ new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ LayoutResultCallback callback = (LayoutResultCallback) invocation.getArguments()[3];
+ PrintDocumentInfo info = new PrintDocumentInfo.Builder(PRINT_JOB_NAME)
+ .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT).setPageCount(1)
+ .build();
+ callback.onLayoutFinished(info, false);
+ return null;
+ }
+ }, new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ Object[] args = invocation.getArguments();
+ ParcelFileDescriptor fd = (ParcelFileDescriptor) args[1];
+ fd.close();
+ // Break the contract and never call the callback.
+ // Mark write was called.
+ onWriteCalled();
+ return null;
+ }
+ }, new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ // Mark finish was called.
+ onFinishCalled();
+ return null;
+ }
+ });
+
+ // Start printing.
+ print(adapter);
+
+ // Wait for write.
+ waitForWriteForAdapterCallback();
+
+ // Cancel printing.
+ getUiDevice().pressBack(); // wakes up the device.
+ getUiDevice().pressBack();
+
+ // Wait for a finish.
+ waitForAdapterFinishCallbackCalled();
+
+ // Verify the expected calls.
+ InOrder inOrder = inOrder(adapter);
+
+ // Start is always called first.
+ inOrder.verify(adapter).onStart();
+
+ // Start is always followed by a layout. The PDF printer is selected if
+ // there are other printers but none of them was used.
+ PrintAttributes firstOldAttributes = new PrintAttributes.Builder().build();
+ PrintAttributes firstNewAttributes = new PrintAttributes.Builder()
+ .setMediaSize(MediaSize.NA_LETTER)
+ .setResolution(new Resolution("PDF resolution", "PDF resolution", 300, 300))
+ .setMinMargins(Margins.NO_MARGINS).setColorMode(PrintAttributes.COLOR_MODE_COLOR)
+ .build();
+ verifyLayoutCall(inOrder, adapter, firstOldAttributes, firstNewAttributes, true);
+
+ // We always ask for the first page for preview.
+ PageRange[] firstPages = new PageRange[] {new PageRange(0, 0)};
+ inOrder.verify(adapter).onWrite(eq(firstPages), any(ParcelFileDescriptor.class),
+ any(CancellationSignal.class), any(WriteResultCallback.class));
+
+ // Finish is always called last.
+ inOrder.verify(adapter).onFinish();
+
+ // No other call are expected.
+ verifyNoMoreInteractions(adapter);
+ }
+
+ private PrintServiceCallbacks createFirstMockPrintServiceCallbacks() {
+ final PrinterDiscoverySessionCallbacks callbacks =
+ createMockPrinterDiscoverySessionCallbacks(new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) {
+ PrinterDiscoverySessionCallbacks mock = (PrinterDiscoverySessionCallbacks)
+ invocation.getMock();
+
+ StubbablePrinterDiscoverySession session = mock.getSession();
+ PrintService service = session.getService();
+
+ if (session.getPrinters().isEmpty()) {
+ List<PrinterInfo> printers = new ArrayList<PrinterInfo>();
+
+ // Add the first printer.
+ PrinterId firstPrinterId = service.generatePrinterId("first_printer");
+ PrinterCapabilitiesInfo firstCapabilities =
+ new PrinterCapabilitiesInfo.Builder(firstPrinterId)
+ .setMinMargins(new Margins(200, 200, 200, 200))
+ .addMediaSize(MediaSize.ISO_A4, true)
+ .addMediaSize(MediaSize.ISO_A5, false)
+ .addResolution(new Resolution("300x300", "300x300", 300, 300), true)
+ .setColorModes(PrintAttributes.COLOR_MODE_COLOR,
+ PrintAttributes.COLOR_MODE_COLOR)
+ .build();
+ PrinterInfo firstPrinter = new PrinterInfo.Builder(firstPrinterId,
+ "First printer", PrinterInfo.STATUS_IDLE)
+ .setCapabilities(firstCapabilities)
+ .build();
+ printers.add(firstPrinter);
+
+ // Add the second printer.
+ PrinterId secondPrinterId = service.generatePrinterId("second_printer");
+ PrinterCapabilitiesInfo secondCapabilities =
+ new PrinterCapabilitiesInfo.Builder(secondPrinterId)
+ .addMediaSize(MediaSize.ISO_A3, true)
+ .addMediaSize(MediaSize.ISO_A4, false)
+ .addResolution(new Resolution("200x200", "200x200", 200, 200), true)
+ .addResolution(new Resolution("300x300", "300x300", 300, 300), false)
+ .setColorModes(PrintAttributes.COLOR_MODE_COLOR
+ | PrintAttributes.COLOR_MODE_MONOCHROME,
+ PrintAttributes.COLOR_MODE_MONOCHROME)
+ .build();
+ PrinterInfo secondPrinter = new PrinterInfo.Builder(secondPrinterId,
+ "Second printer", PrinterInfo.STATUS_IDLE)
+ .setCapabilities(secondCapabilities)
+ .build();
+ printers.add(secondPrinter);
+
+ // Add the third printer.
+ PrinterId thirdPrinterId = service.generatePrinterId("third_printer");
+ PrinterCapabilitiesInfo thirdCapabilities =
+ new PrinterCapabilitiesInfo.Builder(thirdPrinterId)
+ .addMediaSize(MediaSize.NA_LETTER, true)
+ .addResolution(new Resolution("300x300", "300x300", 300, 300), true)
+ .setColorModes(PrintAttributes.COLOR_MODE_COLOR,
+ PrintAttributes.COLOR_MODE_COLOR)
+ .build();
+ PrinterInfo thirdPrinter = new PrinterInfo.Builder(thirdPrinterId,
+ "Third printer", PrinterInfo.STATUS_IDLE)
+ .setCapabilities(thirdCapabilities)
+ .build();
+ printers.add(thirdPrinter);
+
+ session.addPrinters(printers);
+ }
+ return null;
+ }
+ }, null, null, null, null, null);
+ return createMockPrintServiceCallbacks(new Answer<PrinterDiscoverySessionCallbacks>() {
+ @Override
+ public PrinterDiscoverySessionCallbacks answer(InvocationOnMock invocation) {
+ return callbacks;
+ }
+ }, new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ PrintJob printJob = (PrintJob) invocation.getArguments()[0];
+ printJob.complete();
+ return null;
+ }
+ }, null);
+ }
+
+ private PrintServiceCallbacks createSecondMockPrintServiceCallbacks() {
+ return createMockPrintServiceCallbacks(null, null, null);
+ }
+}
diff --git a/tests/tests/print/src/android/print/cts/PrinterDiscoverySessionLifecycleTest.java b/tests/tests/print/src/android/print/cts/PrinterDiscoverySessionLifecycleTest.java
new file mode 100644
index 0000000..b9ea280
--- /dev/null
+++ b/tests/tests/print/src/android/print/cts/PrinterDiscoverySessionLifecycleTest.java
@@ -0,0 +1,406 @@
+/*
+ * 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 android.print.cts;
+
+import static org.mockito.Mockito.inOrder;
+
+import android.os.ParcelFileDescriptor;
+import android.print.PageRange;
+import android.print.PrintAttributes;
+import android.print.PrintAttributes.Margins;
+import android.print.PrintAttributes.MediaSize;
+import android.print.PrintAttributes.Resolution;
+import android.print.PrintDocumentAdapter;
+import android.print.PrintDocumentAdapter.LayoutResultCallback;
+import android.print.PrintDocumentAdapter.WriteResultCallback;
+import android.print.PrintDocumentInfo;
+import android.print.PrinterCapabilitiesInfo;
+import android.print.PrinterId;
+import android.print.PrinterInfo;
+import android.print.cts.services.FirstPrintService;
+import android.print.cts.services.PrintServiceCallbacks;
+import android.print.cts.services.PrinterDiscoverySessionCallbacks;
+import android.print.cts.services.SecondPrintService;
+import android.print.cts.services.StubbablePrinterDiscoverySession;
+import android.printservice.PrintJob;
+import android.printservice.PrinterDiscoverySession;
+
+import org.mockito.InOrder;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * This test verifies that the system respects the {@link PrinterDiscoverySession}
+ * contract is respected.
+ */
+public class PrinterDiscoverySessionLifecycleTest extends BasePrintTest {
+ private static final String FIRST_PRINTER_NAME = "First printer";
+ private static final String SECOND_PRINTER_NAME = "Second printer";
+
+ private static final String FIRST_PRINTER_LOCAL_ID= "first_printer";
+ private static final String SECOND_PRINTER_LOCAL_ID = "second_printer";
+
+ public void testNormalLifecycle() throws Exception {
+ // Create the session callbacks that we will be checking.
+ final PrinterDiscoverySessionCallbacks firstSessionCallbacks =
+ createFirstMockPrinterDiscoverySessionCallbacks();
+
+ // Create the service callbacks for the first print service.
+ PrintServiceCallbacks firstServiceCallbacks = createMockPrintServiceCallbacks(
+ new Answer<PrinterDiscoverySessionCallbacks>() {
+ @Override
+ public PrinterDiscoverySessionCallbacks answer(InvocationOnMock invocation) {
+ return firstSessionCallbacks;
+ }
+ },
+ new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) {
+ PrintJob printJob = (PrintJob) invocation.getArguments()[0];
+ // We pretend the job is handled immediately.
+ printJob.complete();
+ return null;
+ }
+ }, null);
+
+ // Configure the print services.
+ FirstPrintService.setCallbacks(firstServiceCallbacks);
+ SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
+
+ // Create a print adapter that respects the print contract.
+ PrintDocumentAdapter adapter = createMockPrintDocumentAdapter();
+
+ // Start printing.
+ print(adapter);
+
+ // Wait for write of the first page.
+ waitForWriteForAdapterCallback();
+
+ // Select the first printer.
+ selectPrinter(FIRST_PRINTER_NAME);
+
+ // Wait for layout as the printer has different capabilities.
+ waitForLayoutAdapterCallbackCount(2);
+
+ // Select the second printer (same capabilities as the other
+ // one so no layout should happen).
+ selectPrinter(SECOND_PRINTER_NAME);
+
+ // While the printer discovery session is still alive store the
+ // ids of printers as we want to make some assertions about them
+ // but only the print service can create printer ids which means
+ // that we need to get the created ones.
+ PrinterId firstPrinterId = getAddedPrinterIdForLocalId(firstSessionCallbacks,
+ FIRST_PRINTER_LOCAL_ID);
+ PrinterId secondPrinterId = getAddedPrinterIdForLocalId(firstSessionCallbacks,
+ SECOND_PRINTER_LOCAL_ID);
+ assertNotNull("Coundn't find printer:" + FIRST_PRINTER_LOCAL_ID, firstPrinterId);
+ assertNotNull("Coundn't find printer:" + SECOND_PRINTER_LOCAL_ID, secondPrinterId);
+
+ // Click the print button.
+ clickPrintButton();
+
+ // Wait for all print jobs to be handled after which the session destroyed.
+ waitForPrinterDiscoverySessionDestroyCallbackCalled();
+
+ // Verify the expected calls.
+ InOrder inOrder = inOrder(firstSessionCallbacks);
+
+ // We start discovery as the print dialog was up.
+ List<PrinterId> emptyPrinterIdList = Collections.emptyList();
+ inOrder.verify(firstSessionCallbacks).onStartPrinterDiscovery(
+ emptyPrinterIdList);
+
+ // We selected the first printer and now it should be tracked.
+ inOrder.verify(firstSessionCallbacks).onStartPrinterStateTracking(
+ firstPrinterId);
+
+ // We selected the second printer so the first should not be tracked.
+ inOrder.verify(firstSessionCallbacks).onStopPrinterStateTracking(
+ firstPrinterId);
+
+ // We selected the second printer and now it should be tracked.
+ inOrder.verify(firstSessionCallbacks).onStartPrinterStateTracking(
+ secondPrinterId);
+
+ // The print dialog went away so we first stop the printer tracking...
+ inOrder.verify(firstSessionCallbacks).onStopPrinterStateTracking(
+ secondPrinterId);
+
+ // ... next we stop printer discovery...
+ inOrder.verify(firstSessionCallbacks).onStopPrinterDiscovery();
+
+ // ... last the session is destroyed.
+ inOrder.verify(firstSessionCallbacks).onDestroy();
+ }
+
+ public void testStartPrinterDiscoveryWithHistoricalPrinters() throws Exception {
+ // Create the session callbacks that we will be checking.
+ final PrinterDiscoverySessionCallbacks firstSessionCallbacks =
+ createFirstMockPrinterDiscoverySessionCallbacks();
+
+ // Create the service callbacks for the first print service.
+ PrintServiceCallbacks firstServiceCallbacks = createMockPrintServiceCallbacks(
+ new Answer<PrinterDiscoverySessionCallbacks>() {
+ @Override
+ public PrinterDiscoverySessionCallbacks answer(InvocationOnMock invocation) {
+ return firstSessionCallbacks;
+ }
+ },
+ new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) {
+ PrintJob printJob = (PrintJob) invocation.getArguments()[0];
+ // We pretend the job is handled immediately.
+ printJob.complete();
+ return null;
+ }
+ }, null);
+
+ // Configure the print services.
+ FirstPrintService.setCallbacks(firstServiceCallbacks);
+ SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
+
+ // Create a print adapter that respects the print contract.
+ PrintDocumentAdapter adapter = createMockPrintDocumentAdapter();
+
+ // Start printing.
+ print(adapter);
+
+ // Wait for write of the first page.
+ waitForWriteForAdapterCallback();
+
+ // Select the first printer.
+ selectPrinter(FIRST_PRINTER_NAME);
+
+ // Wait for a layout to finish - first layout was for the
+ // PDF printer, second for the first printer in preview mode.
+ waitForLayoutAdapterCallbackCount(2);
+
+ // While the printer discovery session is still alive store the
+ // ids of printer as we want to make some assertions about it
+ // but only the print service can create printer ids which means
+ // that we need to get the created one.
+ PrinterId firstPrinterId = getAddedPrinterIdForLocalId(
+ firstSessionCallbacks, FIRST_PRINTER_LOCAL_ID);
+
+ // Click the print button.
+ clickPrintButton();
+
+ // Wait for the print to complete.
+ waitForAdapterFinishCallbackCalled();
+
+ // Now print again as we want to confirm that the start
+ // printer discovery passes in the priority list.
+ print(adapter);
+
+ // Wait for a layout to finish - first layout was for the
+ // PDF printer, second for the first printer in preview mode,
+ // the third for the first printer in non-preview mode, and
+ // now a fourth for the PDF printer as we are printing again.
+ waitForLayoutAdapterCallbackCount(4);
+
+ // Cancel the printing.
+ getUiDevice().pressBack(); // wakes up the device.
+ getUiDevice().pressBack();
+
+ // Wait for all print jobs to be handled after which the session destroyed.
+ waitForPrinterDiscoverySessionDestroyCallbackCalled();
+
+ // Verify the expected calls.
+ InOrder inOrder = inOrder(firstSessionCallbacks);
+
+ // We start discovery as the print dialog was up.
+ List<PrinterId> priorityList = new ArrayList<PrinterId>();
+ priorityList.add(firstPrinterId);
+ inOrder.verify(firstSessionCallbacks).onStartPrinterDiscovery(
+ priorityList);
+
+ // We selected the first printer and now it should be tracked.
+ inOrder.verify(firstSessionCallbacks).onStartPrinterStateTracking(
+ firstPrinterId);
+
+ // We selected the second printer so the first should not be tracked.
+ inOrder.verify(firstSessionCallbacks).onStopPrinterStateTracking(
+ firstPrinterId);
+
+ // ...next we stop printer discovery...
+ inOrder.verify(firstSessionCallbacks).onStopPrinterDiscovery();
+
+ // ...last the session is destroyed.
+ inOrder.verify(firstSessionCallbacks).onDestroy();
+ }
+
+ private PrinterId getAddedPrinterIdForLocalId(
+ final PrinterDiscoverySessionCallbacks sessionCallbacks, String printerLocalId) {
+ final List<PrinterInfo> reportedPrinters = new ArrayList<PrinterInfo>();
+ getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ // Grab the printer ids as only the service can create such.
+ StubbablePrinterDiscoverySession session = sessionCallbacks.getSession();
+ reportedPrinters.addAll(session.getPrinters());
+ }
+ });
+
+ final int reportedPrinterCount = reportedPrinters.size();
+ for (int i = 0; i < reportedPrinterCount; i++) {
+ PrinterInfo reportedPrinter = reportedPrinters.get(i);
+ String localId = reportedPrinter.getId().getLocalId();
+ if (printerLocalId.equals(localId)) {
+ return reportedPrinter.getId();
+ }
+ }
+
+ return null;
+ }
+
+ private PrintServiceCallbacks createSecondMockPrintServiceCallbacks() {
+ return createMockPrintServiceCallbacks(null, null, null);
+ }
+
+ private PrinterDiscoverySessionCallbacks createFirstMockPrinterDiscoverySessionCallbacks() {
+ return createMockPrinterDiscoverySessionCallbacks(new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) {
+ // Get the session.
+ StubbablePrinterDiscoverySession session = ((PrinterDiscoverySessionCallbacks)
+ invocation.getMock()).getSession();
+
+ if (session.getPrinters().isEmpty()) {
+ List<PrinterInfo> printers = new ArrayList<PrinterInfo>();
+
+ // Add the first printer.
+ PrinterId firstPrinterId = session.getService().generatePrinterId(
+ FIRST_PRINTER_LOCAL_ID);
+ PrinterInfo firstPrinter = new PrinterInfo.Builder(firstPrinterId,
+ FIRST_PRINTER_NAME, PrinterInfo.STATUS_IDLE)
+ .build();
+ printers.add(firstPrinter);
+
+ // Add the first printer.
+ PrinterId secondPrinterId = session.getService().generatePrinterId(
+ SECOND_PRINTER_LOCAL_ID);
+ PrinterInfo secondPrinter = new PrinterInfo.Builder(secondPrinterId,
+ SECOND_PRINTER_NAME, PrinterInfo.STATUS_IDLE)
+ .build();
+ printers.add(secondPrinter);
+
+ session.addPrinters(printers);
+ }
+ return null;
+ }
+ }, null, null, new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ // Get the session.
+ StubbablePrinterDiscoverySession session = ((PrinterDiscoverySessionCallbacks)
+ invocation.getMock()).getSession();
+
+ PrinterId trackedPrinterId = (PrinterId) invocation.getArguments()[0];
+ List<PrinterInfo> reportedPrinters = session.getPrinters();
+
+ // We should be tracking a printer that we added.
+ PrinterInfo trackedPrinter = null;
+ final int reportedPrinterCount = reportedPrinters.size();
+ for (int i = 0; i < reportedPrinterCount; i++) {
+ PrinterInfo reportedPrinter = reportedPrinters.get(i);
+ if (reportedPrinter.getId().equals(trackedPrinterId)) {
+ trackedPrinter = reportedPrinter;
+ break;
+ }
+ }
+ assertNotNull("Can track only added printers", trackedPrinter);
+
+ // If the printer does not have capabilities reported add them.
+ if (trackedPrinter.getCapabilities() == null) {
+
+ // Add the capabilities to emulate lazy discovery.
+ // Same for each printer is fine for what we test.
+ PrinterCapabilitiesInfo capabilities =
+ new PrinterCapabilitiesInfo.Builder(trackedPrinterId)
+ .setMinMargins(new Margins(200, 200, 200, 200))
+ .addMediaSize(MediaSize.ISO_A4, true)
+ .addMediaSize(MediaSize.ISO_A5, false)
+ .addResolution(new Resolution("300x300", "300x300", 300, 300), true)
+ .setColorModes(PrintAttributes.COLOR_MODE_COLOR,
+ PrintAttributes.COLOR_MODE_COLOR)
+ .build();
+ PrinterInfo updatedPrinter = new PrinterInfo.Builder(trackedPrinter)
+ .setCapabilities(capabilities)
+ .build();
+
+ // Update the printer.
+ List<PrinterInfo> printers = new ArrayList<PrinterInfo>();
+ printers.add(updatedPrinter);
+ session.addPrinters(printers);
+ }
+
+ return null;
+ }
+ }, null, new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ // Take a note onDestroy was called.
+ onPrinterDiscoverySessionDestroyCalled();
+ return null;
+ }
+ });
+ }
+
+ public PrintDocumentAdapter createMockPrintDocumentAdapter() {
+ return createMockPrintDocumentAdapter(
+ new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ LayoutResultCallback callback = (LayoutResultCallback) invocation.getArguments()[3];
+ PrintDocumentInfo info = new PrintDocumentInfo.Builder(PRINT_JOB_NAME)
+ .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
+ .setPageCount(3)
+ .build();
+ callback.onLayoutFinished(info, false);
+ // Mark layout was called.
+ onLayoutCalled();
+ return null;
+ }
+ }, new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ Object[] args = invocation.getArguments();
+ PageRange[] pages = (PageRange[]) args[0];
+ ParcelFileDescriptor fd = (ParcelFileDescriptor) args[1];
+ WriteResultCallback callback = (WriteResultCallback) args[3];
+ fd.close();
+ callback.onWriteFinished(pages);
+ // Mark write was called.
+ onWriteCalled();
+ return null;
+ }
+ }, new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ // Mark finish was called.
+ onFinishCalled();
+ return null;
+ }
+ });
+ }
+}
diff --git a/tests/tests/print/src/android/print/cts/services/AddPrintersActivity.java b/tests/tests/print/src/android/print/cts/services/AddPrintersActivity.java
new file mode 100644
index 0000000..c72d6f9
--- /dev/null
+++ b/tests/tests/print/src/android/print/cts/services/AddPrintersActivity.java
@@ -0,0 +1,28 @@
+/*
+ * 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 android.print.cts.services;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class AddPrintersActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+}
diff --git a/tests/tests/print/src/android/print/cts/services/CustomPrintOptionsActivity.java b/tests/tests/print/src/android/print/cts/services/CustomPrintOptionsActivity.java
new file mode 100644
index 0000000..9d26d81
--- /dev/null
+++ b/tests/tests/print/src/android/print/cts/services/CustomPrintOptionsActivity.java
@@ -0,0 +1,28 @@
+/*
+ * 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 android.print.cts.services;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class CustomPrintOptionsActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+}
diff --git a/tests/tests/print/src/android/print/cts/services/FirstPrintService.java b/tests/tests/print/src/android/print/cts/services/FirstPrintService.java
new file mode 100644
index 0000000..a234de4
--- /dev/null
+++ b/tests/tests/print/src/android/print/cts/services/FirstPrintService.java
@@ -0,0 +1,40 @@
+/*
+ * 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 android.print.cts.services;
+
+public class FirstPrintService extends StubbablePrintService {
+
+ private static final Object sLock = new Object();
+
+ private static PrintServiceCallbacks sCallbacks;
+
+ public static void setCallbacks(PrintServiceCallbacks callbacks) {
+ synchronized (sLock) {
+ sCallbacks = callbacks;
+ }
+ }
+
+ @Override
+ protected PrintServiceCallbacks getCallbacks() {
+ synchronized (sLock) {
+ if (sCallbacks != null) {
+ sCallbacks.setService(this);
+ }
+ return sCallbacks;
+ }
+ }
+}
diff --git a/tests/tests/print/src/android/print/cts/services/PrintServiceCallbacks.java b/tests/tests/print/src/android/print/cts/services/PrintServiceCallbacks.java
new file mode 100644
index 0000000..ff0245f
--- /dev/null
+++ b/tests/tests/print/src/android/print/cts/services/PrintServiceCallbacks.java
@@ -0,0 +1,39 @@
+/*
+ * 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 android.print.cts.services;
+
+import android.printservice.PrintJob;
+import android.printservice.PrintService;
+
+public abstract class PrintServiceCallbacks {
+
+ private PrintService mService;
+
+ public PrintService getService() {
+ return mService;
+ }
+
+ public void setService(PrintService service) {
+ mService = service;
+ }
+
+ public abstract PrinterDiscoverySessionCallbacks onCreatePrinterDiscoverySessionCallbacks();
+
+ public abstract void onRequestCancelPrintJob(PrintJob printJob);
+
+ public abstract void onPrintJobQueued(PrintJob printJob);
+}
diff --git a/tests/tests/print/src/android/print/cts/services/PrinterDiscoverySessionCallbacks.java b/tests/tests/print/src/android/print/cts/services/PrinterDiscoverySessionCallbacks.java
new file mode 100644
index 0000000..6b2c3a9
--- /dev/null
+++ b/tests/tests/print/src/android/print/cts/services/PrinterDiscoverySessionCallbacks.java
@@ -0,0 +1,46 @@
+/*
+ * 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 android.print.cts.services;
+
+import android.print.PrinterId;
+
+import java.util.List;
+
+public abstract class PrinterDiscoverySessionCallbacks {
+
+ private StubbablePrinterDiscoverySession mSession;
+
+ public void setSession(StubbablePrinterDiscoverySession session) {
+ mSession = session;
+ }
+
+ public StubbablePrinterDiscoverySession getSession() {
+ return mSession;
+ }
+
+ public abstract void onStartPrinterDiscovery(List<PrinterId> priorityList);
+
+ public abstract void onStopPrinterDiscovery();
+
+ public abstract void onValidatePrinters(List<PrinterId> printerIds);
+
+ public abstract void onStartPrinterStateTracking(PrinterId printerId);
+
+ public abstract void onStopPrinterStateTracking(PrinterId printerId);
+
+ public abstract void onDestroy();
+}
diff --git a/tests/tests/print/src/android/print/cts/services/SecondPrintService.java b/tests/tests/print/src/android/print/cts/services/SecondPrintService.java
new file mode 100644
index 0000000..1029a8e
--- /dev/null
+++ b/tests/tests/print/src/android/print/cts/services/SecondPrintService.java
@@ -0,0 +1,40 @@
+/*
+ * 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 android.print.cts.services;
+
+public class SecondPrintService extends StubbablePrintService {
+
+ private static final Object sLock = new Object();
+
+ private static PrintServiceCallbacks sCallbacks;
+
+ public static void setCallbacks(PrintServiceCallbacks callbacks) {
+ synchronized (sLock) {
+ sCallbacks = callbacks;
+ }
+ }
+
+ @Override
+ protected PrintServiceCallbacks getCallbacks() {
+ synchronized (sLock) {
+ if (sCallbacks != null) {
+ sCallbacks.setService(this);
+ }
+ return sCallbacks;
+ }
+ }
+}
diff --git a/tests/tests/print/src/android/print/cts/services/SettingsActivity.java b/tests/tests/print/src/android/print/cts/services/SettingsActivity.java
new file mode 100644
index 0000000..eb23574
--- /dev/null
+++ b/tests/tests/print/src/android/print/cts/services/SettingsActivity.java
@@ -0,0 +1,28 @@
+/*
+ * 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 android.print.cts.services;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class SettingsActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+}
diff --git a/tests/tests/print/src/android/print/cts/services/StubbablePrintService.java b/tests/tests/print/src/android/print/cts/services/StubbablePrintService.java
new file mode 100644
index 0000000..2686b41
--- /dev/null
+++ b/tests/tests/print/src/android/print/cts/services/StubbablePrintService.java
@@ -0,0 +1,52 @@
+/*
+ * 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 android.print.cts.services;
+
+import android.printservice.PrintJob;
+import android.printservice.PrintService;
+import android.printservice.PrinterDiscoverySession;
+
+public abstract class StubbablePrintService extends PrintService {
+
+ @Override
+ public PrinterDiscoverySession onCreatePrinterDiscoverySession() {
+ PrintServiceCallbacks callbacks = getCallbacks();
+ if (callbacks != null) {
+ return new StubbablePrinterDiscoverySession(this,
+ getCallbacks().onCreatePrinterDiscoverySessionCallbacks());
+ }
+ return null;
+ }
+
+ @Override
+ public void onRequestCancelPrintJob(PrintJob printJob) {
+ PrintServiceCallbacks callbacks = getCallbacks();
+ if (callbacks != null) {
+ callbacks.onRequestCancelPrintJob(printJob);
+ }
+ }
+
+ @Override
+ public void onPrintJobQueued(PrintJob printJob) {
+ PrintServiceCallbacks callbacks = getCallbacks();
+ if (callbacks != null) {
+ callbacks.onPrintJobQueued(printJob);
+ }
+ }
+
+ protected abstract PrintServiceCallbacks getCallbacks();
+}
diff --git a/tests/tests/print/src/android/print/cts/services/StubbablePrinterDiscoverySession.java b/tests/tests/print/src/android/print/cts/services/StubbablePrinterDiscoverySession.java
new file mode 100644
index 0000000..fdc2713
--- /dev/null
+++ b/tests/tests/print/src/android/print/cts/services/StubbablePrinterDiscoverySession.java
@@ -0,0 +1,83 @@
+/*
+ * 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 android.print.cts.services;
+
+import android.print.PrinterId;
+import android.printservice.PrintService;
+import android.printservice.PrinterDiscoverySession;
+
+import java.util.List;
+
+public class StubbablePrinterDiscoverySession extends PrinterDiscoverySession {
+ private final PrintService mService;
+ private final PrinterDiscoverySessionCallbacks mCallbacks;
+
+ public StubbablePrinterDiscoverySession(PrintService service,
+ PrinterDiscoverySessionCallbacks callbacks) {
+ mService = service;
+ mCallbacks = callbacks;
+ if (mCallbacks != null) {
+ mCallbacks.setSession(this);
+ }
+ }
+
+ public PrintService getService() {
+ return mService;
+ }
+
+ @Override
+ public void onStartPrinterDiscovery(List<PrinterId> priorityList) {
+ if (mCallbacks != null) {
+ mCallbacks.onStartPrinterDiscovery(priorityList);
+ }
+ }
+
+ @Override
+ public void onStopPrinterDiscovery() {
+ if (mCallbacks != null) {
+ mCallbacks.onStopPrinterDiscovery();
+ }
+ }
+
+ @Override
+ public void onValidatePrinters(List<PrinterId> printerIds) {
+ if (mCallbacks != null) {
+ mCallbacks.onValidatePrinters(printerIds);
+ }
+ }
+
+ @Override
+ public void onStartPrinterStateTracking(PrinterId printerId) {
+ if (mCallbacks != null) {
+ mCallbacks.onStartPrinterStateTracking(printerId);
+ }
+ }
+
+ @Override
+ public void onStopPrinterStateTracking(PrinterId printerId) {
+ if (mCallbacks != null) {
+ mCallbacks.onStopPrinterStateTracking(printerId);
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ if (mCallbacks != null) {
+ mCallbacks.onDestroy();
+ }
+ }
+}
diff --git a/tests/tests/provider/AndroidManifest.xml b/tests/tests/provider/AndroidManifest.xml
index 94dc408..7bf44b7 100644
--- a/tests/tests/provider/AndroidManifest.xml
+++ b/tests/tests/provider/AndroidManifest.xml
@@ -40,9 +40,12 @@
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.stub"
- android:label="CTS tests of android.provider"/>
+ android:label="CTS tests of android.provider">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
<instrumentation android:name="android.provider.cts.CalendarTest$CalendarEmmaTestRunner"
android:targetPackage="com.android.cts.stub"
diff --git a/tests/tests/provider/src/android/provider/cts/BrowserTest.java b/tests/tests/provider/src/android/provider/cts/BrowserTest.java
index 9654e43..ffeb2a1 100644
--- a/tests/tests/provider/src/android/provider/cts/BrowserTest.java
+++ b/tests/tests/provider/src/android/provider/cts/BrowserTest.java
@@ -28,9 +28,6 @@
import android.provider.Browser;
import android.provider.Browser.BookmarkColumns;
import android.provider.Browser.SearchColumns;
-import android.provider.BrowserContract;
-import android.provider.BrowserContract.Bookmarks;
-import android.provider.BrowserContract.History;
import android.test.ActivityInstrumentationTestCase2;
import java.util.ArrayList;
@@ -73,39 +70,30 @@
ContentResolver.setMasterSyncAutomatically(false);
// backup the current contents in database
- Cursor cursor = mProvider.query(Bookmarks.CONTENT_URI, null, null, null, null, null);
- if (cursor.moveToFirst()) {
+ Cursor cursor = mProvider.query(Browser.BOOKMARKS_URI, null, null, null, null, null);
+ while (cursor.moveToNext()) {
String[] colNames = cursor.getColumnNames();
- while (!cursor.isAfterLast()) {
- ContentValues value = new ContentValues();
+ ContentValues value = new ContentValues();
- for (int i = 0; i < colNames.length; i++) {
- if (Bookmarks.PARENT_SOURCE_ID.equals(colNames[i])
- || Bookmarks.INSERT_AFTER_SOURCE_ID.equals(colNames[i])
- || Bookmarks.TYPE.equals(colNames[i])) {
- // These aren't actual columns, so skip them in the backup
- continue;
- }
- switch (cursor.getType(i)) {
- case Cursor.FIELD_TYPE_BLOB:
- value.put(colNames[i], cursor.getBlob(i));
- break;
- case Cursor.FIELD_TYPE_FLOAT:
- value.put(colNames[i], cursor.getFloat(i));
- break;
- case Cursor.FIELD_TYPE_INTEGER:
- value.put(colNames[i], cursor.getLong(i));
- break;
- case Cursor.FIELD_TYPE_STRING:
- value.put(colNames[i], cursor.getString(i));
- break;
- }
+ for (int i = 0; i < colNames.length; i++) {
+ switch (cursor.getType(i)) {
+ case Cursor.FIELD_TYPE_BLOB:
+ value.put(colNames[i], cursor.getBlob(i));
+ break;
+ case Cursor.FIELD_TYPE_FLOAT:
+ value.put(colNames[i], cursor.getFloat(i));
+ break;
+ case Cursor.FIELD_TYPE_INTEGER:
+ value.put(colNames[i], cursor.getLong(i));
+ break;
+ case Cursor.FIELD_TYPE_STRING:
+ value.put(colNames[i], cursor.getString(i));
+ break;
}
- mBookmarksBackup.add(value);
-
- cursor.moveToNext();
- };
+ }
+ mBookmarksBackup.add(value);
}
+
cursor.close();
cursor = mProvider.query(Browser.SEARCHES_URI, null, null, null, null, null);
@@ -123,12 +111,8 @@
}
cursor.close();
- Uri uri = Bookmarks.CONTENT_URI.buildUpon()
- .appendQueryParameter(BrowserContract.CALLER_IS_SYNCADAPTER, "true")
- .build();
- mProvider.delete(uri, null, null);
+ mProvider.delete(Browser.BOOKMARKS_URI, null, null);
mProvider.delete(Browser.SEARCHES_URI, null, null);
- mProvider.delete(History.CONTENT_URI, null, null);
mActivity = getActivity();
}
@@ -136,17 +120,13 @@
@Override
protected void tearDown() throws Exception {
try {
-
// clear all new contents added in test cases.
- Uri uri = Bookmarks.CONTENT_URI.buildUpon()
- .appendQueryParameter(BrowserContract.CALLER_IS_SYNCADAPTER, "true")
- .build();
- mProvider.delete(uri, null, null);
+ mProvider.delete(Browser.BOOKMARKS_URI, null, null);
mProvider.delete(Browser.SEARCHES_URI, null, null);
// recover the old backup contents
for (ContentValues value : mBookmarksBackup) {
- mProvider.insert(uri, value);
+ mProvider.insert(Browser.BOOKMARKS_URI, value);
}
for (ContentValues value : mSearchesBackup) {
diff --git a/tests/tests/provider/src/android/provider/cts/CalendarTest.java b/tests/tests/provider/src/android/provider/cts/CalendarTest.java
index bd8e06d..a8f547b 100644
--- a/tests/tests/provider/src/android/provider/cts/CalendarTest.java
+++ b/tests/tests/provider/src/android/provider/cts/CalendarTest.java
@@ -41,7 +41,6 @@
import android.provider.CalendarContract.Instances;
import android.provider.CalendarContract.Reminders;
import android.provider.CalendarContract.SyncState;
-import android.test.InstrumentationCtsTestRunner;
import android.test.InstrumentationTestCase;
import android.test.suitebuilder.annotation.MediumTest;
import android.text.TextUtils;
@@ -3706,7 +3705,8 @@
/**
* Special version of the test runner that does some remote Emma coverage housekeeping.
*/
- public static class CalendarEmmaTestRunner extends InstrumentationCtsTestRunner {
+ // TODO: find if this is still used and if so convert to AndroidJUnitRunner framework
+ public static class CalendarEmmaTestRunner extends android.test.InstrumentationTestRunner {
private static final Uri EMMA_CONTENT_URI =
Uri.parse("content://" + CalendarContract.AUTHORITY + "/emma");
private ContentResolver mContentResolver;
diff --git a/tests/tests/provider/src/android/provider/cts/ContactsContract_DataTest.java b/tests/tests/provider/src/android/provider/cts/ContactsContract_DataTest.java
index 7cfb183..bbfa259 100644
--- a/tests/tests/provider/src/android/provider/cts/ContactsContract_DataTest.java
+++ b/tests/tests/provider/src/android/provider/cts/ContactsContract_DataTest.java
@@ -26,10 +26,12 @@
import android.net.Uri;
import android.os.SystemClock;
import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.Callable;
import android.provider.ContactsContract.CommonDataKinds.Contactables;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Organization;
import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.SipAddress;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
@@ -49,6 +51,60 @@
private ContentResolver mResolver;
private ContactsContract_TestDataBuilder mBuilder;
+ private static ContentValues[] sContentValues = new ContentValues[7];
+ static {
+ ContentValues cv1 = new ContentValues();
+ cv1.put(Contacts.DISPLAY_NAME, "Hot Tamale");
+ cv1.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
+ cv1.put(Email.DATA, "tamale@acme.com");
+ cv1.put(Email.TYPE, Email.TYPE_HOME);
+ sContentValues[0] = cv1;
+
+ ContentValues cv2 = new ContentValues();
+ cv2.put(Contacts.DISPLAY_NAME, "Hot Tamale");
+ cv2.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
+ cv2.put(Phone.DATA, "510-123-5769");
+ cv2.put(Phone.TYPE, Phone.TYPE_HOME);
+ sContentValues[1] = cv2;
+
+ ContentValues cv3 = new ContentValues();
+ cv3.put(Contacts.DISPLAY_NAME, "Hot Tamale");
+ cv3.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
+ cv3.put(Email.DATA, "hot@google.com");
+ cv3.put(Email.TYPE, Email.TYPE_WORK);
+ sContentValues[2] = cv3;
+
+ ContentValues cv4 = new ContentValues();
+ cv4.put(Contacts.DISPLAY_NAME, "Cold Tamago");
+ cv4.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
+ cv4.put(Email.DATA, "eggs@farmers.org");
+ cv4.put(Email.TYPE, Email.TYPE_HOME);
+ sContentValues[3] = cv4;
+
+ ContentValues cv5 = new ContentValues();
+ cv5.put(Contacts.DISPLAY_NAME, "John Doe");
+ cv5.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
+ cv5.put(Email.DATA, "doeassociates@deer.com");
+ cv5.put(Email.TYPE, Email.TYPE_WORK);
+ sContentValues[4] = cv5;
+
+ ContentValues cv6 = new ContentValues();
+ cv6.put(Contacts.DISPLAY_NAME, "John Doe");
+ cv6.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
+ cv6.put(Phone.DATA, "518-354-1111");
+ cv6.put(Phone.TYPE, Phone.TYPE_HOME);
+ sContentValues[5] = cv6;
+
+ ContentValues cv7 = new ContentValues();
+ cv7.put(Contacts.DISPLAY_NAME, "Cold Tamago");
+ cv7.put(Data.MIMETYPE, SipAddress.CONTENT_ITEM_TYPE);
+ cv7.put(SipAddress.DATA, "mysip@sipaddress.com");
+ cv7.put(SipAddress.TYPE, SipAddress.TYPE_HOME);
+ sContentValues[6] = cv7;
+ }
+
+ private TestRawContact[] mRawContacts = new TestRawContact[3];
+
@Override
protected void setUp() throws Exception {
super.setUp();
@@ -182,6 +238,37 @@
assertCursorStoredValuesWithRawContactsFilter(filterUri, ids, new ContentValues[0]);
}
+ /**
+ * Verifies that Callable.CONTENT_URI returns only data items that can be called (i.e.
+ * phone numbers and sip addresses)
+ */
+ public void testCallableUri_returnsCorrectDataRows() throws Exception {
+ long[] ids = setupContactablesTestData();
+ Uri uri = Callable.CONTENT_URI;
+ assertCursorStoredValuesWithRawContactsFilter(uri, ids, sContentValues[1],
+ sContentValues[5], sContentValues[6]);
+ }
+
+ public void testCallableFilterByNameOrOrganization_returnsCorrectDataRows() throws Exception {
+ long[] ids = setupContactablesTestData();
+ Uri uri = Uri.withAppendedPath(Callable.CONTENT_FILTER_URI, "doe");
+ // Only callables belonging to John Doe (name) and Cold Tamago (organization) are returned.
+ assertCursorStoredValuesWithRawContactsFilter(uri, ids, sContentValues[5],
+ sContentValues[6]);
+ }
+
+ public void testCallableFilterByNumber_returnsCorrectDataRows() throws Exception {
+ long[] ids = setupContactablesTestData();
+ Uri uri = Uri.withAppendedPath(Callable.CONTENT_FILTER_URI, "510");
+ assertCursorStoredValuesWithRawContactsFilter(uri, ids, sContentValues[1]);
+ }
+
+ public void testCallableFilterBySipAddress_returnsCorrectDataRows() throws Exception {
+ long[] ids = setupContactablesTestData();
+ Uri uri = Uri.withAppendedPath(Callable.CONTENT_FILTER_URI, "mysip");
+ assertCursorStoredValuesWithRawContactsFilter(uri, ids, sContentValues[6]);
+ }
+
public void testDataInsert_updatesContactLastUpdatedTimestamp() {
DatabaseAsserts.ContactIdPair ids = DatabaseAsserts.assertAndCreateContact(mResolver);
long baseTime = ContactUtil.queryContactLastUpdatedTimestamp(mResolver, ids.mContactId);
@@ -213,6 +300,70 @@
RawContactUtil.delete(mResolver, ids.mRawContactId, true);
}
+ /**
+ * Tests that specifying the {@link android.provider.ContactsContract#REMOVE_DUPLICATE_ENTRIES}
+ * boolean parameter correctly results in deduped phone numbers.
+ */
+ public void testPhoneQuery_removeDuplicateEntries() throws Exception{
+ long[] ids = setupContactablesTestData();
+
+ // Insert duplicate data entry for raw contact 3. (existing phone number 518-354-1111)
+ mRawContacts[2].newDataRow(Phone.CONTENT_ITEM_TYPE)
+ .with(Phone.DATA, "518-354-1111")
+ .with(Phone.TYPE, Phone.TYPE_HOME)
+ .insert();
+
+ ContentValues dupe = new ContentValues();
+ dupe.put(Contacts.DISPLAY_NAME, "John Doe");
+ dupe.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
+ dupe.put(Phone.DATA, "518-354-1111");
+ dupe.put(Phone.TYPE, Phone.TYPE_HOME);
+
+ // Query for all phone numbers in the contacts database (without deduping).
+ // The phone number above should be listed twice, in its duplicated forms.
+ assertCursorStoredValuesWithRawContactsFilter(Phone.CONTENT_URI, ids, sContentValues[1],
+ sContentValues[5], dupe);
+
+ // Now query for all phone numbers in the contacts database but request deduping.
+ // The phone number should now be listed only once.
+ Uri uri = Phone.CONTENT_URI.buildUpon().
+ appendQueryParameter(ContactsContract.REMOVE_DUPLICATE_ENTRIES, "true").build();
+ assertCursorStoredValuesWithRawContactsFilter(uri, ids, sContentValues[1],
+ sContentValues[5]);
+ }
+
+ /**
+ * Tests that specifying the {@link android.provider.ContactsContract#REMOVE_DUPLICATE_ENTRIES}
+ * boolean parameter correctly results in deduped email addresses.
+ */
+ public void testEmailQuery_removeDuplicateEntries() throws Exception{
+ long[] ids = setupContactablesTestData();
+
+ // Insert duplicate data entry for raw contact 3. (existing email doeassociates@deer.com)
+ mRawContacts[2].newDataRow(Email.CONTENT_ITEM_TYPE)
+ .with(Email.DATA, "doeassociates@deer.com")
+ .with(Email.TYPE, Email.TYPE_WORK)
+ .insert();
+
+ ContentValues dupe = new ContentValues();
+ dupe.put(Contacts.DISPLAY_NAME, "John Doe");
+ dupe.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
+ dupe.put(Email.DATA, "doeassociates@deer.com");
+ dupe.put(Email.TYPE, Email.TYPE_WORK);
+
+ // Query for all email addresses in the contacts database (without deduping).
+ // The email address above should be listed twice, in its duplicated forms.
+ assertCursorStoredValuesWithRawContactsFilter(Email.CONTENT_URI, ids, sContentValues[0],
+ sContentValues[2], sContentValues[3], sContentValues[4], dupe);
+
+ // Now query for all email addresses in the contacts database but request deduping.
+ // The email address should now be listed only once.
+ Uri uri = Email.CONTENT_URI.buildUpon().
+ appendQueryParameter(ContactsContract.REMOVE_DUPLICATE_ENTRIES, "true").build();
+ assertCursorStoredValuesWithRawContactsFilter(uri, ids, sContentValues[0],
+ sContentValues[2], sContentValues[3], sContentValues[4]);
+ }
+
public void testDataUpdate_updatesContactLastUpdatedTimestamp() {
DatabaseAsserts.ContactIdPair ids = DatabaseAsserts.assertAndCreateContact(mResolver);
long dataId = createData(ids.mRawContactId);
@@ -277,6 +428,7 @@
.with(Phone.DATA, "510-123-5769")
.with(Email.TYPE, Phone.TYPE_HOME)
.insert();
+ mRawContacts[0] = rawContact;
TestRawContact rawContact2 = mBuilder.newRawContact()
.with(RawContacts.ACCOUNT_TYPE, "test_account")
@@ -289,6 +441,14 @@
.with(Email.DATA, "eggs@farmers.org")
.with(Email.TYPE, Email.TYPE_HOME)
.insert();
+ rawContact2.newDataRow(SipAddress.CONTENT_ITEM_TYPE)
+ .with(SipAddress.DATA, "mysip@sipaddress.com")
+ .with(SipAddress.TYPE, SipAddress.TYPE_HOME)
+ .insert();
+ rawContact2.newDataRow(Organization.CONTENT_ITEM_TYPE)
+ .with(Organization.COMPANY, "Doe Corp")
+ .insert();
+ mRawContacts[1] = rawContact2;
TestRawContact rawContact3 = mBuilder.newRawContact()
.with(RawContacts.ACCOUNT_TYPE, "test_account")
@@ -308,55 +468,12 @@
rawContact3.newDataRow(Organization.CONTENT_ITEM_TYPE)
.with(Organization.DATA, "Doe Industries")
.insert();
+ mRawContacts[2] = rawContact3;
return new long[] {rawContact.getId(), rawContact2.getId(), rawContact3.getId()};
}
// Provides functionality to set up content values for the Contactables tests
private static class ContactablesTestHelper {
- private static ContentValues[] sContentValues = new ContentValues[6];
- static {
- ContentValues cv1 = new ContentValues();
- cv1.put(Contacts.DISPLAY_NAME, "Hot Tamale");
- cv1.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
- cv1.put(Email.DATA, "tamale@acme.com");
- cv1.put(Email.TYPE, Email.TYPE_HOME);
- sContentValues[0] = cv1;
-
- ContentValues cv2 = new ContentValues();
- cv2.put(Contacts.DISPLAY_NAME, "Hot Tamale");
- cv2.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
- cv2.put(Phone.DATA, "510-123-5769");
- cv2.put(Phone.TYPE, Phone.TYPE_HOME);
- sContentValues[1] = cv2;
-
- ContentValues cv3 = new ContentValues();
- cv3.put(Contacts.DISPLAY_NAME, "Hot Tamale");
- cv3.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
- cv3.put(Email.DATA, "hot@google.com");
- cv3.put(Email.TYPE, Email.TYPE_WORK);
- sContentValues[2] = cv3;
-
- ContentValues cv4 = new ContentValues();
- cv4.put(Contacts.DISPLAY_NAME, "Cold Tamago");
- cv4.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
- cv4.put(Email.DATA, "eggs@farmers.org");
- cv4.put(Email.TYPE, Email.TYPE_HOME);
- sContentValues[3] = cv4;
-
- ContentValues cv5 = new ContentValues();
- cv5.put(Contacts.DISPLAY_NAME, "John Doe");
- cv5.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
- cv5.put(Email.DATA, "doeassociates@deer.com");
- cv5.put(Email.TYPE, Email.TYPE_WORK);
- sContentValues[4] = cv5;
-
- ContentValues cv6 = new ContentValues();
- cv6.put(Contacts.DISPLAY_NAME, "John Doe");
- cv6.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
- cv6.put(Phone.DATA, "518-354-1111");
- cv6.put(Phone.TYPE, Phone.TYPE_HOME);
- sContentValues[5] = cv6;
- }
/**
* @return An arraylist of contentValues that correspond to the provided raw contacts
diff --git a/tests/tests/provider/src/android/provider/cts/ContactsContract_PinnedPositionsTest.java b/tests/tests/provider/src/android/provider/cts/ContactsContract_PinnedPositionsTest.java
new file mode 100644
index 0000000..2a8e806
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/ContactsContract_PinnedPositionsTest.java
@@ -0,0 +1,489 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.provider.cts;
+
+import static android.provider.cts.contacts.ContactUtil.newContentValues;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.AggregationExceptions;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.PinnedPositions;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.cts.contacts.CommonDatabaseUtils;
+import android.provider.cts.contacts.ContactUtil;
+import android.provider.cts.contacts.DatabaseAsserts;
+import android.provider.cts.contacts.RawContactUtil;
+import android.test.AndroidTestCase;
+
+/**
+ * CTS tests for {@link android.provider.ContactsContract.PinnedPositions} API
+ */
+public class ContactsContract_PinnedPositionsTest extends AndroidTestCase {
+ private ContentResolver mResolver;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mResolver = getContext().getContentResolver();
+ }
+
+ /**
+ * Tests that the ContactsProvider automatically stars/unstars a pinned/unpinned contact if
+ * {@link PinnedPositions#STAR_WHEN_PINNING} boolean parameter is set to true, and that the
+ * values are correctly propogated to the contact's constituent raw contacts.
+ */
+ public void testPinnedPositionsUpdateForceStar() {
+ final DatabaseAsserts.ContactIdPair i1 = DatabaseAsserts.assertAndCreateContact(mResolver);
+ final DatabaseAsserts.ContactIdPair i2 = DatabaseAsserts.assertAndCreateContact(mResolver);
+ final DatabaseAsserts.ContactIdPair i3 = DatabaseAsserts.assertAndCreateContact(mResolver);
+ final DatabaseAsserts.ContactIdPair i4 = DatabaseAsserts.assertAndCreateContact(mResolver);
+
+ final int unpinned = PinnedPositions.UNPINNED;
+
+ assertValuesForContact(i1.mContactId,
+ newContentValues(Contacts.PINNED, unpinned, Contacts.STARRED, 0));
+ assertValuesForContact(i2.mContactId,
+ newContentValues(Contacts.PINNED, unpinned, Contacts.STARRED, 0));
+ assertValuesForContact(i3.mContactId,
+ newContentValues(Contacts.PINNED, unpinned, Contacts.STARRED, 0));
+ assertValuesForContact(i4.mContactId,
+ newContentValues(Contacts.PINNED, unpinned, Contacts.STARRED, 0));
+
+ assertValuesForRawContact(i1.mRawContactId, newContentValues(RawContacts.PINNED, unpinned));
+ assertValuesForRawContact(i2.mRawContactId, newContentValues(RawContacts.PINNED, unpinned));
+ assertValuesForRawContact(i3.mRawContactId, newContentValues(RawContacts.PINNED, unpinned));
+ assertValuesForRawContact(i4.mRawContactId, newContentValues(RawContacts.PINNED, unpinned));
+
+ final ContentValues values =
+ newContentValues(i1.mContactId, 1, i3.mContactId, 3, i4.mContactId, 2);
+ mResolver.update(ContactsContract.PinnedPositions.UPDATE_URI.buildUpon()
+ .appendQueryParameter(PinnedPositions.STAR_WHEN_PINNING, "true").build(),
+ values, null, null);
+
+ // Pinning a contact should automatically star it if we specified the boolean parameter.
+ assertValuesForContact(i1.mContactId,
+ newContentValues(Contacts.PINNED, 1, Contacts.STARRED, 1));
+ assertValuesForContact(i2.mContactId,
+ newContentValues(Contacts.PINNED, unpinned, Contacts.STARRED, 0));
+ assertValuesForContact(i3.mContactId,
+ newContentValues(Contacts.PINNED, 3, Contacts.STARRED, 1));
+ assertValuesForContact(i4.mContactId,
+ newContentValues(Contacts.PINNED, 2, Contacts.STARRED, 1));
+
+ // Make sure the values are propagated to raw contacts.
+ assertValuesForRawContact(i1.mRawContactId, newContentValues(RawContacts.PINNED, 1));
+ assertValuesForRawContact(i2.mRawContactId, newContentValues(RawContacts.PINNED, unpinned));
+ assertValuesForRawContact(i3.mRawContactId, newContentValues(RawContacts.PINNED, 3));
+ assertValuesForRawContact(i4.mRawContactId, newContentValues(RawContacts.PINNED, 2));
+
+ final ContentValues unpin = newContentValues(i3.mContactId, unpinned);
+ mResolver.update(ContactsContract.PinnedPositions.UPDATE_URI.buildUpon()
+ .appendQueryParameter(PinnedPositions.STAR_WHEN_PINNING, "true").build(),
+ unpin, null, null);
+
+ // Unpinning a contact should automatically unstar it.
+ assertValuesForContact(i1.mContactId,
+ newContentValues(Contacts.PINNED, 1, Contacts.STARRED, 1));
+ assertValuesForContact(i2.mContactId,
+ newContentValues(Contacts.PINNED, unpinned, Contacts.STARRED, 0));
+ assertValuesForContact(i3.mContactId,
+ newContentValues(Contacts.PINNED, unpinned, Contacts.STARRED, 0));
+ assertValuesForContact(i4.mContactId,
+ newContentValues(Contacts.PINNED, 2, Contacts.STARRED, 1));
+
+ assertValuesForRawContact(i1.mRawContactId,
+ newContentValues(RawContacts.PINNED, 1, RawContacts.STARRED, 1));
+ assertValuesForRawContact(i2.mRawContactId,
+ newContentValues(RawContacts.PINNED, unpinned, RawContacts.STARRED, 0));
+ assertValuesForRawContact(i3.mRawContactId,
+ newContentValues(RawContacts.PINNED, unpinned, RawContacts.STARRED, 0));
+ assertValuesForRawContact(i4.mRawContactId,
+ newContentValues(RawContacts.PINNED, 2, RawContacts.STARRED, 1));
+
+ ContactUtil.delete(mResolver, i1.mContactId);
+ ContactUtil.delete(mResolver, i2.mContactId);
+ ContactUtil.delete(mResolver, i3.mContactId);
+ ContactUtil.delete(mResolver, i4.mContactId);
+ }
+
+ /**
+ * Tests that the ContactsProvider does not automatically star/unstar a pinned/unpinned contact
+ * if {@link PinnedPositions#STAR_WHEN_PINNING} boolean parameter not set to true or not
+ * provided.
+ */
+ public void testPinnedPositionsUpdateDontForceStar() {
+ final DatabaseAsserts.ContactIdPair i1 = DatabaseAsserts.assertAndCreateContact(mResolver);
+ final DatabaseAsserts.ContactIdPair i2 = DatabaseAsserts.assertAndCreateContact(mResolver);
+ final DatabaseAsserts.ContactIdPair i3 = DatabaseAsserts.assertAndCreateContact(mResolver);
+ final DatabaseAsserts.ContactIdPair i4 = DatabaseAsserts.assertAndCreateContact(mResolver);
+
+ final int unpinned = PinnedPositions.UNPINNED;
+
+ final ContentValues values =
+ newContentValues(i1.mContactId, 1, i3.mContactId, 3, i4.mContactId, 2);
+ mResolver.update(ContactsContract.PinnedPositions.UPDATE_URI, values, null, null);
+
+ // Pinning a contact should not automatically star it since we didn't specify the
+ // STAR_WHEN_PINNING boolean parameter.
+ assertValuesForContact(i1.mContactId,
+ newContentValues(Contacts.PINNED, 1, Contacts.STARRED, 0));
+ assertValuesForContact(i2.mContactId,
+ newContentValues(Contacts.PINNED, unpinned, Contacts.STARRED, 0));
+ assertValuesForContact(i3.mContactId,
+ newContentValues(Contacts.PINNED, 3, Contacts.STARRED, 0));
+ assertValuesForContact(i4.mContactId,
+ newContentValues(Contacts.PINNED, 2, Contacts.STARRED, 0));
+
+ // Make sure the values are propagated to raw contacts.
+ assertValuesForRawContact(i1.mRawContactId,
+ newContentValues(RawContacts.PINNED, 1, RawContacts.STARRED, 0));
+ assertValuesForRawContact(i2.mRawContactId,
+ newContentValues(RawContacts.PINNED, unpinned, RawContacts.STARRED, 0));
+ assertValuesForRawContact(i3.mRawContactId,
+ newContentValues(RawContacts.PINNED, 3, RawContacts.STARRED, 0));
+ assertValuesForRawContact(i4.mRawContactId,
+ newContentValues(RawContacts.PINNED, 2, RawContacts.STARRED, 0));
+
+ // Manually star contact 3.
+ assertEquals(1,
+ updateItemForContact(Contacts.CONTENT_URI, i3.mContactId, Contacts.STARRED, "1"));
+
+ // Check the third contact and raw contact is starred.
+ assertValuesForContact(i1.mContactId,
+ newContentValues(Contacts.PINNED, 1, Contacts.STARRED, 0));
+ assertValuesForContact(i2.mContactId,
+ newContentValues(Contacts.PINNED, unpinned, Contacts.STARRED, 0));
+ assertValuesForContact(i3.mContactId,
+ newContentValues(Contacts.PINNED, 3, Contacts.STARRED, 1));
+ assertValuesForContact(i4.mContactId,
+ newContentValues(Contacts.PINNED, 2, Contacts.STARRED, 0));
+
+ assertValuesForRawContact(i1.mRawContactId,
+ newContentValues(RawContacts.PINNED, 1, RawContacts.STARRED, 0));
+ assertValuesForRawContact(i2.mRawContactId,
+ newContentValues(RawContacts.PINNED, unpinned, RawContacts.STARRED, 0));
+ assertValuesForRawContact(i3.mRawContactId,
+ newContentValues(RawContacts.PINNED, 3, RawContacts.STARRED, 1));
+ assertValuesForRawContact(i4.mRawContactId,
+ newContentValues(RawContacts.PINNED, 2, RawContacts.STARRED, 0));
+
+ final ContentValues unpin = newContentValues(i3.mContactId, unpinned);
+
+ mResolver.update(ContactsContract.PinnedPositions.UPDATE_URI, unpin, null, null);
+
+ // Unpinning a contact should not automatically unstar it.
+ assertValuesForContact(i1.mContactId,
+ newContentValues(Contacts.PINNED, 1, Contacts.STARRED, 0));
+ assertValuesForContact(i2.mContactId,
+ newContentValues(Contacts.PINNED, unpinned, Contacts.STARRED, 0));
+ assertValuesForContact(i3.mContactId,
+ newContentValues(Contacts.PINNED, unpinned, Contacts.STARRED, 1));
+ assertValuesForContact(i4.mContactId,
+ newContentValues(Contacts.PINNED, 2, Contacts.STARRED, 0));
+
+ assertValuesForRawContact(i1.mRawContactId,
+ newContentValues(RawContacts.PINNED, 1, RawContacts.STARRED, 0));
+ assertValuesForRawContact(i2.mRawContactId,
+ newContentValues(RawContacts.PINNED, unpinned, RawContacts.STARRED, 0));
+ assertValuesForRawContact(i3.mRawContactId,
+ newContentValues(RawContacts.PINNED, unpinned, RawContacts.STARRED, 1));
+ assertValuesForRawContact(i4.mRawContactId,
+ newContentValues(RawContacts.PINNED, 2, RawContacts.STARRED, 0));
+
+ ContactUtil.delete(mResolver, i1.mContactId);
+ ContactUtil.delete(mResolver, i2.mContactId);
+ ContactUtil.delete(mResolver, i3.mContactId);
+ ContactUtil.delete(mResolver, i4.mContactId);
+ }
+
+ /**
+ * Tests that updating the ContactsProvider with illegal pinned position correctly
+ * throws an IllegalArgumentException.
+ */
+ public void testPinnedPositionsUpdateIllegalValues() {
+ final DatabaseAsserts.ContactIdPair i1 = DatabaseAsserts.assertAndCreateContact(mResolver);
+ final DatabaseAsserts.ContactIdPair i2 = DatabaseAsserts.assertAndCreateContact(mResolver);
+ final DatabaseAsserts.ContactIdPair i3 = DatabaseAsserts.assertAndCreateContact(mResolver);
+ final DatabaseAsserts.ContactIdPair i4 = DatabaseAsserts.assertAndCreateContact(mResolver);
+
+ final int unpinned = PinnedPositions.UNPINNED;
+
+ assertValuesForContact(i1.mContactId, newContentValues(Contacts.PINNED, unpinned));
+ assertValuesForContact(i2.mContactId, newContentValues(Contacts.PINNED, unpinned));
+ assertValuesForContact(i3.mContactId, newContentValues(Contacts.PINNED, unpinned));
+ assertValuesForContact(i4.mContactId, newContentValues(Contacts.PINNED, unpinned));
+
+ // Unsupported string should throw an IllegalArgumentException.
+ final ContentValues values = newContentValues(i1.mContactId, 1, i3.mContactId, 3,
+ i4.mContactId, "undemotemeplease!");
+ try {
+ mResolver.update(ContactsContract.PinnedPositions.UPDATE_URI, values, null, null);
+ fail("Pinned position must be an integer.");
+ } catch (IllegalArgumentException expected) {
+ }
+
+ // Unsupported pinned position (e.g. float value) should throw an IllegalArgumentException.
+ final ContentValues values2 = newContentValues(i1.mContactId, "1.1");
+ try {
+ mResolver.update(ContactsContract.PinnedPositions.UPDATE_URI, values2, null, null);
+ fail("Pinned position must be an integer");
+ } catch (IllegalArgumentException expected) {
+ }
+
+ // Nothing should have been changed.
+
+ assertValuesForContact(i1.mContactId, newContentValues(Contacts.PINNED, unpinned));
+ assertValuesForContact(i2.mContactId, newContentValues(Contacts.PINNED, unpinned));
+ assertValuesForContact(i3.mContactId, newContentValues(Contacts.PINNED, unpinned));
+ assertValuesForContact(i4.mContactId, newContentValues(Contacts.PINNED, unpinned));
+
+ assertValuesForRawContact(i1.mRawContactId, newContentValues(RawContacts.PINNED, unpinned));
+ assertValuesForRawContact(i2.mRawContactId, newContentValues(RawContacts.PINNED, unpinned));
+ assertValuesForRawContact(i3.mRawContactId, newContentValues(RawContacts.PINNED, unpinned));
+ assertValuesForRawContact(i4.mRawContactId, newContentValues(RawContacts.PINNED, unpinned));
+
+ ContactUtil.delete(mResolver, i1.mContactId);
+ ContactUtil.delete(mResolver, i2.mContactId);
+ ContactUtil.delete(mResolver, i3.mContactId);
+ ContactUtil.delete(mResolver, i4.mContactId);
+ }
+
+ /**
+ * Tests that pinned positions are correctly handled after the ContactsProvider aggregates
+ * and splits raw contacts.
+ */
+ public void testPinnedPositionsAfterJoinAndSplit() {
+ final DatabaseAsserts.ContactIdPair i1 = DatabaseAsserts.assertAndCreateContact(mResolver);
+ final DatabaseAsserts.ContactIdPair i2 = DatabaseAsserts.assertAndCreateContact(mResolver);
+ final DatabaseAsserts.ContactIdPair i3 = DatabaseAsserts.assertAndCreateContact(mResolver);
+ final DatabaseAsserts.ContactIdPair i4 = DatabaseAsserts.assertAndCreateContact(mResolver);
+ final DatabaseAsserts.ContactIdPair i5 = DatabaseAsserts.assertAndCreateContact(mResolver);
+ final DatabaseAsserts.ContactIdPair i6 = DatabaseAsserts.assertAndCreateContact(mResolver);
+
+ final ContentValues values = newContentValues(i1.mContactId, 1, i2.mContactId, 2,
+ i3.mContactId, 3, i5.mContactId, 5, i6.mContactId, 6);
+ mResolver.update(ContactsContract.PinnedPositions.UPDATE_URI.buildUpon()
+ .appendQueryParameter(PinnedPositions.STAR_WHEN_PINNING, "true").build(),
+ values, null, null);
+
+ // Aggregate raw contact 1 and 4 together.
+ ContactUtil.setAggregationException(mResolver, AggregationExceptions.TYPE_KEEP_TOGETHER,
+ i1.mRawContactId, i4.mRawContactId);
+
+ // If only one contact is pinned, the resulting contact should inherit the pinned position.
+ assertValuesForContact(i1.mContactId, newContentValues(Contacts.PINNED, 1));
+ assertValuesForContact(i2.mContactId, newContentValues(Contacts.PINNED, 2));
+ assertValuesForContact(i3.mContactId, newContentValues(Contacts.PINNED, 3));
+ assertValuesForContact(i5.mContactId, newContentValues(Contacts.PINNED, 5));
+ assertValuesForContact(i6.mContactId, newContentValues(Contacts.PINNED, 6));
+
+ assertValuesForRawContact(i1.mRawContactId,
+ newContentValues(RawContacts.PINNED, 1, RawContacts.STARRED, 1));
+ assertValuesForRawContact(i2.mRawContactId,
+ newContentValues(RawContacts.PINNED, 2, RawContacts.STARRED, 1));
+ assertValuesForRawContact(i3.mRawContactId,
+ newContentValues(RawContacts.PINNED, 3, RawContacts.STARRED, 1));
+ assertValuesForRawContact(i4.mRawContactId,
+ newContentValues(RawContacts.PINNED, PinnedPositions.UNPINNED, RawContacts.STARRED,
+ 0));
+ assertValuesForRawContact(i5.mRawContactId,
+ newContentValues(RawContacts.PINNED, 5, RawContacts.STARRED, 1));
+ assertValuesForRawContact(i6.mRawContactId,
+ newContentValues(RawContacts.PINNED, 6, RawContacts.STARRED, 1));
+
+ // Aggregate raw contact 2 and 3 together.
+ ContactUtil.setAggregationException(mResolver, AggregationExceptions.TYPE_KEEP_TOGETHER,
+ i2.mRawContactId, i3.mRawContactId);
+
+ // If both raw contacts are pinned, the resulting contact should inherit the lower
+ // pinned position.
+ assertValuesForContact(i1.mContactId, newContentValues(Contacts.PINNED, 1));
+ assertValuesForContact(i2.mContactId, newContentValues(Contacts.PINNED, 2));
+ assertValuesForContact(i5.mContactId, newContentValues(Contacts.PINNED, 5));
+ assertValuesForContact(i6.mContactId, newContentValues(Contacts.PINNED, 6));
+
+ assertValuesForRawContact(i1.mRawContactId, newContentValues(RawContacts.PINNED, 1));
+ assertValuesForRawContact(i2.mRawContactId, newContentValues(RawContacts.PINNED, 2));
+ assertValuesForRawContact(i3.mRawContactId, newContentValues(RawContacts.PINNED, 3));
+ assertValuesForRawContact(i4.mRawContactId,
+ newContentValues(RawContacts.PINNED, PinnedPositions.UNPINNED));
+ assertValuesForRawContact(i5.mRawContactId, newContentValues(RawContacts.PINNED, 5));
+ assertValuesForRawContact(i6.mRawContactId, newContentValues(RawContacts.PINNED, 6));
+
+ // Split the aggregated raw contacts.
+ ContactUtil.setAggregationException(mResolver, AggregationExceptions.TYPE_KEEP_SEPARATE,
+ i1.mRawContactId, i4.mRawContactId);
+
+ // Raw contacts should be unpinned after being split, but still starred.
+ assertValuesForRawContact(i1.mRawContactId,
+ newContentValues(RawContacts.PINNED, PinnedPositions.UNPINNED, RawContacts.STARRED,
+ 1));
+ assertValuesForRawContact(i2.mRawContactId,
+ newContentValues(RawContacts.PINNED, 2, RawContacts.STARRED, 1));
+ assertValuesForRawContact(i3.mRawContactId,
+ newContentValues(RawContacts.PINNED, 3, RawContacts.STARRED, 1));
+ assertValuesForRawContact(i4.mRawContactId,
+ newContentValues(RawContacts.PINNED, PinnedPositions.UNPINNED, RawContacts.STARRED,
+ 0));
+ assertValuesForRawContact(i5.mRawContactId,
+ newContentValues(RawContacts.PINNED, 5, RawContacts.STARRED, 1));
+ assertValuesForRawContact(i6.mRawContactId,
+ newContentValues(RawContacts.PINNED, 6, RawContacts.STARRED, 1));
+
+ // Now demote contact 5.
+ final ContentValues cv = newContentValues(i5.mContactId, PinnedPositions.DEMOTED);
+ mResolver.update(ContactsContract.PinnedPositions.UPDATE_URI.buildUpon().build(),
+ cv, null, null);
+
+ // Get new contact Ids for contacts composing of raw contacts 1 and 4 because they have
+ // changed.
+ final long cId1 = RawContactUtil.queryContactIdByRawContactId(mResolver, i1.mRawContactId);
+ final long cId4 = RawContactUtil.queryContactIdByRawContactId(mResolver, i4.mRawContactId);
+
+ assertValuesForContact(cId1, newContentValues(Contacts.PINNED, PinnedPositions.UNPINNED));
+ assertValuesForContact(i2.mContactId, newContentValues(Contacts.PINNED, 2));
+ assertValuesForContact(cId4, newContentValues(Contacts.PINNED, PinnedPositions.UNPINNED));
+ assertValuesForContact(i5.mContactId,
+ newContentValues(Contacts.PINNED, PinnedPositions.DEMOTED));
+ assertValuesForContact(i6.mContactId, newContentValues(Contacts.PINNED, 6));
+
+ // Aggregate contacts 5 and 6 together.
+ ContactUtil.setAggregationException(mResolver, AggregationExceptions.TYPE_KEEP_TOGETHER,
+ i5.mRawContactId, i6.mRawContactId);
+
+ // The resulting contact should have a pinned value of 6.
+ assertValuesForContact(cId1, newContentValues(Contacts.PINNED, PinnedPositions.UNPINNED));
+ assertValuesForContact(i2.mContactId, newContentValues(Contacts.PINNED, 2));
+ assertValuesForContact(cId4, newContentValues(Contacts.PINNED, PinnedPositions.UNPINNED));
+ assertValuesForContact(i5.mContactId, newContentValues(Contacts.PINNED, 6));
+
+ ContactUtil.delete(mResolver, cId1);
+ ContactUtil.delete(mResolver, i2.mContactId);
+ ContactUtil.delete(mResolver, cId4);
+ ContactUtil.delete(mResolver, i5.mContactId);
+ }
+
+ /**
+ * Tests that pinned positions are correctly handled for contacts that have been demoted
+ * or undemoted.
+ */
+ public void testPinnedPositionsAfterDemoteAndUndemote() {
+ final DatabaseAsserts.ContactIdPair i1 = DatabaseAsserts.assertAndCreateContact(mResolver);
+ final DatabaseAsserts.ContactIdPair i2 = DatabaseAsserts.assertAndCreateContact(mResolver);
+
+ final ContentValues values =
+ newContentValues(i1.mContactId, 0, i2.mContactId, PinnedPositions.DEMOTED);
+
+ // Pin contact 1 and demote contact 2.
+ mResolver.update(ContactsContract.PinnedPositions.UPDATE_URI.buildUpon().
+ appendQueryParameter(PinnedPositions.STAR_WHEN_PINNING, "true").
+ build(), values, null, null);
+
+ assertValuesForContact(i1.mContactId,
+ newContentValues(Contacts.PINNED, 0, Contacts.STARRED, 1));
+ assertValuesForContact(i2.mContactId,
+ newContentValues(Contacts.PINNED, PinnedPositions.DEMOTED, Contacts.STARRED, 0));
+
+ assertValuesForRawContact(i1.mRawContactId,
+ newContentValues(RawContacts.PINNED, 0, RawContacts.STARRED, 1));
+ assertValuesForRawContact(i2.mRawContactId,
+ newContentValues(RawContacts.PINNED, PinnedPositions.DEMOTED, RawContacts.STARRED, 0));
+
+ // Now undemote both contacts.
+ final ContentValues values2 = newContentValues(i1.mContactId, PinnedPositions.UNDEMOTE,
+ i2.mContactId, PinnedPositions.UNDEMOTE);
+ mResolver.update(ContactsContract.PinnedPositions.UPDATE_URI.buildUpon().
+ build(), values2, null, null);
+
+ // Contact 1 remains pinned at 0, while contact 2 becomes unpinned.
+ assertValuesForContact(i1.mContactId,
+ newContentValues(Contacts.PINNED, 0, Contacts.STARRED, 1));
+ assertValuesForContact(i2.mContactId,
+ newContentValues(Contacts.PINNED, PinnedPositions.UNPINNED, Contacts.STARRED, 0));
+
+ assertValuesForRawContact(i1.mRawContactId,
+ newContentValues(RawContacts.PINNED, 0, RawContacts.STARRED, 1));
+ assertValuesForRawContact(i2.mRawContactId,
+ newContentValues(RawContacts.PINNED, PinnedPositions.UNPINNED, RawContacts.STARRED,
+ 0));
+
+ ContactUtil.delete(mResolver, i1.mContactId);
+ ContactUtil.delete(mResolver, i2.mContactId);
+ }
+
+ /**
+ * Verifies that the stored values for the contact that corresponds to the given contactId
+ * contain the exact same name-value pairs in the given ContentValues.
+ *
+ * @param contactId Id of a valid contact in the contacts database.
+ * @param contentValues A valid ContentValues object.
+ */
+ private void assertValuesForContact(long contactId, ContentValues contentValues) {
+ DatabaseAsserts.assertStoredValuesInUriMatchExactly(mResolver, Contacts.CONTENT_URI.
+ buildUpon().appendEncodedPath(String.valueOf(contactId)).build(), contentValues);
+ }
+
+ /**
+ * Verifies that the stored values for the raw contact that corresponds to the given
+ * rawContactId contain the exact same name-value pairs in the given ContentValues.
+ *
+ * @param rawContactId Id of a valid contact in the contacts database
+ * @param contentValues A valid ContentValues object
+ */
+ private void assertValuesForRawContact(long rawContactId, ContentValues contentValues) {
+ DatabaseAsserts.assertStoredValuesInUriMatchExactly(mResolver, RawContacts.CONTENT_URI.
+ buildUpon().appendEncodedPath(String.valueOf(rawContactId)).build(), contentValues);
+ }
+
+ /**
+ * Updates the contacts provider for a contact or raw contact corresponding to the given
+ * contact with key-value pairs as specified in the provided string parameters. Throws an
+ * exception if the number of provided string parameters is not zero or non-even.
+ *
+ * @param uri base URI that the provided ID will be appended onto, in order to creating the
+ * resulting URI
+ * @param id id of the contact of raw contact to perform the update for
+ * @param extras an even number of string parameters that correspond to name-value pairs
+ *
+ * @return the number of rows that were updated
+ */
+ private int updateItemForContact(Uri uri, long id, String... extras) {
+ Uri itemUri = ContentUris.withAppendedId(uri, id);
+ return updateItemForUri(itemUri, extras);
+ }
+
+ /**
+ * Updates the contacts provider for the given YRU with key-value pairs as specified in the
+ * provided string parameters. Throws an exception if the number of provided string parameters
+ * is not zero or non-even.
+ *
+ * @param uri URI to perform the update for
+ * @param extras an even number of string parameters that correspond to name-value pairs
+ *
+ * @return the number of rows that were updated
+ */
+ private int updateItemForUri(Uri uri, String... extras) {
+ ContentValues values = new ContentValues();
+ CommonDatabaseUtils.extrasVarArgsToValues(values, extras);
+ return mResolver.update(uri, values, null, null);
+ }
+}
+
diff --git a/tests/tests/provider/src/android/provider/cts/ContactsContract_StrequentsTest.java b/tests/tests/provider/src/android/provider/cts/ContactsContract_StrequentsTest.java
new file mode 100644
index 0000000..d188d18
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/ContactsContract_StrequentsTest.java
@@ -0,0 +1,326 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.provider.cts;
+
+
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.Contactables;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Organization;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.DataUsageFeedback;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.cts.ContactsContract_TestDataBuilder.TestContact;
+import android.provider.cts.ContactsContract_TestDataBuilder.TestRawContact;
+import android.provider.cts.contacts.DatabaseAsserts;
+import android.test.InstrumentationTestCase;
+
+import java.util.ArrayList;
+
+/**
+ * CTS tests for {@link android.provider.ContactsContract.Contacts#CONTENT_STREQUENT_URI} and
+ * {@link android.provider.ContactsContract.Contacts#CONTENT_STREQUENT_FILTER_URI} apis.
+ */
+public class ContactsContract_StrequentsTest extends InstrumentationTestCase {
+ private ContentResolver mResolver;
+ private ContactsContract_TestDataBuilder mBuilder;
+
+ public static ContentValues[] sContentValues = new ContentValues[3];
+ static {
+ ContentValues cv1 = new ContentValues();
+ cv1.put(Contacts.DISPLAY_NAME, "Hot Tamale");
+ sContentValues[0] = cv1;
+
+ ContentValues cv2 = new ContentValues();
+ cv2.put(Contacts.DISPLAY_NAME, "Cold Tamago");
+ sContentValues[1] = cv2;
+
+ ContentValues cv3 = new ContentValues();
+ cv3.put(Contacts.DISPLAY_NAME, "John Doe");
+ sContentValues[2] = cv3;
+ }
+
+ private long[] mDataIds = new long[3];
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mResolver = getInstrumentation().getTargetContext().getContentResolver();
+ ContentProviderClient provider =
+ mResolver.acquireContentProviderClient(ContactsContract.AUTHORITY);
+ mBuilder = new ContactsContract_TestDataBuilder(provider);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ mBuilder.cleanup();
+ }
+
+ /**
+ * Tests that {@link android.provider.ContactsContract.Contacts#CONTENT_STREQUENT_URI} returns
+ * no contacts if there are no starred or frequent contacts in the user's contacts.
+ */
+ public void testStrequents_noStarredOrFrequents() throws Exception {
+ long[] ids = setupTestData();
+ assertCursorStoredValuesWithContactsFilter(Contacts.CONTENT_STREQUENT_URI, ids);
+ }
+
+ /**
+ * Tests that {@link android.provider.ContactsContract.Contacts#CONTENT_STREQUENT_URI} returns
+ * starred contacts in the correct order if there are only starred contacts in the user's
+ * contacts.
+ */
+ public void testStrequents_starredOnlyInCorrectOrder() throws Exception {
+ long[] ids = setupTestData();
+
+ // Star/favorite the first and third contact.
+ starContact(ids[0]);
+ starContact(ids[1]);
+
+ // Only the starred contacts should be returned, ordered alphabetically by name
+ assertCursorStoredValuesWithContactsFilter(Contacts.CONTENT_STREQUENT_URI, ids,
+ sContentValues[1], sContentValues[0]);
+ }
+
+ /**
+ * Tests that {@link android.provider.ContactsContract.Contacts#CONTENT_STREQUENT_URI} returns
+ * frequent contacts in the correct order if there are only frequent contacts in the user's
+ * contacts.
+ */
+ public void testStrequents_frequentsOnlyInCorrectOrder() throws Exception {
+ long[] ids = setupTestData();
+
+ // Contact the first contact once.
+ markDataAsUsed(mDataIds[0], 1);
+
+ // Contact the second contact thrice.
+ markDataAsUsed(mDataIds[1], 3);
+
+ // Contact the third contact twice.
+ markDataAsUsed(mDataIds[2], 2);
+
+ // The strequents uri should now return contact 2, 3, 1 in order due to ranking by
+ // data usage.
+ assertCursorStoredValuesWithContactsFilter(Contacts.CONTENT_STREQUENT_URI, ids,
+ sContentValues[1], sContentValues[2], sContentValues[0]);
+ }
+
+ /**
+ * Tests that {@link android.provider.ContactsContract.Contacts#CONTENT_STREQUENT_URI} returns
+ * first starred, then frequent contacts in their respective correct orders if there are both
+ * starred and frequent contacts in the user's contacts.
+ */
+ public void testStrequents_starredAndFrequentsInCorrectOrder() throws Exception {
+ long[] ids = setupTestData();
+
+ // Contact the first contact once.
+ markDataAsUsed(mDataIds[0], 1);
+
+ // Contact the second contact thrice.
+ markDataAsUsed(mDataIds[1], 3);
+
+ // Contact the third contact twice, and mark it as used
+ markDataAsUsed(mDataIds[2], 2);
+ starContact(ids[2]);
+
+ // The strequents uri should now return contact 3, 2, 1 in order. Contact 3 is ranked first
+ // because it is starred, followed by contacts 2 and 1 due to their data usage ranking.
+ // Note that contact 3 is only returned once (as a starred contact) even though it is also
+ // a frequently contacted contact.
+ assertCursorStoredValuesWithContactsFilter(Contacts.CONTENT_STREQUENT_URI, ids,
+ sContentValues[2], sContentValues[1], sContentValues[0]);
+ }
+
+ /**
+ * Tests that {@link android.provider.ContactsContract.Contacts#CONTENT_STREQUENT_FILTER_URI}
+ * correctly filters the returned contacts with the given user input.
+ */
+ public void testStrequents_withFilter() throws Exception {
+ long[] ids = setupTestData();
+
+ //Star all 3 contacts
+ starContact(ids[0]);
+ starContact(ids[1]);
+ starContact(ids[2]);
+
+ // Construct a uri that filters for the query string "ta".
+ Uri uri = Contacts.CONTENT_STREQUENT_FILTER_URI.buildUpon().appendEncodedPath("ta").build();
+
+ // Only contact 1 and 2 should be returned (sorted in alphabetical order) due to the
+ // filtered query.
+ assertCursorStoredValuesWithContactsFilter(uri, ids, sContentValues[1], sContentValues[0]);
+ }
+
+ public void testStrequents_phoneOnly() throws Exception {
+ long[] ids = setupTestData();
+
+ // Star all 3 contacts
+ starContact(ids[0]);
+ starContact(ids[1]);
+ starContact(ids[2]);
+
+ // Construct a uri for phone only favorites.
+ Uri uri = Contacts.CONTENT_STREQUENT_URI.buildUpon().
+ appendQueryParameter(ContactsContract.STREQUENT_PHONE_ONLY, "true").build();
+
+ // Only the contacts with phone numbers are returned, in alphabetical order. Filtering
+ // is done with data ids instead of contact ids since each row contains a single data item.
+ assertCursorStoredValuesWithContactsFilter(uri, mDataIds, sContentValues[0],
+ sContentValues[2]);
+ }
+
+ public void testStrequents_phoneOnlyFrequentsOrder() throws Exception {
+ long[] ids = setupTestData();
+
+ // Contact the first contact once.
+ markDataAsUsed(mDataIds[0], 1);
+
+ // Contact the second contact twice.
+ markDataAsUsed(mDataIds[1], 2);
+
+ // Contact the third contact thrice.
+ markDataAsUsed(mDataIds[2], 3);
+
+ // Construct a uri for phone only favorites.
+ Uri uri = Contacts.CONTENT_STREQUENT_URI.buildUpon().
+ appendQueryParameter(ContactsContract.STREQUENT_PHONE_ONLY, "true").build();
+
+ // Only the contacts with phone numbers are returned, in frequency ranking order.
+ assertCursorStoredValuesWithContactsFilter(uri, mDataIds, sContentValues[2],
+ sContentValues[0]);
+ }
+
+ /**
+ * Given a uri, performs a query on the contacts provider for that uri and asserts that the
+ * cursor returned from the query matches the expected results.
+ *
+ * @param uri Uri to perform the query for
+ * @param contactsId Array of contact IDs that serves as an additional filter on the result
+ * set. This is needed to limit the output to temporary test contacts that were created for
+ * purposes of the test, so that the tests do not fail on devices with existing contacts on
+ * them
+ * @param expected An array of ContentValues corresponding to the expected output of the query
+ */
+ private void assertCursorStoredValuesWithContactsFilter(Uri uri, long[] contactsId,
+ ContentValues... expected) {
+ // We need this helper function to add a filter for specific contacts because
+ // otherwise tests will fail if performed on a device with existing contacts data
+ StringBuilder sb = new StringBuilder();
+ sb.append(Contacts._ID + " in ");
+ sb.append("(");
+ for (int i = 0; i < contactsId.length; i++) {
+ if (i != 0) sb.append(",");
+ sb.append(contactsId[i]);
+ }
+ sb.append(")");
+ DatabaseAsserts.assertStoredValuesInUriMatchExactly(mResolver, uri, null, sb.toString(),
+ null, null, expected);
+ }
+
+ /**
+ * Given a contact id, update the contact corresponding to that contactId so that it now shows
+ * up in the user's favorites/starred contacts.
+ *
+ * @param contactId Contact ID corresponding to the contact to star
+ */
+ private void starContact(long contactId) {
+ ContentValues values = new ContentValues();
+ values.put(Contacts.STARRED, 1);
+ mResolver.update(ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId), values,
+ null, null);
+ }
+
+ /**
+ * Given a data id, increment the data usage stats by a given number of usages to simulate
+ * the user making a call to the given data item.
+ *
+ * @param dataId Id of the data item to increment data usage stats for
+ * @param numTimes The number of times to increase the data usage stats by
+ */
+ private void markDataAsUsed(long dataId, int numTimes) {
+ Uri uri = ContactsContract.DataUsageFeedback.FEEDBACK_URI.buildUpon().
+ appendPath(String.valueOf(dataId)).
+ appendQueryParameter(DataUsageFeedback.USAGE_TYPE,
+ DataUsageFeedback.USAGE_TYPE_CALL).build();
+ for (int i = 1; i <= numTimes; i++) {
+ mResolver.update(uri, new ContentValues(), null, null);
+ }
+ }
+
+ /**
+ * Setup the contacts database with temporary contacts used for testing. These contacts will
+ * be removed during teardown.
+ *
+ * @return An array of long values corresponding to the ids of the created contacts
+ *
+ * @throws Exception
+ */
+ private long[] setupTestData() throws Exception {
+ TestRawContact rawContact = mBuilder.newRawContact()
+ .with(RawContacts.ACCOUNT_TYPE, "test_account")
+ .with(RawContacts.ACCOUNT_NAME, "test_name")
+ .insert();
+ rawContact.newDataRow(StructuredName.CONTENT_ITEM_TYPE)
+ .with(StructuredName.DISPLAY_NAME, "Hot Tamale")
+ .insert();
+ mDataIds[0] = rawContact.newDataRow(Phone.CONTENT_ITEM_TYPE)
+ .with(Phone.DATA, "510-123-5769")
+ .with(Email.TYPE, Phone.TYPE_HOME)
+ .insert().load().getId();
+ rawContact.load();
+ TestContact contact = rawContact.getContact().load();
+
+ TestRawContact rawContact2 = mBuilder.newRawContact()
+ .with(RawContacts.ACCOUNT_TYPE, "test_account")
+ .with(RawContacts.ACCOUNT_NAME, "test_name")
+ .insert();
+ rawContact2.newDataRow(StructuredName.CONTENT_ITEM_TYPE)
+ .with(StructuredName.DISPLAY_NAME, "Cold Tamago")
+ .insert();
+ mDataIds[1] = rawContact2.newDataRow(Email.CONTENT_ITEM_TYPE)
+ .with(Email.DATA, "eggs@farmers.org")
+ .with(Email.TYPE, Email.TYPE_HOME)
+ .insert().load().getId();
+ rawContact2.load();
+ TestContact contact2 = rawContact2.getContact().load();
+
+ TestRawContact rawContact3 = mBuilder.newRawContact()
+ .with(RawContacts.ACCOUNT_TYPE, "test_account")
+ .with(RawContacts.ACCOUNT_NAME, "test_name")
+ .insert();
+ rawContact3.newDataRow(StructuredName.CONTENT_ITEM_TYPE)
+ .with(StructuredName.DISPLAY_NAME, "John Doe")
+ .insert();
+ mDataIds[2] = rawContact3.newDataRow(Phone.CONTENT_ITEM_TYPE)
+ .with(Phone.DATA, "518-354-1111")
+ .with(Phone.TYPE, Phone.TYPE_HOME)
+ .insert().load().getId();
+ rawContact3.load();
+ TestContact contact3 = rawContact3.getContact().load();
+
+ return new long[] {contact.getId(), contact2.getId(), contact3.getId()};
+ }
+}
+
diff --git a/tests/tests/provider/src/android/provider/cts/ContactsContract_TestDataBuilder.java b/tests/tests/provider/src/android/provider/cts/ContactsContract_TestDataBuilder.java
index 43c249e..471f895 100644
--- a/tests/tests/provider/src/android/provider/cts/ContactsContract_TestDataBuilder.java
+++ b/tests/tests/provider/src/android/provider/cts/ContactsContract_TestDataBuilder.java
@@ -334,6 +334,10 @@
return getLong(Data.RAW_CONTACT_ID);
}
+ public long getId() {
+ return getLong(Data._ID);
+ }
+
public TestRawContact getRawContact() throws Exception {
return mRawContact;
}
diff --git a/tests/tests/provider/src/android/provider/cts/ContactsTest.java b/tests/tests/provider/src/android/provider/cts/ContactsTest.java
index b496007..db1c4f7 100644
--- a/tests/tests/provider/src/android/provider/cts/ContactsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/ContactsTest.java
@@ -413,7 +413,10 @@
final String[] CALLS_PROJECTION = new String[] {
Calls._ID, Calls.NUMBER, Calls.DATE, Calls.DURATION, Calls.TYPE,
Calls.NEW, Calls.CACHED_NAME, Calls.CACHED_NUMBER_TYPE,
- Calls.CACHED_NUMBER_LABEL};
+ Calls.CACHED_NUMBER_LABEL, Calls.CACHED_FORMATTED_NUMBER,
+ Calls.CACHED_MATCHED_NUMBER, Calls.CACHED_NORMALIZED_NUMBER,
+ Calls.CACHED_LOOKUP_URI, Calls.CACHED_PHOTO_ID, Calls.COUNTRY_ISO,
+ Calls.GEOCODED_LOCATION};
final int ID_INDEX = 0;
final int NUMBER_INDEX = 1;
final int DATE_INDEX = 2;
@@ -423,15 +426,30 @@
final int CACHED_NAME_INDEX = 6;
final int CACHED_NUMBER_TYPE_INDEX = 7;
final int CACHED_NUMBER_LABEL_INDEX = 8;
+ final int CACHED_FORMATTED_NUMBER_INDEX = 9;
+ final int CACHED_MATCHED_NUMBER_INDEX = 10;
+ final int CACHED_NORMALIZED_NUMBER_INDEX = 11;
+ final int CACHED_LOOKUP_URI_INDEX = 12;
+ final int CACHED_PHOTO_ID_INDEX = 13;
+ final int COUNTRY_ISO_INDEX = 14;
+ final int GEOCODED_LOCATION_INDEX = 15;
String insertCallsNumber = "0123456789";
int insertCallsDuration = 120;
String insertCallsName = "cached_name_insert";
String insertCallsNumberLabel = "cached_label_insert";
- String updateCallsNumber = "9876543210";
+
+ String updateCallsNumber = "987654321";
int updateCallsDuration = 310;
String updateCallsName = "cached_name_update";
String updateCallsNumberLabel = "cached_label_update";
+ String updateCachedFormattedNumber = "987-654-4321";
+ String updateCachedMatchedNumber = "987-654-4321";
+ String updateCachedNormalizedNumber = "+1987654321";
+ String updateCachedLookupUri = "cached_lookup_uri_update";
+ long updateCachedPhotoId = 100;
+ String updateCountryIso = "hk";
+ String updateGeocodedLocation = "Hong Kong";
try {
// Test: insert
@@ -463,7 +481,8 @@
int id = cursor.getInt(ID_INDEX);
cursor.close();
- // Test: update
+ // Test: update. Also add new cached fields to simulate extra cached fields being
+ // inserted into the call log after the initial lookup.
int now = (int) new Date().getTime();
value.clear();
value.put(Calls.NUMBER, updateCallsNumber);
@@ -474,6 +493,13 @@
value.put(Calls.CACHED_NAME, updateCallsName);
value.put(Calls.CACHED_NUMBER_TYPE, Phones.TYPE_CUSTOM);
value.put(Calls.CACHED_NUMBER_LABEL, updateCallsNumberLabel);
+ value.put(Calls.CACHED_FORMATTED_NUMBER, updateCachedFormattedNumber);
+ value.put(Calls.CACHED_MATCHED_NUMBER, updateCachedMatchedNumber);
+ value.put(Calls.CACHED_NORMALIZED_NUMBER, updateCachedNormalizedNumber);
+ value.put(Calls.CACHED_PHOTO_ID, updateCachedPhotoId);
+ value.put(Calls.COUNTRY_ISO, updateCountryIso);
+ value.put(Calls.GEOCODED_LOCATION, updateGeocodedLocation);
+ value.put(Calls.CACHED_LOOKUP_URI, updateCachedLookupUri);
mCallLogProvider.update(uri, value, null, null);
cursor = mCallLogProvider.query(Calls.CONTENT_URI, CALLS_PROJECTION,
@@ -487,6 +513,15 @@
assertEquals(updateCallsName, cursor.getString(CACHED_NAME_INDEX));
assertEquals(Phones.TYPE_CUSTOM, cursor.getInt(CACHED_NUMBER_TYPE_INDEX));
assertEquals(updateCallsNumberLabel, cursor.getString(CACHED_NUMBER_LABEL_INDEX));
+ assertEquals(updateCachedFormattedNumber,
+ cursor.getString(CACHED_FORMATTED_NUMBER_INDEX));
+ assertEquals(updateCachedMatchedNumber, cursor.getString(CACHED_MATCHED_NUMBER_INDEX));
+ assertEquals(updateCachedNormalizedNumber,
+ cursor.getString(CACHED_NORMALIZED_NUMBER_INDEX));
+ assertEquals(updateCachedPhotoId, cursor.getLong(CACHED_PHOTO_ID_INDEX));
+ assertEquals(updateCountryIso, cursor.getString(COUNTRY_ISO_INDEX));
+ assertEquals(updateGeocodedLocation, cursor.getString(GEOCODED_LOCATION_INDEX));
+ assertEquals(updateCachedLookupUri, cursor.getString(CACHED_LOOKUP_URI_INDEX));
cursor.close();
// Test: delete
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_FilesTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_FilesTest.java
index a362df3..356fe3c 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_FilesTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_FilesTest.java
@@ -26,14 +26,21 @@
import android.net.Uri;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
import android.provider.MediaStore;
import android.provider.MediaStore.MediaColumns;
import android.test.AndroidTestCase;
+import android.util.Log;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
+import java.io.FilenameFilter;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
public class MediaStore_FilesTest extends AndroidTestCase {
@@ -43,6 +50,40 @@
protected void setUp() throws Exception {
super.setUp();
mResolver = mContext.getContentResolver();
+ cleanup();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ cleanup();
+ }
+
+ void cleanup() {
+ final String testName = getClass().getCanonicalName();
+ mResolver.delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+ "_data LIKE ?1", new String[] {"%" + testName + "%"});
+ File ext = Environment.getExternalStorageDirectory();
+ File[] junk = ext.listFiles(new FilenameFilter() {
+
+ @Override
+ public boolean accept(File dir, String filename) {
+ return filename.contains(testName);
+ }
+ });
+ for (File f: junk) {
+ deleteAll(f);
+ }
+ }
+
+ void deleteAll(File f) {
+ if (f.isDirectory()) {
+ File [] sub = f.listFiles();
+ for (File s: sub) {
+ deleteAll(s);
+ }
+ }
+ f.delete();
}
public void testGetContentUri() {
@@ -153,6 +194,15 @@
}
}
+ String realPathFor(ParcelFileDescriptor pfd) {
+ File real = new File("/proc/self/fd/" + pfd.getFd());
+ try {
+ return real.getCanonicalPath();
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
public void testAccess() throws IOException {
// clean up from previous run
mResolver.delete(MediaStore.Images.Media.INTERNAL_CONTENT_URI,
@@ -255,19 +305,19 @@
// get the real path from the file descriptor (this relies on the media provider
// having opened the path via the real path instead of the emulated path).
- File real = new File("/proc/self/fd/" + pfd.getFd());
values = new ContentValues();
- values.put("_data", real.getCanonicalPath());
+ values.put("_data", realPathFor(pfd));
mResolver.update(uri, values, null, null);
pfd.close();
// we shouldn't be able to access this
try {
pfd = mResolver.openFileDescriptor(uri, "r");
- pfd.close();
- fail("shouldn't be here");
+ fail("shouldn't have fd for " + realPathFor(pfd));
} catch (FileNotFoundException e) {
// expected
+ } finally {
+ pfd.close();
}
} catch (FileNotFoundException e) {
fail("couldn't open file");
@@ -279,6 +329,165 @@
if (sdfile != null) {
assertEquals(true, sdfile.delete());
}
+
+ // test secondary storage if present
+ List<File> allpaths = getSecondaryPackageSpecificPaths(mContext);
+ List<String> trimmedPaths = new ArrayList<String>();
+
+ for (File extpath: allpaths) {
+ assertNotNull("Valid media must be inserted during CTS", extpath);
+ assertEquals("Valid media must be inserted for " + extpath
+ + " during CTS", Environment.MEDIA_MOUNTED,
+ Environment.getStorageState(extpath));
+
+ File child = extpath;
+ while (true) {
+ File parent = child.getParentFile();
+ if (parent == null) {
+ fail("didn't expect to be here");
+ }
+ if (!Environment.MEDIA_MOUNTED.equals(Environment.getStorageState(parent))) {
+ // we went past the root
+ String abspath = child.getAbsolutePath();
+ if (!trimmedPaths.contains(abspath)) {
+ trimmedPaths.add(abspath);
+ }
+ break;
+ }
+ child = parent;
+ }
+ }
+
+ String fileDir = Environment.getExternalStorageDirectory() +
+ "/" + getClass().getCanonicalName() + "-" + SystemClock.elapsedRealtime();
+ String fileName = fileDir + "/TestSecondary.Mp3";
+ writeFile(R.raw.testmp3_2, fileName); // file without album art
+
+
+ // insert temp file
+ values = new ContentValues();
+ values.put(MediaStore.Audio.Media.DATA, fileName);
+ values.put(MediaStore.Audio.Media.ARTIST, "Artist-" + SystemClock.elapsedRealtime());
+ values.put(MediaStore.Audio.Media.ALBUM, "Album-" + SystemClock.elapsedRealtime());
+ values.put(MediaStore.Audio.Media.MIME_TYPE, "audio/mp3");
+ Uri fileUri = mResolver.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, values);
+ // give media provider some time to realize there's no album art
+ //SystemClock.sleep(1000);
+ // get its album id
+ Cursor c = mResolver.query(fileUri, new String[] { MediaStore.Audio.Media.ALBUM_ID},
+ null, null, null);
+ assertTrue(c.moveToFirst());
+ int albumid = c.getInt(0);
+ Uri albumArtUriBase = Uri.parse("content://media/external/audio/albumart");
+ Uri albumArtUri = ContentUris.withAppendedId(albumArtUriBase, albumid);
+ try {
+ pfd = mResolver.openFileDescriptor(albumArtUri, "r");
+ fail("no album art, shouldn't be here. Got: " + realPathFor(pfd));
+ } catch (Exception e) {
+ // expected
+ }
+
+ // replace file with one that has album art
+ writeFile(R.raw.testmp3, fileName); // file with album art
+
+ for (String s: trimmedPaths) {
+ File dir = new File(s + "/foobardir-" + SystemClock.elapsedRealtime());
+ assertFalse("please remove " + dir.getAbsolutePath()
+ + " before running", dir.exists());
+ File file = new File(dir, "foobar");
+ values = new ContentValues();
+ values.put(MediaStore.Audio.Media.ALBUM_ID, albumid);
+ values.put(MediaStore.Audio.Media.DATA, file.getAbsolutePath());
+ mResolver.insert(albumArtUriBase, values);
+ try {
+ pfd = mResolver.openFileDescriptor(albumArtUri, "r");
+ fail("shouldn't have fd for album " + albumid + ", got " + realPathFor(pfd));
+ } catch (Exception e) {
+ // expected
+ } finally {
+ pfd.close();
+ }
+ assertFalse(dir.getAbsolutePath() + " was created", dir.exists());
+ }
+ mResolver.delete(fileUri, null, null);
+ new File(fileName).delete();
+
+ // try creating files in root
+ for (String s: trimmedPaths) {
+ File dir = new File(s);
+ File file = new File(dir, "foobar.jpg");
+
+ values = new ContentValues();
+ values.put(MediaStore.Files.FileColumns.DATA, file.getAbsolutePath());
+ fileUri = mResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
+ assertNotNull(fileUri);
+
+ // check that adding the file doesn't cause it to be created
+ assertFalse(file.exists());
+
+ // check if opening the file for write works
+ try {
+ mResolver.openOutputStream(fileUri).close();
+ fail("shouldn't have been able to create output stream");
+ } catch (SecurityException e) {
+ // expected
+ }
+ // check that deleting the file doesn't cause it to be created
+ mResolver.delete(fileUri, null, null);
+ assertFalse(file.exists());
+ }
+
+ // try creating files in new subdir
+ for (String s: trimmedPaths) {
+ File dir = new File(s + "/foobardir");
+ File file = new File(dir, "foobar.jpg");
+
+ values = new ContentValues();
+ values.put(MediaStore.Files.FileColumns.DATA, dir.getAbsolutePath());
+
+ Uri dirUri = mResolver.insert(MediaStore.Files.getContentUri("external"), values);
+ assertNotNull(dirUri);
+
+ values = new ContentValues();
+ values.put(MediaStore.Files.FileColumns.DATA, file.getAbsolutePath());
+ fileUri = mResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
+ assertNotNull(fileUri);
+
+ // check that adding the file or its folder didn't cause either one to be created
+ assertFalse(dir.exists());
+ assertFalse(file.exists());
+
+ // check if opening the file for write works
+ try {
+ mResolver.openOutputStream(fileUri).close();
+ fail("shouldn't have been able to create output stream");
+ } catch (SecurityException e) {
+ // expected
+ }
+ // check that deleting the file or its folder doesn't cause either one to be created
+ mResolver.delete(fileUri, null, null);
+ assertFalse(dir.exists());
+ assertFalse(file.exists());
+ mResolver.delete(dirUri, null, null);
+ assertFalse(dir.exists());
+ assertFalse(file.exists());
+ }
+ }
+
+ public static List<File> getSecondaryPackageSpecificPaths(Context context) {
+ final List<File> paths = new ArrayList<File>();
+ Collections.addAll(paths, dropFirst(context.getExternalCacheDirs()));
+ Collections.addAll(paths, dropFirst(context.getExternalFilesDirs(null)));
+ Collections.addAll(
+ paths, dropFirst(context.getExternalFilesDirs(Environment.DIRECTORY_PICTURES)));
+ Collections.addAll(paths, dropFirst(context.getObbDirs()));
+ return paths;
+ }
+
+ private static File[] dropFirst(File[] before) {
+ final File[] after = new File[before.length - 1];
+ System.arraycopy(before, 1, after, 0, after.length);
+ return after;
}
private void writeFile(int resid, String path) throws IOException {
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Images_MediaTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Images_MediaTest.java
index 3f28a34..e68286f 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Images_MediaTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_Images_MediaTest.java
@@ -78,13 +78,6 @@
mHelper = new FileCopyHelper(mContext);
mRowsAdded = new ArrayList<Uri>();
-
- String campath = Environment.getExternalStorageDirectory() + File.separator +
- Environment.DIRECTORY_DCIM + File.separator + "Camera";
- File camfile = new File(campath);
- if (!camfile.exists()) {
- assertTrue("failed to create " + campath, camfile.mkdir());
- }
}
public void testInsertImageWithImagePath() throws Exception {
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Images_ThumbnailsTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Images_ThumbnailsTest.java
index 03adad7..e8a13a9 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Images_ThumbnailsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_Images_ThumbnailsTest.java
@@ -68,13 +68,6 @@
mHelper = new FileCopyHelper(mContext);
mRowsAdded = new ArrayList<Uri>();
-
- String campath = Environment.getExternalStorageDirectory() + File.separator +
- Environment.DIRECTORY_DCIM + File.separator + "Camera";
- File camfile = new File(campath);
- if (!camfile.exists()) {
- assertTrue("failed to create " + campath, camfile.mkdir());
- }
}
public void testQueryInternalThumbnails() throws Exception {
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/CommonDatabaseUtils.java b/tests/tests/provider/src/android/provider/cts/contacts/CommonDatabaseUtils.java
index 833de64..d89e06c 100644
--- a/tests/tests/provider/src/android/provider/cts/contacts/CommonDatabaseUtils.java
+++ b/tests/tests/provider/src/android/provider/cts/contacts/CommonDatabaseUtils.java
@@ -16,8 +16,11 @@
package android.provider.cts.contacts;
+import android.content.ContentValues;
import android.database.Cursor;
+import junit.framework.Assert;
+
import java.util.ArrayList;
import java.util.List;
@@ -67,4 +70,21 @@
cursor.close();
}
}
+
+ /**
+ * Verifies that the number of string parameters is either zero or even, and inserts them
+ * into the provided ContentValues object as a set of name-value pairs. Throws an exception if
+ * the number of string parameters is odd, or a single null parameter was provided.
+ *
+ * @param values ContentValues object to insert name-value pairs into
+ * @param extras Zero or even number of string parameters
+ */
+ public static void extrasVarArgsToValues(ContentValues values, String... extras) {
+ Assert.assertNotNull(extras);
+ // Check that the number of provided string parameters is even.
+ Assert.assertEquals(0, extras.length % 2);
+ for (int i = 0; i < extras.length; i += 2) {
+ values.put(extras[i], extras[i + 1]);
+ }
+ }
}
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactUtil.java b/tests/tests/provider/src/android/provider/cts/contacts/ContactUtil.java
index 2a53781..f6d67b9 100644
--- a/tests/tests/provider/src/android/provider/cts/contacts/ContactUtil.java
+++ b/tests/tests/provider/src/android/provider/cts/contacts/ContactUtil.java
@@ -22,6 +22,9 @@
import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract;
+import android.provider.ContactsContract.AggregationExceptions;
+
+import junit.framework.Assert;
/**
* Convenience methods for operating on the Contacts table.
@@ -71,4 +74,64 @@
}
return CommonDatabaseUtils.NOT_FOUND;
}
+
+ /**
+ * Verifies that the number of object parameters is either zero or even, inserts them
+ * into a new ContentValues object as a set of name-value pairs, and returns the newly created
+ * ContentValues object. Throws an exception if the number of string parameters is odd, or a
+ * single null parameter was provided.
+ *
+ * @param namesAndValues Zero or even number of object parameters to convert into name-value
+ * pairs
+ *
+ * @return newly created ContentValues containing the provided name-value pairs
+ */
+ public static ContentValues newContentValues(Object... namesAndValues) {
+ // Checks that the number of provided parameters is zero or even.
+ Assert.assertEquals(0, namesAndValues.length % 2);
+ final ContentValues contentValues = new ContentValues();
+ for (int i = 0; i < namesAndValues.length - 1; i += 2) {
+ Assert.assertNotNull(namesAndValues[i]);
+ final String name = namesAndValues[i].toString();
+ final Object value = namesAndValues[i + 1];
+ if (value == null) {
+ contentValues.putNull(name);
+ } else if (value instanceof String) {
+ contentValues.put(name, (String) value);
+ } else if (value instanceof Integer) {
+ contentValues.put(name, (Integer) value);
+ } else if (value instanceof Long) {
+ contentValues.put(name, (Long) value);
+ } else {
+ Assert.fail("Unsupported value type: " + value.getClass().getSimpleName() + " for "
+ + " name: " + name);
+ }
+ }
+ return contentValues;
+ }
+
+ /**
+ * Updates the content resolver with two given raw contact ids and an aggregation type to
+ * manually trigger the forced aggregation, splitting of two raw contacts or specify that
+ * the provider should automatically decide whether or not to aggregate the two raw contacts.
+ *
+ * @param resolver ContentResolver from a valid context
+ * @param type One of the following aggregation exception types:
+ * {@link AggregationExceptions#TYPE_AUTOMATIC},
+ * {@link AggregationExceptions#TYPE_KEEP_SEPARATE},
+ * {@link AggregationExceptions#TYPE_KEEP_TOGETHER}
+ * @param rawContactId1 Id of the first raw contact
+ * @param rawContactId2 Id of the second raw contact
+ */
+ public static void setAggregationException(ContentResolver resolver, int type,
+ long rawContactId1, long rawContactId2) {
+ ContentValues values = new ContentValues();
+ values.put(AggregationExceptions.RAW_CONTACT_ID1, rawContactId1);
+ values.put(AggregationExceptions.RAW_CONTACT_ID2, rawContactId2);
+ values.put(AggregationExceptions.TYPE, type);
+ // Actually set the aggregation exception in the contacts database, and check that a
+ // single row was updated.
+ Assert.assertEquals(1, resolver.update(AggregationExceptions.CONTENT_URI, values, null,
+ null));
+ }
}
diff --git a/tests/tests/renderscript/Android.mk b/tests/tests/renderscript/Android.mk
index e5ef654..77dc210 100644
--- a/tests/tests/renderscript/Android.mk
+++ b/tests/tests/renderscript/Android.mk
@@ -25,9 +25,6 @@
# When built, explicitly put it in the data partition.
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-# All tests should include android.test.runner.
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
LOCAL_JNI_SHARED_LIBRARIES := libcoremathtestcpp_jni
diff --git a/tests/tests/renderscript/AndroidManifest.xml b/tests/tests/renderscript/AndroidManifest.xml
index 49fca1e..2a23090 100644
--- a/tests/tests/renderscript/AndroidManifest.xml
+++ b/tests/tests/renderscript/AndroidManifest.xml
@@ -27,9 +27,12 @@
</application>
<!-- This is a self-instrumenting test package. -->
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.stub"
- android:label="CTS tests of Renderscript component"/>
+ android:label="CTS tests of Renderscript component">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/AtomicTest.java b/tests/tests/renderscript/src/android/renderscript/cts/AtomicTest.java
new file mode 100644
index 0000000..a0e3b7a
--- /dev/null
+++ b/tests/tests/renderscript/src/android/renderscript/cts/AtomicTest.java
@@ -0,0 +1,391 @@
+/*
+ * 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 android.renderscript.cts;
+import java.util.Random;
+import android.renderscript.*;
+
+public class AtomicTest extends RSBaseCompute {
+ int[] mSrcData;
+ int[] mReturnData = new int[1];
+ int[] mRefData = new int[1];
+ ScriptC_AtomicTest mScript;
+ Allocation mSrc;
+ Allocation mReturn;
+
+ private void initS(boolean fillData, int sz) {
+ mSrcData = new int[sz * sz];
+ mScript = new ScriptC_AtomicTest(mRS);
+ mSrc = Allocation.createTyped(mRS, Type.createXY(mRS, Element.I32(mRS), sz, sz));
+ mReturn = Allocation.createSized(mRS, Element.I32(mRS), 1);
+ if (fillData) {
+ RSUtils.genRandomInts(0, mSrcData, true, 32);
+ mSrc.copyFrom(mSrcData);
+ }
+ }
+
+ private void initU(boolean fillData, int sz) {
+ mSrcData = new int[sz * sz];
+ mScript = new ScriptC_AtomicTest(mRS);
+ mSrc = Allocation.createTyped(mRS, Type.createXY(mRS, Element.U32(mRS), sz, sz));
+ mReturn = Allocation.createSized(mRS, Element.U32(mRS), 1);
+ if (fillData) {
+ RSUtils.genRandomInts(0, mSrcData, false, 32);
+ mSrc.copyFrom(mSrcData);
+ }
+ }
+
+ public void testCas() {
+ initS(true, 1024);
+ mScript.set_gISum(10);
+ mScript.forEach_test_Cas(mSrc);
+ mScript.invoke_getValueS(mReturn);
+ mReturn.copyTo(mReturnData);
+ assertEquals("Incorrect value for Cas ", (10 + 1024*1024), mReturnData[0]);
+ }
+
+ public void testUCas() {
+ initU(true, 1024);
+ mScript.set_gUSum(10);
+ mScript.forEach_test_uCas(mSrc);
+ mScript.invoke_getValueU(mReturn);
+ mReturn.copyTo(mReturnData);
+ assertEquals("Incorrect value for UCas ", (10 + 1024*1024), mReturnData[0]);
+ }
+
+ public void testInc() {
+ initS(true, 1024);
+ mScript.set_gISum(10);
+ mScript.forEach_test_Inc(mSrc);
+ mScript.invoke_getValueS(mReturn);
+ mReturn.copyTo(mReturnData);
+ assertEquals("Incorrect value for Inc ", (10 + 1024*1024), mReturnData[0]);
+ }
+
+ public void testUInc() {
+ initU(true, 1024);
+ mScript.set_gUSum(10);
+ mScript.forEach_test_uInc(mSrc);
+ mScript.invoke_getValueU(mReturn);
+ mReturn.copyTo(mReturnData);
+ assertEquals("Incorrect value for UInc ", (10 + 1024*1024), mReturnData[0]);
+ }
+
+ public void testDec() {
+ initS(true, 1024);
+ mScript.set_gISum(10 + 1024*1024);
+ mScript.forEach_test_Dec(mSrc);
+ mScript.invoke_getValueS(mReturn);
+ mReturn.copyTo(mReturnData);
+ assertEquals("Incorrect value for Dec ", 10, mReturnData[0]);
+ }
+
+ public void testUDec() {
+ initU(true, 1024);
+ mScript.set_gUSum(10 + 1024*1024);
+ mScript.forEach_test_uDec(mSrc);
+ mScript.invoke_getValueU(mReturn);
+ mReturn.copyTo(mReturnData);
+ assertEquals("Incorrect value for UDec ", 10, mReturnData[0]);
+ }
+
+
+ public void testAdd() {
+ initS(true, 1024);
+ mScript.set_gISum(10);
+ mScript.forEach_test_Add(mSrc);
+ mScript.invoke_getValueS(mReturn);
+ mReturn.copyTo(mReturnData);
+
+ int sExpected = 10;
+ for (int i=0; i < mSrcData.length; i++) {
+ sExpected += mSrcData[i];
+ }
+ assertEquals("Incorrect value for Add ", sExpected, mReturnData[0]);
+ }
+
+ public void testUAdd() {
+ initU(true, 1024);
+ mScript.set_gUSum(10);
+ mScript.forEach_test_uAdd(mSrc);
+ mScript.invoke_getValueU(mReturn);
+ mReturn.copyTo(mReturnData);
+
+ int sExpected = 10;
+ for (int i=0; i < mSrcData.length; i++) {
+ sExpected += mSrcData[i];
+ }
+ assertEquals("Incorrect value for UAdd ", sExpected, mReturnData[0]);
+ }
+
+ public void testSub() {
+ initS(true, 1024);
+ mScript.set_gISum(10);
+ mScript.forEach_test_Sub(mSrc);
+ mScript.invoke_getValueS(mReturn);
+ mReturn.copyTo(mReturnData);
+
+ int sExpected = 10;
+ for (int i=0; i < mSrcData.length; i++) {
+ sExpected -= mSrcData[i];
+ }
+ assertEquals("Incorrect value for Sub ", sExpected, mReturnData[0]);
+ }
+
+ public void testUSub() {
+ initU(true, 1024);
+ mScript.set_gUSum(10);
+ mScript.forEach_test_uSub(mSrc);
+ mScript.invoke_getValueU(mReturn);
+ mReturn.copyTo(mReturnData);
+
+ int sExpected = 10;
+ for (int i=0; i < mSrcData.length; i++) {
+ sExpected -= mSrcData[i];
+ }
+ assertEquals("Incorrect value for USub ", sExpected, mReturnData[0]);
+ }
+
+ public void testXor() {
+ initS(true, 1024);
+ mScript.set_gISum(10);
+ mScript.forEach_test_Xor(mSrc);
+ mScript.invoke_getValueS(mReturn);
+ mReturn.copyTo(mReturnData);
+
+ int sExpected = 10;
+ for (int i=0; i < mSrcData.length; i++) {
+ sExpected ^= mSrcData[i];
+ }
+ assertEquals("Incorrect value for Xor ", sExpected, mReturnData[0]);
+ }
+
+ public void testUXor() {
+ initU(true, 1024);
+ mScript.set_gUSum(10);
+ mScript.forEach_test_uXor(mSrc);
+ mScript.invoke_getValueU(mReturn);
+ mReturn.copyTo(mReturnData);
+
+ int sExpected = 10;
+ for (int i=0; i < mSrcData.length; i++) {
+ sExpected ^= mSrcData[i];
+ }
+ assertEquals("Incorrect value for UXor ", sExpected, mReturnData[0]);
+ }
+
+ public void testMin() {
+ for (int i = 0; i < 16; i++) {
+ initS(true, 256);
+
+ mScript.set_gISum(0x7fffffff);
+ mScript.forEach_test_Min(mSrc);
+ mScript.invoke_getValueS(mReturn);
+ mReturn.copyTo(mReturnData);
+
+ mScript.set_gISum(0x7fffffff);
+ mScript.invoke_computeReference_Min(mSrc, mReturn);
+ mReturn.copyTo(mRefData);
+
+ assertEquals("Incorrect value for Min ", mRefData[0], mReturnData[0]);
+ }
+ }
+
+ public void testUMin() {
+ for (int i = 0; i < 16; i++) {
+ initU(true, 256);
+
+ mScript.set_gUSum(0xffffffffL);
+ mScript.forEach_test_uMin(mSrc);
+ mScript.invoke_getValueU(mReturn);
+ mReturn.copyTo(mReturnData);
+
+ mScript.set_gUSum(0xffffffffL);
+ mScript.invoke_computeReference_uMin(mSrc, mReturn);
+ mReturn.copyTo(mRefData);
+
+ assertEquals("Incorrect value for UMin ", mRefData[0], mReturnData[0]);
+ }
+ }
+
+ public void testMax() {
+ for (int i = 0; i < 16; i++) {
+ initS(true, 256);
+
+ mScript.set_gISum(0);
+ mScript.forEach_test_Max(mSrc);
+ mScript.invoke_getValueS(mReturn);
+ mReturn.copyTo(mReturnData);
+
+ mScript.set_gISum(0);
+ mScript.invoke_computeReference_Max(mSrc, mReturn);
+ mReturn.copyTo(mRefData);
+
+ assertEquals("Incorrect value for Min ", mRefData[0], mReturnData[0]);
+ }
+ }
+
+ public void testUMax() {
+ for (int i = 0; i < 16; i++) {
+ initU(true, 256);
+
+ mScript.set_gISum(0);
+ mScript.forEach_test_uMax(mSrc);
+ mScript.invoke_getValueU(mReturn);
+ mReturn.copyTo(mReturnData);
+
+ mScript.set_gISum(0);
+ mScript.invoke_computeReference_uMax(mSrc, mReturn);
+ mReturn.copyTo(mRefData);
+
+ assertEquals("Incorrect value for UMax ", mRefData[0], mReturnData[0]);
+ }
+ }
+
+ public void testAnd() {
+ Random r = new Random(78);
+
+ for (int i = 0; i < 64; i++) {
+ initS(false, 128);
+
+ for (int j = 0; j < mSrcData.length; j++) {
+ mSrcData[j] = ~0;
+ }
+ mSrcData[r.nextInt(mSrcData.length)] = ~0x40000000;
+ mSrcData[r.nextInt(mSrcData.length)] = ~0x10000000;
+ mSrcData[r.nextInt(mSrcData.length)] = ~0x02000000;
+ mSrcData[r.nextInt(mSrcData.length)] = ~0x00c00000;
+ mSrcData[r.nextInt(mSrcData.length)] = ~0x00010000;
+ mSrcData[r.nextInt(mSrcData.length)] = ~0x00080000;
+ mSrcData[r.nextInt(mSrcData.length)] = ~0x00001000;
+ mSrcData[r.nextInt(mSrcData.length)] = ~0x00000200;
+ mSrcData[r.nextInt(mSrcData.length)] = ~0x0000000f;
+ mSrc.copyFrom(mSrcData);
+
+ mScript.set_gISum(0xffffffff);
+ mScript.forEach_test_And(mSrc);
+ mScript.invoke_getValueS(mReturn);
+ mReturn.copyTo(mReturnData);
+
+ int sExpected = 0xffffffff;
+ for (int j = 0; j < mSrcData.length; j++) {
+ sExpected &= mSrcData[j];
+ }
+ assertEquals("Incorrect value for And ", sExpected, mReturnData[0]);
+ }
+ }
+
+ public void testUAnd() {
+ Random r = new Random(78);
+
+ for (int i = 0; i < 64; i++) {
+ initU(false, 128);
+
+ for (int j = 0; j < mSrcData.length; j++) {
+ mSrcData[j] = ~0;
+ }
+ mSrcData[r.nextInt(mSrcData.length)] = ~0x40000000;
+ mSrcData[r.nextInt(mSrcData.length)] = ~0x10000000;
+ mSrcData[r.nextInt(mSrcData.length)] = ~0x02000000;
+ mSrcData[r.nextInt(mSrcData.length)] = ~0x00c00000;
+ mSrcData[r.nextInt(mSrcData.length)] = ~0x00010000;
+ mSrcData[r.nextInt(mSrcData.length)] = ~0x00080000;
+ mSrcData[r.nextInt(mSrcData.length)] = ~0x00001000;
+ mSrcData[r.nextInt(mSrcData.length)] = ~0x00000200;
+ mSrcData[r.nextInt(mSrcData.length)] = ~0x0000000f;
+ mSrc.copyFrom(mSrcData);
+
+ mScript.set_gUSum(0xffffffffL);
+ mScript.forEach_test_uAnd(mSrc);
+ mScript.invoke_getValueU(mReturn);
+ mReturn.copyTo(mReturnData);
+
+ int sExpected = 0xffffffff;
+ for (int j = 0; j < mSrcData.length; j++) {
+ sExpected &= mSrcData[j];
+ }
+ assertEquals("Incorrect value for uAnd ", sExpected, mReturnData[0]);
+ }
+ }
+
+ public void testOr() {
+ Random r = new Random(78);
+
+ for (int i = 0; i < 64; i++) {
+ initS(false, 128);
+
+ for (int j = 0; j < mSrcData.length; j++) {
+ mSrcData[j] = 0;
+ }
+ mSrcData[r.nextInt(mSrcData.length)] = 0x40000000;
+ mSrcData[r.nextInt(mSrcData.length)] = 0x10000000;
+ mSrcData[r.nextInt(mSrcData.length)] = 0x02000000;
+ mSrcData[r.nextInt(mSrcData.length)] = 0x00c00000;
+ mSrcData[r.nextInt(mSrcData.length)] = 0x00010000;
+ mSrcData[r.nextInt(mSrcData.length)] = 0x00080000;
+ mSrcData[r.nextInt(mSrcData.length)] = 0x00001000;
+ mSrcData[r.nextInt(mSrcData.length)] = 0x00000200;
+ mSrcData[r.nextInt(mSrcData.length)] = 0x0000000f;
+ mSrc.copyFrom(mSrcData);
+
+ mScript.set_gISum(0);
+ mScript.forEach_test_Or(mSrc);
+ mScript.invoke_getValueS(mReturn);
+ mReturn.copyTo(mReturnData);
+
+ int sExpected = 0;
+ for (int j = 0; j < mSrcData.length; j++) {
+ sExpected |= mSrcData[j];
+ }
+ assertEquals("Incorrect value for Or ", sExpected, mReturnData[0]);
+ }
+ }
+
+ public void testUOr() {
+ Random r = new Random(78);
+
+ for (int i = 0; i < 64; i++) {
+ initU(false, 128);
+
+ for (int j = 0; j < mSrcData.length; j++) {
+ mSrcData[j] = 0;
+ }
+ mSrcData[r.nextInt(mSrcData.length)] = 0x40000000;
+ mSrcData[r.nextInt(mSrcData.length)] = 0x10000000;
+ mSrcData[r.nextInt(mSrcData.length)] = 0x02000000;
+ mSrcData[r.nextInt(mSrcData.length)] = 0x00c00000;
+ mSrcData[r.nextInt(mSrcData.length)] = 0x00010000;
+ mSrcData[r.nextInt(mSrcData.length)] = 0x00080000;
+ mSrcData[r.nextInt(mSrcData.length)] = 0x00001000;
+ mSrcData[r.nextInt(mSrcData.length)] = 0x00000200;
+ mSrcData[r.nextInt(mSrcData.length)] = 0x0000000f;
+ mSrc.copyFrom(mSrcData);
+
+ mScript.set_gUSum(0);
+ mScript.forEach_test_uOr(mSrc);
+ mScript.invoke_getValueU(mReturn);
+ mReturn.copyTo(mReturnData);
+
+ int sExpected = 0;
+ for (int j = 0; j < mSrcData.length; j++) {
+ sExpected |= mSrcData[j];
+ }
+ assertEquals("Incorrect value for UOr ", sExpected, mReturnData[0]);
+ }
+ }
+
+}
+
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/CoreMathVerifier.java b/tests/tests/renderscript/src/android/renderscript/cts/CoreMathVerifier.java
index 8841b14..dd0117f 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/CoreMathVerifier.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/CoreMathVerifier.java
@@ -364,7 +364,6 @@
Math.max(args.inValue, args.inMinValue)), 0, 0);
}
- /* TODO Not supporting long arguments currently
static public void computeClamp(TestClamp.ArgumentsLongLongLongLong args) {
args.out = minI64(args.inMaxValue, maxI64(args.inValue, args.inMinValue));
}
@@ -372,7 +371,6 @@
static public void computeClamp(TestClamp.ArgumentsUlongUlongUlongUlong args) {
args.out = minU64(args.inMaxValue, maxU64(args.inValue, args.inMinValue));
}
- */
static public void computeClz(TestClz.ArgumentsCharChar args) {
int x = args.inValue;
@@ -419,22 +417,18 @@
static public void computeConvert(TestConvert.ArgumentsCharUint args) {
args.out = convertCharToUint(args.inV);
}
- /* TODO Not supporting long arguments currently
static public void computeConvert(TestConvert.ArgumentsCharLong args) {
args.out = convertCharToLong(args.inV);
}
static public void computeConvert(TestConvert.ArgumentsCharUlong args) {
args.out = convertCharToUlong(args.inV);
}
- */
static public void computeConvert(TestConvert.ArgumentsCharFloat args) {
args.out = new Floaty(convertCharToFloat(args.inV), 0, 0);
}
- /* TODO Not supporting double arguments currently
static public void computeConvert(TestConvert.ArgumentsCharDouble args) {
args.out = new Floaty(convertCharToDouble(args.inV), 0, 0);
}
- */
static public void computeConvert(TestConvert.ArgumentsUcharChar args) {
args.out = convertUcharToChar(args.inV);
@@ -454,22 +448,18 @@
static public void computeConvert(TestConvert.ArgumentsUcharUint args) {
args.out = convertUcharToUint(args.inV);
}
- /* TODO Not supporting long arguments currently
static public void computeConvert(TestConvert.ArgumentsUcharLong args) {
args.out = convertUcharToLong(args.inV);
}
static public void computeConvert(TestConvert.ArgumentsUcharUlong args) {
args.out = convertUcharToUlong(args.inV);
}
- */
static public void computeConvert(TestConvert.ArgumentsUcharFloat args) {
args.out = new Floaty(convertUcharToFloat(args.inV), 0, 0);
}
- /* TODO Not supporting double arguments currently
static public void computeConvert(TestConvert.ArgumentsUcharDouble args) {
args.out = new Floaty(convertUcharToDouble(args.inV), 0, 0);
}
- */
static public void computeConvert(TestConvert.ArgumentsShortChar args) {
args.out = convertShortToChar(args.inV);
@@ -489,22 +479,18 @@
static public void computeConvert(TestConvert.ArgumentsShortUint args) {
args.out = convertShortToUint(args.inV);
}
- /* TODO Not supporting long arguments currently
static public void computeConvert(TestConvert.ArgumentsShortLong args) {
args.out = convertShortToLong(args.inV);
}
static public void computeConvert(TestConvert.ArgumentsShortUlong args) {
args.out = convertShortToUlong(args.inV);
}
- */
static public void computeConvert(TestConvert.ArgumentsShortFloat args) {
args.out = new Floaty(convertShortToFloat(args.inV), 0, 0);
}
- /* TODO Not supporting double arguments currently
static public void computeConvert(TestConvert.ArgumentsShortDouble args) {
args.out = new Floaty(convertShortToDouble(args.inV), 0, 0);
}
- */
static public void computeConvert(TestConvert.ArgumentsUshortChar args) {
args.out = convertUshortToChar(args.inV);
@@ -524,22 +510,18 @@
static public void computeConvert(TestConvert.ArgumentsUshortUint args) {
args.out = convertUshortToUint(args.inV);
}
- /* TODO Not supporting long arguments currently
static public void computeConvert(TestConvert.ArgumentsUshortLong args) {
args.out = convertUshortToLong(args.inV);
}
static public void computeConvert(TestConvert.ArgumentsUshortUlong args) {
args.out = convertUshortToUlong(args.inV);
}
- */
static public void computeConvert(TestConvert.ArgumentsUshortFloat args) {
args.out = new Floaty(convertUshortToFloat(args.inV), 0, 0);
}
- /* TODO Not supporting double arguments currently
static public void computeConvert(TestConvert.ArgumentsUshortDouble args) {
args.out = new Floaty(convertUshortToDouble(args.inV), 0, 0);
}
- */
static public void computeConvert(TestConvert.ArgumentsIntChar args) {
args.out = convertIntToChar(args.inV);
@@ -559,22 +541,18 @@
static public void computeConvert(TestConvert.ArgumentsIntUint args) {
args.out = convertIntToUint(args.inV);
}
- /* TODO Not supporting long arguments currently
static public void computeConvert(TestConvert.ArgumentsIntLong args) {
args.out = convertIntToLong(args.inV);
}
static public void computeConvert(TestConvert.ArgumentsIntUlong args) {
args.out = convertIntToUlong(args.inV);
}
- */
static public void computeConvert(TestConvert.ArgumentsIntFloat args) {
args.out = new Floaty(convertIntToFloat(args.inV), 1, 1);
}
- /* TODO Not supporting double arguments currently
static public void computeConvert(TestConvert.ArgumentsIntDouble args) {
args.out = new Floaty(convertIntToDouble(args.inV), 0, 0);
}
- */
static public void computeConvert(TestConvert.ArgumentsUintChar args) {
args.out = convertUintToChar(args.inV);
@@ -594,24 +572,19 @@
static public void computeConvert(TestConvert.ArgumentsUintUint args) {
args.out = convertUintToUint(args.inV);
}
- /* TODO Not supporting long arguments currently
static public void computeConvert(TestConvert.ArgumentsUintLong args) {
args.out = convertUintToLong(args.inV);
}
static public void computeConvert(TestConvert.ArgumentsUintUlong args) {
args.out = convertUintToUlong(args.inV);
}
- */
static public void computeConvert(TestConvert.ArgumentsUintFloat args) {
args.out = new Floaty(convertUintToFloat(args.inV), 1, 1);
}
- /* TODO Not supporting double arguments currently
static public void computeConvert(TestConvert.ArgumentsUintDouble args) {
args.out = new Floaty(convertUintToDouble(args.inV), 0, 0);
}
- */
- /* TODO Not supporting long arguments currently
static public void computeConvert(TestConvert.ArgumentsLongChar args) {
args.out = convertLongToChar(args.inV);
}
@@ -673,7 +646,6 @@
static public void computeConvert(TestConvert.ArgumentsUlongDouble args) {
args.out = new Floaty(convertUlongToDouble(args.inV), 1, 1);
}
- */
static public void computeConvert(TestConvert.ArgumentsFloatChar args) {
args.out = convertFloatToChar(args.inV);
@@ -693,24 +665,19 @@
static public void computeConvert(TestConvert.ArgumentsFloatUint args) {
args.out = convertFloatToUint(args.inV);
}
- /* TODO Not supporting long arguments currently
static public void computeConvert(TestConvert.ArgumentsFloatLong args) {
args.out = convertFloatToLong(args.inV);
}
static public void computeConvert(TestConvert.ArgumentsFloatUlong args) {
args.out = convertFloatToUlong(args.inV);
}
- */
static public void computeConvert(TestConvert.ArgumentsFloatFloat args) {
args.out = new Floaty(convertFloatToFloat(args.inV), 0, 0);
}
- /* TODO Not supporting double arguments currently
static public void computeConvert(TestConvert.ArgumentsFloatDouble args) {
args.out = new Floaty(convertFloatToDouble(args.inV), 0, 0);
}
- */
- /* TODO Not supporting double arguments currently
static public void computeConvert(TestConvert.ArgumentsDoubleChar args) {
args.out = convertDoubleToChar(args.inV);
}
@@ -741,7 +708,6 @@
static public void computeConvert(TestConvert.ArgumentsDoubleDouble args) {
args.out = new Floaty(convertDoubleToDouble(args.inV), 0, 0);
}
- */
static public void computeCopysign(TestCopysign.ArgumentsFloatFloatFloat args) {
args.out = new Floaty(Math.copySign(args.inX, args.inY), 0, 0);
@@ -986,7 +952,6 @@
args.out = maxU32(args.inV1, args.inV2);
}
- /* TODO enable once precision has been improved.
static public void computeMax(TestMax.ArgumentsLongLongLong args) {
args.out = maxI64(args.inV1, args.inV2);
}
@@ -994,7 +959,6 @@
static public void computeMax(TestMax.ArgumentsUlongUlongUlong args) {
args.out = maxU64(args.inV1, args.inV2);
}
- */
static public void computeMax(TestMax.ArgumentsFloatFloatFloat args) {
args.out = new Floaty(Math.max(args.in, args.in1), 0, 0);
@@ -1024,7 +988,6 @@
args.out = minU32(args.inV1, args.inV2);
}
- /* TODO enable once precision has been improved.
static public void computeMin(TestMin.ArgumentsLongLongLong args) {
args.out = minI64(args.inV1, args.inV2);
}
@@ -1032,7 +995,6 @@
static public void computeMin(TestMin.ArgumentsUlongUlongUlong args) {
args.out = minU64(args.inV1, args.inV2);
}
- */
static public void computeMin(TestMin.ArgumentsFloatFloatFloat args) {
args.out = new Floaty(Math.min(args.in, args.in1), 0, 0);
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/RSBaseCompute.java b/tests/tests/renderscript/src/android/renderscript/cts/RSBaseCompute.java
index f217bc3..0413f22 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/RSBaseCompute.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/RSBaseCompute.java
@@ -115,7 +115,6 @@
Element element = getElement(rs, dataType, size);
Allocation alloc = Allocation.createSized(rs, element, INPUTSIZE);
int width = (size == 3) ? 4 : size;
- /* TODO copy1DRangeFrom does not work for double
if (dataType == Element.DataType.FLOAT_64) {
double[] inArray = new double[INPUTSIZE * width];
// TODO The ranges for float is too small. We need to accept a wider range of values.
@@ -123,16 +122,13 @@
double max = 4.0 * Math.PI;
RSUtils.genRandomDoubles(seed, min, max, inArray, includeExtremes);
alloc.copy1DRangeFrom(0, INPUTSIZE, inArray);
- } else
- */
- if (dataType == Element.DataType.FLOAT_32) {
+ } else if (dataType == Element.DataType.FLOAT_32) {
float[] inArray = new float[INPUTSIZE * width];
// TODO The ranges for float is too small. We need to accept a wider range of values.
float min = -4.0f * (float) Math.PI;
float max = 4.0f * (float) Math.PI;
RSUtils.genRandomFloats(seed, min, max, inArray, includeExtremes);
alloc.copy1DRangeFrom(0, INPUTSIZE, inArray);
- /* TODO copy1DRangFrom does not work for long
} else if (dataType == Element.DataType.SIGNED_64) {
long[] inArray = new long[INPUTSIZE * width];
RSUtils.genRandomLongs(seed, inArray, true, 63);
@@ -141,7 +137,6 @@
long[] inArray = new long[INPUTSIZE * width];
RSUtils.genRandomLongs(seed, inArray, false, 64);
alloc.copy1DRangeFrom(0, INPUTSIZE, inArray);
- */
} else if (dataType == Element.DataType.SIGNED_32) {
int[] inArray = new int[INPUTSIZE * width];
RSUtils.genRandomInts(seed, inArray, true, 31);
@@ -178,13 +173,11 @@
Element element = getElement(rs, dataType, size);
Allocation alloc = Allocation.createSized(rs, element, INPUTSIZE);
int width = (size == 3) ? 4 : size;
- /* TODO copy1DRangeFrom does not work for double
if (dataType == Element.DataType.FLOAT_64) {
double[] inArray = new double[INPUTSIZE * width];
RSUtils.genRandomDoubles(seed, minValue, maxValue, inArray, false);
alloc.copy1DRangeFrom(0, INPUTSIZE, inArray);
- } else */
- if (dataType == Element.DataType.FLOAT_32) {
+ } else if (dataType == Element.DataType.FLOAT_32) {
float[] inArray = new float[INPUTSIZE * width];
RSUtils.genRandomFloats(seed, (float) minValue, (float) maxValue, inArray, false);
alloc.copy1DRangeFrom(0, INPUTSIZE, inArray);
@@ -201,13 +194,12 @@
Element element = getElement(rs, dataType, size);
Allocation alloc = Allocation.createSized(rs, element, INPUTSIZE);
int width = (size == 3) ? 4 : size;
- /* TODO copy1DRangFrom does not work for long
if (dataType == Element.DataType.SIGNED_64 ||
dataType == Element.DataType.UNSIGNED_64) {
long[] inArray = new long[INPUTSIZE * width];
RSUtils.genRandomLongs(seed, inArray, signed, numberOfBits);
alloc.copy1DRangeFrom(0, INPUTSIZE, inArray);
- } else */
+ } else
if (dataType == Element.DataType.SIGNED_32 ||
dataType == Element.DataType.UNSIGNED_32) {
int[] inArray = new int[INPUTSIZE * width];
@@ -239,7 +231,6 @@
}
int size = INPUTSIZE * stride;
Element.DataType dataType = element.getDataType();
- /* TODO copy1DRangeFrom does not work for double
if (dataType == Element.DataType.FLOAT_64) {
double[] minArray = new double[size];
double[] maxArray = new double[size];
@@ -254,7 +245,7 @@
}
minAlloc.copyFrom(minArray);
maxAlloc.copyFrom(maxArray);
- } else */
+ } else
if (dataType == Element.DataType.FLOAT_32) {
float[] minArray = new float[size];
float[] maxArray = new float[size];
@@ -269,7 +260,6 @@
}
minAlloc.copyFrom(minArray);
maxAlloc.copyFrom(maxArray);
- /* TODO copy1DRangFrom does not work for long
} else if (dataType == Element.DataType.SIGNED_64) {
long[] minArray = new long[size];
long[] maxArray = new long[size];
@@ -298,7 +288,6 @@
}
minAlloc.copyFrom(minArray);
maxAlloc.copyFrom(maxArray);
- */
} else if (dataType == Element.DataType.SIGNED_32) {
int[] minArray = new int[size];
int[] maxArray = new int[size];
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/TestClamp.java b/tests/tests/renderscript/src/android/renderscript/cts/TestClamp.java
index 8e11182..e635f15 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/TestClamp.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/TestClamp.java
@@ -2467,6 +2467,628 @@
}
}
+ public class ArgumentsLongLongLongLong {
+ public long inValue;
+ public long inMinValue;
+ public long inMaxValue;
+ public long out;
+ }
+
+ private void checkClampLongLongLongLong() {
+ Allocation inValue = createRandomAllocation(mRS, Element.DataType.SIGNED_64, 1, 0x63fd360531c9c41dl, false);
+ Allocation inMinValue = createRandomAllocation(mRS, Element.DataType.SIGNED_64, 1, 0x9d04d1824ef4907l, false);
+ Allocation inMaxValue = createRandomAllocation(mRS, Element.DataType.SIGNED_64, 1, 0x9d04d0cb64c3b0dl, false);
+ enforceOrdering(inMinValue, inMaxValue);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 1), INPUTSIZE);
+ script.set_gAllocInMinValue(inMinValue);
+ script.set_gAllocInMaxValue(inMaxValue);
+ script.forEach_testClampLongLongLongLong(inValue, out);
+ verifyResultsClampLongLongLongLong(inValue, inMinValue, inMaxValue, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testClampLongLongLongLong: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 1), INPUTSIZE);
+ scriptRelaxed.set_gAllocInMinValue(inMinValue);
+ scriptRelaxed.set_gAllocInMaxValue(inMaxValue);
+ scriptRelaxed.forEach_testClampLongLongLongLong(inValue, out);
+ verifyResultsClampLongLongLongLong(inValue, inMinValue, inMaxValue, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testClampLongLongLongLong: " + e.toString());
+ }
+ }
+
+ private void verifyResultsClampLongLongLongLong(Allocation inValue, Allocation inMinValue, Allocation inMaxValue, Allocation out, boolean relaxed) {
+ long[] arrayInValue = new long[INPUTSIZE * 1];
+ inValue.copyTo(arrayInValue);
+ long[] arrayInMinValue = new long[INPUTSIZE * 1];
+ inMinValue.copyTo(arrayInMinValue);
+ long[] arrayInMaxValue = new long[INPUTSIZE * 1];
+ inMaxValue.copyTo(arrayInMaxValue);
+ long[] arrayOut = new long[INPUTSIZE * 1];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 1 ; j++) {
+ // Extract the inputs.
+ ArgumentsLongLongLongLong args = new ArgumentsLongLongLongLong();
+ args.inValue = arrayInValue[i];
+ args.inMinValue = arrayInMinValue[i];
+ args.inMaxValue = arrayInMaxValue[i];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeClamp(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 1 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inValue: ");
+ message.append(String.format("%d", args.inValue));
+ message.append("\n");
+ message.append("Input inMinValue: ");
+ message.append(String.format("%d", args.inMinValue));
+ message.append("\n");
+ message.append("Input inMaxValue: ");
+ message.append(String.format("%d", args.inMaxValue));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 1 + j]));
+ if (args.out != arrayOut[i * 1 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkClampLongLongLongLong" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkClampLong2Long2Long2Long2() {
+ Allocation inValue = createRandomAllocation(mRS, Element.DataType.SIGNED_64, 2, 0xccbae869c2b0f12dl, false);
+ Allocation inMinValue = createRandomAllocation(mRS, Element.DataType.SIGNED_64, 2, 0xe4c3844f4a3f8937l, false);
+ Allocation inMaxValue = createRandomAllocation(mRS, Element.DataType.SIGNED_64, 2, 0xe4c38443db9c7b3dl, false);
+ enforceOrdering(inMinValue, inMaxValue);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 2), INPUTSIZE);
+ script.set_gAllocInMinValue(inMinValue);
+ script.set_gAllocInMaxValue(inMaxValue);
+ script.forEach_testClampLong2Long2Long2Long2(inValue, out);
+ verifyResultsClampLong2Long2Long2Long2(inValue, inMinValue, inMaxValue, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testClampLong2Long2Long2Long2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 2), INPUTSIZE);
+ scriptRelaxed.set_gAllocInMinValue(inMinValue);
+ scriptRelaxed.set_gAllocInMaxValue(inMaxValue);
+ scriptRelaxed.forEach_testClampLong2Long2Long2Long2(inValue, out);
+ verifyResultsClampLong2Long2Long2Long2(inValue, inMinValue, inMaxValue, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testClampLong2Long2Long2Long2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsClampLong2Long2Long2Long2(Allocation inValue, Allocation inMinValue, Allocation inMaxValue, Allocation out, boolean relaxed) {
+ long[] arrayInValue = new long[INPUTSIZE * 2];
+ inValue.copyTo(arrayInValue);
+ long[] arrayInMinValue = new long[INPUTSIZE * 2];
+ inMinValue.copyTo(arrayInMinValue);
+ long[] arrayInMaxValue = new long[INPUTSIZE * 2];
+ inMaxValue.copyTo(arrayInMaxValue);
+ long[] arrayOut = new long[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsLongLongLongLong args = new ArgumentsLongLongLongLong();
+ args.inValue = arrayInValue[i * 2 + j];
+ args.inMinValue = arrayInMinValue[i * 2 + j];
+ args.inMaxValue = arrayInMaxValue[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeClamp(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inValue: ");
+ message.append(String.format("%d", args.inValue));
+ message.append("\n");
+ message.append("Input inMinValue: ");
+ message.append(String.format("%d", args.inMinValue));
+ message.append("\n");
+ message.append("Input inMaxValue: ");
+ message.append(String.format("%d", args.inMaxValue));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkClampLong2Long2Long2Long2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkClampLong3Long3Long3Long3() {
+ Allocation inValue = createRandomAllocation(mRS, Element.DataType.SIGNED_64, 3, 0x2568063ab885ed75l, false);
+ Allocation inMinValue = createRandomAllocation(mRS, Element.DataType.SIGNED_64, 3, 0x7246acfc5d0b968fl, false);
+ Allocation inMaxValue = createRandomAllocation(mRS, Element.DataType.SIGNED_64, 3, 0x7246acf0ee688895l, false);
+ enforceOrdering(inMinValue, inMaxValue);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 3), INPUTSIZE);
+ script.set_gAllocInMinValue(inMinValue);
+ script.set_gAllocInMaxValue(inMaxValue);
+ script.forEach_testClampLong3Long3Long3Long3(inValue, out);
+ verifyResultsClampLong3Long3Long3Long3(inValue, inMinValue, inMaxValue, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testClampLong3Long3Long3Long3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 3), INPUTSIZE);
+ scriptRelaxed.set_gAllocInMinValue(inMinValue);
+ scriptRelaxed.set_gAllocInMaxValue(inMaxValue);
+ scriptRelaxed.forEach_testClampLong3Long3Long3Long3(inValue, out);
+ verifyResultsClampLong3Long3Long3Long3(inValue, inMinValue, inMaxValue, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testClampLong3Long3Long3Long3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsClampLong3Long3Long3Long3(Allocation inValue, Allocation inMinValue, Allocation inMaxValue, Allocation out, boolean relaxed) {
+ long[] arrayInValue = new long[INPUTSIZE * 4];
+ inValue.copyTo(arrayInValue);
+ long[] arrayInMinValue = new long[INPUTSIZE * 4];
+ inMinValue.copyTo(arrayInMinValue);
+ long[] arrayInMaxValue = new long[INPUTSIZE * 4];
+ inMaxValue.copyTo(arrayInMaxValue);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsLongLongLongLong args = new ArgumentsLongLongLongLong();
+ args.inValue = arrayInValue[i * 4 + j];
+ args.inMinValue = arrayInMinValue[i * 4 + j];
+ args.inMaxValue = arrayInMaxValue[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeClamp(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inValue: ");
+ message.append(String.format("%d", args.inValue));
+ message.append("\n");
+ message.append("Input inMinValue: ");
+ message.append(String.format("%d", args.inMinValue));
+ message.append("\n");
+ message.append("Input inMaxValue: ");
+ message.append(String.format("%d", args.inMaxValue));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkClampLong3Long3Long3Long3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkClampLong4Long4Long4Long4() {
+ Allocation inValue = createRandomAllocation(mRS, Element.DataType.SIGNED_64, 4, 0x7e15240bae5ae9bdl, false);
+ Allocation inMinValue = createRandomAllocation(mRS, Element.DataType.SIGNED_64, 4, 0xffc9d5a96fd7a3e7l, false);
+ Allocation inMaxValue = createRandomAllocation(mRS, Element.DataType.SIGNED_64, 4, 0xffc9d59e013495edl, false);
+ enforceOrdering(inMinValue, inMaxValue);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 4), INPUTSIZE);
+ script.set_gAllocInMinValue(inMinValue);
+ script.set_gAllocInMaxValue(inMaxValue);
+ script.forEach_testClampLong4Long4Long4Long4(inValue, out);
+ verifyResultsClampLong4Long4Long4Long4(inValue, inMinValue, inMaxValue, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testClampLong4Long4Long4Long4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 4), INPUTSIZE);
+ scriptRelaxed.set_gAllocInMinValue(inMinValue);
+ scriptRelaxed.set_gAllocInMaxValue(inMaxValue);
+ scriptRelaxed.forEach_testClampLong4Long4Long4Long4(inValue, out);
+ verifyResultsClampLong4Long4Long4Long4(inValue, inMinValue, inMaxValue, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testClampLong4Long4Long4Long4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsClampLong4Long4Long4Long4(Allocation inValue, Allocation inMinValue, Allocation inMaxValue, Allocation out, boolean relaxed) {
+ long[] arrayInValue = new long[INPUTSIZE * 4];
+ inValue.copyTo(arrayInValue);
+ long[] arrayInMinValue = new long[INPUTSIZE * 4];
+ inMinValue.copyTo(arrayInMinValue);
+ long[] arrayInMaxValue = new long[INPUTSIZE * 4];
+ inMaxValue.copyTo(arrayInMaxValue);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsLongLongLongLong args = new ArgumentsLongLongLongLong();
+ args.inValue = arrayInValue[i * 4 + j];
+ args.inMinValue = arrayInMinValue[i * 4 + j];
+ args.inMaxValue = arrayInMaxValue[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeClamp(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inValue: ");
+ message.append(String.format("%d", args.inValue));
+ message.append("\n");
+ message.append("Input inMinValue: ");
+ message.append(String.format("%d", args.inMinValue));
+ message.append("\n");
+ message.append("Input inMaxValue: ");
+ message.append(String.format("%d", args.inMaxValue));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkClampLong4Long4Long4Long4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsUlongUlongUlongUlong {
+ public long inValue;
+ public long inMinValue;
+ public long inMaxValue;
+ public long out;
+ }
+
+ private void checkClampUlongUlongUlongUlong() {
+ Allocation inValue = createRandomAllocation(mRS, Element.DataType.UNSIGNED_64, 1, 0x2b378139749bf4c5l, false);
+ Allocation inMinValue = createRandomAllocation(mRS, Element.DataType.UNSIGNED_64, 1, 0x75ac5050a8ca97fl, false);
+ Allocation inMaxValue = createRandomAllocation(mRS, Element.DataType.UNSIGNED_64, 1, 0x75ac4f99be99b85l, false);
+ enforceOrdering(inMinValue, inMaxValue);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 1), INPUTSIZE);
+ script.set_gAllocInMinValue(inMinValue);
+ script.set_gAllocInMaxValue(inMaxValue);
+ script.forEach_testClampUlongUlongUlongUlong(inValue, out);
+ verifyResultsClampUlongUlongUlongUlong(inValue, inMinValue, inMaxValue, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testClampUlongUlongUlongUlong: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 1), INPUTSIZE);
+ scriptRelaxed.set_gAllocInMinValue(inMinValue);
+ scriptRelaxed.set_gAllocInMaxValue(inMaxValue);
+ scriptRelaxed.forEach_testClampUlongUlongUlongUlong(inValue, out);
+ verifyResultsClampUlongUlongUlongUlong(inValue, inMinValue, inMaxValue, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testClampUlongUlongUlongUlong: " + e.toString());
+ }
+ }
+
+ private void verifyResultsClampUlongUlongUlongUlong(Allocation inValue, Allocation inMinValue, Allocation inMaxValue, Allocation out, boolean relaxed) {
+ long[] arrayInValue = new long[INPUTSIZE * 1];
+ inValue.copyTo(arrayInValue);
+ long[] arrayInMinValue = new long[INPUTSIZE * 1];
+ inMinValue.copyTo(arrayInMinValue);
+ long[] arrayInMaxValue = new long[INPUTSIZE * 1];
+ inMaxValue.copyTo(arrayInMaxValue);
+ long[] arrayOut = new long[INPUTSIZE * 1];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 1 ; j++) {
+ // Extract the inputs.
+ ArgumentsUlongUlongUlongUlong args = new ArgumentsUlongUlongUlongUlong();
+ args.inValue = arrayInValue[i];
+ args.inMinValue = arrayInMinValue[i];
+ args.inMaxValue = arrayInMaxValue[i];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeClamp(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 1 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inValue: ");
+ message.append(String.format("0x%x", args.inValue));
+ message.append("\n");
+ message.append("Input inMinValue: ");
+ message.append(String.format("0x%x", args.inMinValue));
+ message.append("\n");
+ message.append("Input inMaxValue: ");
+ message.append(String.format("0x%x", args.inMaxValue));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 1 + j]));
+ if (args.out != arrayOut[i * 1 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkClampUlongUlongUlongUlong" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkClampUlong2Ulong2Ulong2Ulong2() {
+ Allocation inValue = createRandomAllocation(mRS, Element.DataType.UNSIGNED_64, 2, 0xa8c7fb17a09bb299l, false);
+ Allocation inMinValue = createRandomAllocation(mRS, Element.DataType.UNSIGNED_64, 2, 0x14e3c8dffe45623bl, false);
+ Allocation inMaxValue = createRandomAllocation(mRS, Element.DataType.UNSIGNED_64, 2, 0x14e3c8d48fa25441l, false);
+ enforceOrdering(inMinValue, inMaxValue);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 2), INPUTSIZE);
+ script.set_gAllocInMinValue(inMinValue);
+ script.set_gAllocInMaxValue(inMaxValue);
+ script.forEach_testClampUlong2Ulong2Ulong2Ulong2(inValue, out);
+ verifyResultsClampUlong2Ulong2Ulong2Ulong2(inValue, inMinValue, inMaxValue, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testClampUlong2Ulong2Ulong2Ulong2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 2), INPUTSIZE);
+ scriptRelaxed.set_gAllocInMinValue(inMinValue);
+ scriptRelaxed.set_gAllocInMaxValue(inMaxValue);
+ scriptRelaxed.forEach_testClampUlong2Ulong2Ulong2Ulong2(inValue, out);
+ verifyResultsClampUlong2Ulong2Ulong2Ulong2(inValue, inMinValue, inMaxValue, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testClampUlong2Ulong2Ulong2Ulong2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsClampUlong2Ulong2Ulong2Ulong2(Allocation inValue, Allocation inMinValue, Allocation inMaxValue, Allocation out, boolean relaxed) {
+ long[] arrayInValue = new long[INPUTSIZE * 2];
+ inValue.copyTo(arrayInValue);
+ long[] arrayInMinValue = new long[INPUTSIZE * 2];
+ inMinValue.copyTo(arrayInMinValue);
+ long[] arrayInMaxValue = new long[INPUTSIZE * 2];
+ inMaxValue.copyTo(arrayInMaxValue);
+ long[] arrayOut = new long[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsUlongUlongUlongUlong args = new ArgumentsUlongUlongUlongUlong();
+ args.inValue = arrayInValue[i * 2 + j];
+ args.inMinValue = arrayInMinValue[i * 2 + j];
+ args.inMaxValue = arrayInMaxValue[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeClamp(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inValue: ");
+ message.append(String.format("0x%x", args.inValue));
+ message.append("\n");
+ message.append("Input inMinValue: ");
+ message.append(String.format("0x%x", args.inMinValue));
+ message.append("\n");
+ message.append("Input inMaxValue: ");
+ message.append(String.format("0x%x", args.inMaxValue));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkClampUlong2Ulong2Ulong2Ulong2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkClampUlong3Ulong3Ulong3Ulong3() {
+ Allocation inValue = createRandomAllocation(mRS, Element.DataType.UNSIGNED_64, 3, 0x722c3c9fbd1e1f75l, false);
+ Allocation inMinValue = createRandomAllocation(mRS, Element.DataType.UNSIGNED_64, 3, 0x20d03c8e4cfc4c8fl, false);
+ Allocation inMaxValue = createRandomAllocation(mRS, Element.DataType.UNSIGNED_64, 3, 0x20d03c82de593e95l, false);
+ enforceOrdering(inMinValue, inMaxValue);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 3), INPUTSIZE);
+ script.set_gAllocInMinValue(inMinValue);
+ script.set_gAllocInMaxValue(inMaxValue);
+ script.forEach_testClampUlong3Ulong3Ulong3Ulong3(inValue, out);
+ verifyResultsClampUlong3Ulong3Ulong3Ulong3(inValue, inMinValue, inMaxValue, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testClampUlong3Ulong3Ulong3Ulong3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 3), INPUTSIZE);
+ scriptRelaxed.set_gAllocInMinValue(inMinValue);
+ scriptRelaxed.set_gAllocInMaxValue(inMaxValue);
+ scriptRelaxed.forEach_testClampUlong3Ulong3Ulong3Ulong3(inValue, out);
+ verifyResultsClampUlong3Ulong3Ulong3Ulong3(inValue, inMinValue, inMaxValue, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testClampUlong3Ulong3Ulong3Ulong3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsClampUlong3Ulong3Ulong3Ulong3(Allocation inValue, Allocation inMinValue, Allocation inMaxValue, Allocation out, boolean relaxed) {
+ long[] arrayInValue = new long[INPUTSIZE * 4];
+ inValue.copyTo(arrayInValue);
+ long[] arrayInMinValue = new long[INPUTSIZE * 4];
+ inMinValue.copyTo(arrayInMinValue);
+ long[] arrayInMaxValue = new long[INPUTSIZE * 4];
+ inMaxValue.copyTo(arrayInMaxValue);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsUlongUlongUlongUlong args = new ArgumentsUlongUlongUlongUlong();
+ args.inValue = arrayInValue[i * 4 + j];
+ args.inMinValue = arrayInMinValue[i * 4 + j];
+ args.inMaxValue = arrayInMaxValue[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeClamp(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inValue: ");
+ message.append(String.format("0x%x", args.inValue));
+ message.append("\n");
+ message.append("Input inMinValue: ");
+ message.append(String.format("0x%x", args.inMinValue));
+ message.append("\n");
+ message.append("Input inMaxValue: ");
+ message.append(String.format("0x%x", args.inMaxValue));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkClampUlong3Ulong3Ulong3Ulong3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkClampUlong4Ulong4Ulong4Ulong4() {
+ Allocation inValue = createRandomAllocation(mRS, Element.DataType.UNSIGNED_64, 4, 0x3b907e27d9a08c51l, false);
+ Allocation inMinValue = createRandomAllocation(mRS, Element.DataType.UNSIGNED_64, 4, 0x2cbcb03c9bb336e3l, false);
+ Allocation inMaxValue = createRandomAllocation(mRS, Element.DataType.UNSIGNED_64, 4, 0x2cbcb0312d1028e9l, false);
+ enforceOrdering(inMinValue, inMaxValue);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 4), INPUTSIZE);
+ script.set_gAllocInMinValue(inMinValue);
+ script.set_gAllocInMaxValue(inMaxValue);
+ script.forEach_testClampUlong4Ulong4Ulong4Ulong4(inValue, out);
+ verifyResultsClampUlong4Ulong4Ulong4Ulong4(inValue, inMinValue, inMaxValue, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testClampUlong4Ulong4Ulong4Ulong4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 4), INPUTSIZE);
+ scriptRelaxed.set_gAllocInMinValue(inMinValue);
+ scriptRelaxed.set_gAllocInMaxValue(inMaxValue);
+ scriptRelaxed.forEach_testClampUlong4Ulong4Ulong4Ulong4(inValue, out);
+ verifyResultsClampUlong4Ulong4Ulong4Ulong4(inValue, inMinValue, inMaxValue, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testClampUlong4Ulong4Ulong4Ulong4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsClampUlong4Ulong4Ulong4Ulong4(Allocation inValue, Allocation inMinValue, Allocation inMaxValue, Allocation out, boolean relaxed) {
+ long[] arrayInValue = new long[INPUTSIZE * 4];
+ inValue.copyTo(arrayInValue);
+ long[] arrayInMinValue = new long[INPUTSIZE * 4];
+ inMinValue.copyTo(arrayInMinValue);
+ long[] arrayInMaxValue = new long[INPUTSIZE * 4];
+ inMaxValue.copyTo(arrayInMaxValue);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsUlongUlongUlongUlong args = new ArgumentsUlongUlongUlongUlong();
+ args.inValue = arrayInValue[i * 4 + j];
+ args.inMinValue = arrayInMinValue[i * 4 + j];
+ args.inMaxValue = arrayInMaxValue[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeClamp(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inValue: ");
+ message.append(String.format("0x%x", args.inValue));
+ message.append("\n");
+ message.append("Input inMinValue: ");
+ message.append(String.format("0x%x", args.inMinValue));
+ message.append("\n");
+ message.append("Input inMaxValue: ");
+ message.append(String.format("0x%x", args.inMaxValue));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkClampUlong4Ulong4Ulong4Ulong4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
private void checkClampChar2CharCharChar2() {
Allocation inValue = createRandomAllocation(mRS, Element.DataType.SIGNED_8, 2, 0xd6884bbb7c57a5d1l, false);
Allocation inMinValue = createRandomAllocation(mRS, Element.DataType.SIGNED_8, 1, 0x3bf8830cc3b7db63l, false);
@@ -3835,6 +4457,462 @@
}
}
+ private void checkClampLong2LongLongLong2() {
+ Allocation inValue = createRandomAllocation(mRS, Element.DataType.SIGNED_64, 2, 0x19353a9f7c535bb5l, false);
+ Allocation inMinValue = createRandomAllocation(mRS, Element.DataType.SIGNED_64, 1, 0xee8dc7f38f83654fl, false);
+ Allocation inMaxValue = createRandomAllocation(mRS, Element.DataType.SIGNED_64, 1, 0xee8dc7e820e05755l, false);
+ enforceOrdering(inMinValue, inMaxValue);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 2), INPUTSIZE);
+ script.set_gAllocInMinValue(inMinValue);
+ script.set_gAllocInMaxValue(inMaxValue);
+ script.forEach_testClampLong2LongLongLong2(inValue, out);
+ verifyResultsClampLong2LongLongLong2(inValue, inMinValue, inMaxValue, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testClampLong2LongLongLong2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 2), INPUTSIZE);
+ scriptRelaxed.set_gAllocInMinValue(inMinValue);
+ scriptRelaxed.set_gAllocInMaxValue(inMaxValue);
+ scriptRelaxed.forEach_testClampLong2LongLongLong2(inValue, out);
+ verifyResultsClampLong2LongLongLong2(inValue, inMinValue, inMaxValue, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testClampLong2LongLongLong2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsClampLong2LongLongLong2(Allocation inValue, Allocation inMinValue, Allocation inMaxValue, Allocation out, boolean relaxed) {
+ long[] arrayInValue = new long[INPUTSIZE * 2];
+ inValue.copyTo(arrayInValue);
+ long[] arrayInMinValue = new long[INPUTSIZE * 1];
+ inMinValue.copyTo(arrayInMinValue);
+ long[] arrayInMaxValue = new long[INPUTSIZE * 1];
+ inMaxValue.copyTo(arrayInMaxValue);
+ long[] arrayOut = new long[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsLongLongLongLong args = new ArgumentsLongLongLongLong();
+ args.inValue = arrayInValue[i * 2 + j];
+ args.inMinValue = arrayInMinValue[i];
+ args.inMaxValue = arrayInMaxValue[i];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeClamp(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inValue: ");
+ message.append(String.format("%d", args.inValue));
+ message.append("\n");
+ message.append("Input inMinValue: ");
+ message.append(String.format("%d", args.inMinValue));
+ message.append("\n");
+ message.append("Input inMaxValue: ");
+ message.append(String.format("%d", args.inMaxValue));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkClampLong2LongLongLong2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkClampLong3LongLongLong3() {
+ Allocation inValue = createRandomAllocation(mRS, Element.DataType.SIGNED_64, 3, 0x8d537aff659e24c9l, false);
+ Allocation inMinValue = createRandomAllocation(mRS, Element.DataType.SIGNED_64, 1, 0x3de0e38714f313cbl, false);
+ Allocation inMaxValue = createRandomAllocation(mRS, Element.DataType.SIGNED_64, 1, 0x3de0e37ba65005d1l, false);
+ enforceOrdering(inMinValue, inMaxValue);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 3), INPUTSIZE);
+ script.set_gAllocInMinValue(inMinValue);
+ script.set_gAllocInMaxValue(inMaxValue);
+ script.forEach_testClampLong3LongLongLong3(inValue, out);
+ verifyResultsClampLong3LongLongLong3(inValue, inMinValue, inMaxValue, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testClampLong3LongLongLong3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 3), INPUTSIZE);
+ scriptRelaxed.set_gAllocInMinValue(inMinValue);
+ scriptRelaxed.set_gAllocInMaxValue(inMaxValue);
+ scriptRelaxed.forEach_testClampLong3LongLongLong3(inValue, out);
+ verifyResultsClampLong3LongLongLong3(inValue, inMinValue, inMaxValue, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testClampLong3LongLongLong3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsClampLong3LongLongLong3(Allocation inValue, Allocation inMinValue, Allocation inMaxValue, Allocation out, boolean relaxed) {
+ long[] arrayInValue = new long[INPUTSIZE * 4];
+ inValue.copyTo(arrayInValue);
+ long[] arrayInMinValue = new long[INPUTSIZE * 1];
+ inMinValue.copyTo(arrayInMinValue);
+ long[] arrayInMaxValue = new long[INPUTSIZE * 1];
+ inMaxValue.copyTo(arrayInMaxValue);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsLongLongLongLong args = new ArgumentsLongLongLongLong();
+ args.inValue = arrayInValue[i * 4 + j];
+ args.inMinValue = arrayInMinValue[i];
+ args.inMaxValue = arrayInMaxValue[i];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeClamp(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inValue: ");
+ message.append(String.format("%d", args.inValue));
+ message.append("\n");
+ message.append("Input inMinValue: ");
+ message.append(String.format("%d", args.inMinValue));
+ message.append("\n");
+ message.append("Input inMaxValue: ");
+ message.append(String.format("%d", args.inMaxValue));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkClampLong3LongLongLong3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkClampLong4LongLongLong4() {
+ Allocation inValue = createRandomAllocation(mRS, Element.DataType.SIGNED_64, 4, 0x171bb5f4ee8edddl, false);
+ Allocation inMinValue = createRandomAllocation(mRS, Element.DataType.SIGNED_64, 1, 0x8d33ff1a9a62c247l, false);
+ Allocation inMaxValue = createRandomAllocation(mRS, Element.DataType.SIGNED_64, 1, 0x8d33ff0f2bbfb44dl, false);
+ enforceOrdering(inMinValue, inMaxValue);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 4), INPUTSIZE);
+ script.set_gAllocInMinValue(inMinValue);
+ script.set_gAllocInMaxValue(inMaxValue);
+ script.forEach_testClampLong4LongLongLong4(inValue, out);
+ verifyResultsClampLong4LongLongLong4(inValue, inMinValue, inMaxValue, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testClampLong4LongLongLong4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 4), INPUTSIZE);
+ scriptRelaxed.set_gAllocInMinValue(inMinValue);
+ scriptRelaxed.set_gAllocInMaxValue(inMaxValue);
+ scriptRelaxed.forEach_testClampLong4LongLongLong4(inValue, out);
+ verifyResultsClampLong4LongLongLong4(inValue, inMinValue, inMaxValue, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testClampLong4LongLongLong4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsClampLong4LongLongLong4(Allocation inValue, Allocation inMinValue, Allocation inMaxValue, Allocation out, boolean relaxed) {
+ long[] arrayInValue = new long[INPUTSIZE * 4];
+ inValue.copyTo(arrayInValue);
+ long[] arrayInMinValue = new long[INPUTSIZE * 1];
+ inMinValue.copyTo(arrayInMinValue);
+ long[] arrayInMaxValue = new long[INPUTSIZE * 1];
+ inMaxValue.copyTo(arrayInMaxValue);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsLongLongLongLong args = new ArgumentsLongLongLongLong();
+ args.inValue = arrayInValue[i * 4 + j];
+ args.inMinValue = arrayInMinValue[i];
+ args.inMaxValue = arrayInMaxValue[i];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeClamp(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inValue: ");
+ message.append(String.format("%d", args.inValue));
+ message.append("\n");
+ message.append("Input inMinValue: ");
+ message.append(String.format("%d", args.inMinValue));
+ message.append("\n");
+ message.append("Input inMaxValue: ");
+ message.append(String.format("%d", args.inMaxValue));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkClampLong4LongLongLong4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkClampUlong2UlongUlongUlong2() {
+ Allocation inValue = createRandomAllocation(mRS, Element.DataType.UNSIGNED_64, 2, 0xf275dabaa7fa1bf7l, false);
+ Allocation inMinValue = createRandomAllocation(mRS, Element.DataType.UNSIGNED_64, 1, 0xf08a9e698d13b735l, false);
+ Allocation inMaxValue = createRandomAllocation(mRS, Element.DataType.UNSIGNED_64, 1, 0xf08a9e5e1e70a93bl, false);
+ enforceOrdering(inMinValue, inMaxValue);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 2), INPUTSIZE);
+ script.set_gAllocInMinValue(inMinValue);
+ script.set_gAllocInMaxValue(inMaxValue);
+ script.forEach_testClampUlong2UlongUlongUlong2(inValue, out);
+ verifyResultsClampUlong2UlongUlongUlong2(inValue, inMinValue, inMaxValue, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testClampUlong2UlongUlongUlong2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 2), INPUTSIZE);
+ scriptRelaxed.set_gAllocInMinValue(inMinValue);
+ scriptRelaxed.set_gAllocInMaxValue(inMaxValue);
+ scriptRelaxed.forEach_testClampUlong2UlongUlongUlong2(inValue, out);
+ verifyResultsClampUlong2UlongUlongUlong2(inValue, inMinValue, inMaxValue, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testClampUlong2UlongUlongUlong2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsClampUlong2UlongUlongUlong2(Allocation inValue, Allocation inMinValue, Allocation inMaxValue, Allocation out, boolean relaxed) {
+ long[] arrayInValue = new long[INPUTSIZE * 2];
+ inValue.copyTo(arrayInValue);
+ long[] arrayInMinValue = new long[INPUTSIZE * 1];
+ inMinValue.copyTo(arrayInMinValue);
+ long[] arrayInMaxValue = new long[INPUTSIZE * 1];
+ inMaxValue.copyTo(arrayInMaxValue);
+ long[] arrayOut = new long[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsUlongUlongUlongUlong args = new ArgumentsUlongUlongUlongUlong();
+ args.inValue = arrayInValue[i * 2 + j];
+ args.inMinValue = arrayInMinValue[i];
+ args.inMaxValue = arrayInMaxValue[i];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeClamp(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inValue: ");
+ message.append(String.format("0x%x", args.inValue));
+ message.append("\n");
+ message.append("Input inMinValue: ");
+ message.append(String.format("0x%x", args.inMinValue));
+ message.append("\n");
+ message.append("Input inMaxValue: ");
+ message.append(String.format("0x%x", args.inMaxValue));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkClampUlong2UlongUlongUlong2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkClampUlong3UlongUlongUlong3() {
+ Allocation inValue = createRandomAllocation(mRS, Element.DataType.UNSIGNED_64, 3, 0x417c2f0620b6497dl, false);
+ Allocation inMinValue = createRandomAllocation(mRS, Element.DataType.UNSIGNED_64, 1, 0xfb4069f3c0421f27l, false);
+ Allocation inMaxValue = createRandomAllocation(mRS, Element.DataType.UNSIGNED_64, 1, 0xfb4069e8519f112dl, false);
+ enforceOrdering(inMinValue, inMaxValue);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 3), INPUTSIZE);
+ script.set_gAllocInMinValue(inMinValue);
+ script.set_gAllocInMaxValue(inMaxValue);
+ script.forEach_testClampUlong3UlongUlongUlong3(inValue, out);
+ verifyResultsClampUlong3UlongUlongUlong3(inValue, inMinValue, inMaxValue, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testClampUlong3UlongUlongUlong3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 3), INPUTSIZE);
+ scriptRelaxed.set_gAllocInMinValue(inMinValue);
+ scriptRelaxed.set_gAllocInMaxValue(inMaxValue);
+ scriptRelaxed.forEach_testClampUlong3UlongUlongUlong3(inValue, out);
+ verifyResultsClampUlong3UlongUlongUlong3(inValue, inMinValue, inMaxValue, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testClampUlong3UlongUlongUlong3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsClampUlong3UlongUlongUlong3(Allocation inValue, Allocation inMinValue, Allocation inMaxValue, Allocation out, boolean relaxed) {
+ long[] arrayInValue = new long[INPUTSIZE * 4];
+ inValue.copyTo(arrayInValue);
+ long[] arrayInMinValue = new long[INPUTSIZE * 1];
+ inMinValue.copyTo(arrayInMinValue);
+ long[] arrayInMaxValue = new long[INPUTSIZE * 1];
+ inMaxValue.copyTo(arrayInMaxValue);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsUlongUlongUlongUlong args = new ArgumentsUlongUlongUlongUlong();
+ args.inValue = arrayInValue[i * 4 + j];
+ args.inMinValue = arrayInMinValue[i];
+ args.inMaxValue = arrayInMaxValue[i];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeClamp(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inValue: ");
+ message.append(String.format("0x%x", args.inValue));
+ message.append("\n");
+ message.append("Input inMinValue: ");
+ message.append(String.format("0x%x", args.inMinValue));
+ message.append("\n");
+ message.append("Input inMaxValue: ");
+ message.append(String.format("0x%x", args.inMaxValue));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkClampUlong3UlongUlongUlong3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkClampUlong4UlongUlongUlong4() {
+ Allocation inValue = createRandomAllocation(mRS, Element.DataType.UNSIGNED_64, 4, 0x9082835199727703l, false);
+ Allocation inMinValue = createRandomAllocation(mRS, Element.DataType.UNSIGNED_64, 1, 0x5f6357df3708719l, false);
+ Allocation inMaxValue = createRandomAllocation(mRS, Element.DataType.UNSIGNED_64, 1, 0x5f6357284cd791fl, false);
+ enforceOrdering(inMinValue, inMaxValue);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 4), INPUTSIZE);
+ script.set_gAllocInMinValue(inMinValue);
+ script.set_gAllocInMaxValue(inMaxValue);
+ script.forEach_testClampUlong4UlongUlongUlong4(inValue, out);
+ verifyResultsClampUlong4UlongUlongUlong4(inValue, inMinValue, inMaxValue, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testClampUlong4UlongUlongUlong4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 4), INPUTSIZE);
+ scriptRelaxed.set_gAllocInMinValue(inMinValue);
+ scriptRelaxed.set_gAllocInMaxValue(inMaxValue);
+ scriptRelaxed.forEach_testClampUlong4UlongUlongUlong4(inValue, out);
+ verifyResultsClampUlong4UlongUlongUlong4(inValue, inMinValue, inMaxValue, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testClampUlong4UlongUlongUlong4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsClampUlong4UlongUlongUlong4(Allocation inValue, Allocation inMinValue, Allocation inMaxValue, Allocation out, boolean relaxed) {
+ long[] arrayInValue = new long[INPUTSIZE * 4];
+ inValue.copyTo(arrayInValue);
+ long[] arrayInMinValue = new long[INPUTSIZE * 1];
+ inMinValue.copyTo(arrayInMinValue);
+ long[] arrayInMaxValue = new long[INPUTSIZE * 1];
+ inMaxValue.copyTo(arrayInMaxValue);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsUlongUlongUlongUlong args = new ArgumentsUlongUlongUlongUlong();
+ args.inValue = arrayInValue[i * 4 + j];
+ args.inMinValue = arrayInMinValue[i];
+ args.inMaxValue = arrayInMaxValue[i];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeClamp(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inValue: ");
+ message.append(String.format("0x%x", args.inValue));
+ message.append("\n");
+ message.append("Input inMinValue: ");
+ message.append(String.format("0x%x", args.inMinValue));
+ message.append("\n");
+ message.append("Input inMaxValue: ");
+ message.append(String.format("0x%x", args.inMaxValue));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkClampUlong4UlongUlongUlong4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
public void testClamp() {
checkClampFloatFloatFloatFloat();
checkClampFloat2Float2Float2Float2();
@@ -3867,6 +4945,14 @@
checkClampUint2Uint2Uint2Uint2();
checkClampUint3Uint3Uint3Uint3();
checkClampUint4Uint4Uint4Uint4();
+ checkClampLongLongLongLong();
+ checkClampLong2Long2Long2Long2();
+ checkClampLong3Long3Long3Long3();
+ checkClampLong4Long4Long4Long4();
+ checkClampUlongUlongUlongUlong();
+ checkClampUlong2Ulong2Ulong2Ulong2();
+ checkClampUlong3Ulong3Ulong3Ulong3();
+ checkClampUlong4Ulong4Ulong4Ulong4();
checkClampChar2CharCharChar2();
checkClampChar3CharCharChar3();
checkClampChar4CharCharChar4();
@@ -3885,5 +4971,11 @@
checkClampUint2UintUintUint2();
checkClampUint3UintUintUint3();
checkClampUint4UintUintUint4();
+ checkClampLong2LongLongLong2();
+ checkClampLong3LongLongLong3();
+ checkClampLong4LongLongLong4();
+ checkClampUlong2UlongUlongUlong2();
+ checkClampUlong3UlongUlongUlong3();
+ checkClampUlong4UlongUlongUlong4();
}
}
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/TestConvert.java b/tests/tests/renderscript/src/android/renderscript/cts/TestConvert.java
index eae020d..80a3c3f 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/TestConvert.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/TestConvert.java
@@ -8700,6 +8700,9060 @@
}
}
+ public class ArgumentsDoubleDouble {
+ public double inV;
+ public Floaty out;
+ }
+
+ private void checkConvertDouble2Double2() {
+ Allocation inV = createRandomFloatAllocation(mRS, Element.DataType.FLOAT_64, 2, 0x345b4a823902786el, -8.5390423905960001625e+307, 8.5390423905960001625e+307);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 2), INPUTSIZE);
+ script.forEach_testConvertDouble2Double2Double2(inV, out);
+ verifyResultsConvertDouble2Double2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble2Double2Double2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertDouble2Double2Double2(inV, out);
+ verifyResultsConvertDouble2Double2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble2Double2Double2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertDouble2Double2(Allocation inV, Allocation out, boolean relaxed) {
+ double[] arrayInV = new double[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ double[] arrayOut = new double[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsDoubleDouble args = new ArgumentsDoubleDouble();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (!args.out.couldBe(arrayOut[i * 2 + j])) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%24.8g %16x %31a",
+ args.inV, Double.doubleToRawLongBits(args.inV), args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(args.out.toString());
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%24.8g %16x %31a",
+ arrayOut[i * 2 + j], Double.doubleToRawLongBits(arrayOut[i * 2 + j]), arrayOut[i * 2 + j]));
+ if (!args.out.couldBe(arrayOut[i * 2 + j])) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertDouble2Double2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertDouble3Double3() {
+ Allocation inV = createRandomFloatAllocation(mRS, Element.DataType.FLOAT_64, 3, 0x34a812098f5e099al, -8.5390423905960001625e+307, 8.5390423905960001625e+307);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 3), INPUTSIZE);
+ script.forEach_testConvertDouble3Double3Double3(inV, out);
+ verifyResultsConvertDouble3Double3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble3Double3Double3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertDouble3Double3Double3(inV, out);
+ verifyResultsConvertDouble3Double3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble3Double3Double3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertDouble3Double3(Allocation inV, Allocation out, boolean relaxed) {
+ double[] arrayInV = new double[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ double[] arrayOut = new double[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsDoubleDouble args = new ArgumentsDoubleDouble();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%24.8g %16x %31a",
+ args.inV, Double.doubleToRawLongBits(args.inV), args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(args.out.toString());
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%24.8g %16x %31a",
+ arrayOut[i * 4 + j], Double.doubleToRawLongBits(arrayOut[i * 4 + j]), arrayOut[i * 4 + j]));
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertDouble3Double3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertDouble4Double4() {
+ Allocation inV = createRandomFloatAllocation(mRS, Element.DataType.FLOAT_64, 4, 0x34f4d990e5b99ac6l, -8.5390423905960001625e+307, 8.5390423905960001625e+307);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 4), INPUTSIZE);
+ script.forEach_testConvertDouble4Double4Double4(inV, out);
+ verifyResultsConvertDouble4Double4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble4Double4Double4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertDouble4Double4Double4(inV, out);
+ verifyResultsConvertDouble4Double4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble4Double4Double4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertDouble4Double4(Allocation inV, Allocation out, boolean relaxed) {
+ double[] arrayInV = new double[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ double[] arrayOut = new double[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsDoubleDouble args = new ArgumentsDoubleDouble();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%24.8g %16x %31a",
+ args.inV, Double.doubleToRawLongBits(args.inV), args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(args.out.toString());
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%24.8g %16x %31a",
+ arrayOut[i * 4 + j], Double.doubleToRawLongBits(arrayOut[i * 4 + j]), arrayOut[i * 4 + j]));
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertDouble4Double4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsLongDouble {
+ public long inV;
+ public Floaty out;
+ }
+
+ private void checkConvertLong2Double2() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_64, 2, 0x7b7807124c70299bl, true, 63);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 2), INPUTSIZE);
+ script.forEach_testConvertDouble2Long2Double2(inV, out);
+ verifyResultsConvertLong2Double2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble2Long2Double2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertDouble2Long2Double2(inV, out);
+ verifyResultsConvertLong2Double2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble2Long2Double2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertLong2Double2(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ double[] arrayOut = new double[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsLongDouble args = new ArgumentsLongDouble();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (!args.out.couldBe(arrayOut[i * 2 + j])) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(args.out.toString());
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%24.8g %16x %31a",
+ arrayOut[i * 2 + j], Double.doubleToRawLongBits(arrayOut[i * 2 + j]), arrayOut[i * 2 + j]));
+ if (!args.out.couldBe(arrayOut[i * 2 + j])) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertLong2Double2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertLong3Double3() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_64, 3, 0x7bc4ce99a2cbbac7l, true, 63);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 3), INPUTSIZE);
+ script.forEach_testConvertDouble3Long3Double3(inV, out);
+ verifyResultsConvertLong3Double3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble3Long3Double3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertDouble3Long3Double3(inV, out);
+ verifyResultsConvertLong3Double3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble3Long3Double3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertLong3Double3(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ double[] arrayOut = new double[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsLongDouble args = new ArgumentsLongDouble();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(args.out.toString());
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%24.8g %16x %31a",
+ arrayOut[i * 4 + j], Double.doubleToRawLongBits(arrayOut[i * 4 + j]), arrayOut[i * 4 + j]));
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertLong3Double3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertLong4Double4() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_64, 4, 0x7c119620f9274bf3l, true, 63);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 4), INPUTSIZE);
+ script.forEach_testConvertDouble4Long4Double4(inV, out);
+ verifyResultsConvertLong4Double4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble4Long4Double4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertDouble4Long4Double4(inV, out);
+ verifyResultsConvertLong4Double4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble4Long4Double4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertLong4Double4(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ double[] arrayOut = new double[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsLongDouble args = new ArgumentsLongDouble();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(args.out.toString());
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%24.8g %16x %31a",
+ arrayOut[i * 4 + j], Double.doubleToRawLongBits(arrayOut[i * 4 + j]), arrayOut[i * 4 + j]));
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertLong4Double4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsUlongDouble {
+ public long inV;
+ public Floaty out;
+ }
+
+ private void checkConvertUlong2Double2() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_64, 2, 0xaa17685979bc7954l, false, 64);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 2), INPUTSIZE);
+ script.forEach_testConvertDouble2Ulong2Double2(inV, out);
+ verifyResultsConvertUlong2Double2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble2Ulong2Double2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertDouble2Ulong2Double2(inV, out);
+ verifyResultsConvertUlong2Double2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble2Ulong2Double2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUlong2Double2(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ double[] arrayOut = new double[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsUlongDouble args = new ArgumentsUlongDouble();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (!args.out.couldBe(arrayOut[i * 2 + j])) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(args.out.toString());
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%24.8g %16x %31a",
+ arrayOut[i * 2 + j], Double.doubleToRawLongBits(arrayOut[i * 2 + j]), arrayOut[i * 2 + j]));
+ if (!args.out.couldBe(arrayOut[i * 2 + j])) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUlong2Double2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertUlong3Double3() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_64, 3, 0xaa642fe0d0180a80l, false, 64);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 3), INPUTSIZE);
+ script.forEach_testConvertDouble3Ulong3Double3(inV, out);
+ verifyResultsConvertUlong3Double3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble3Ulong3Double3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertDouble3Ulong3Double3(inV, out);
+ verifyResultsConvertUlong3Double3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble3Ulong3Double3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUlong3Double3(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ double[] arrayOut = new double[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsUlongDouble args = new ArgumentsUlongDouble();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(args.out.toString());
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%24.8g %16x %31a",
+ arrayOut[i * 4 + j], Double.doubleToRawLongBits(arrayOut[i * 4 + j]), arrayOut[i * 4 + j]));
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUlong3Double3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertUlong4Double4() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_64, 4, 0xaab0f76826739bacl, false, 64);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 4), INPUTSIZE);
+ script.forEach_testConvertDouble4Ulong4Double4(inV, out);
+ verifyResultsConvertUlong4Double4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble4Ulong4Double4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertDouble4Ulong4Double4(inV, out);
+ verifyResultsConvertUlong4Double4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble4Ulong4Double4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUlong4Double4(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ double[] arrayOut = new double[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsUlongDouble args = new ArgumentsUlongDouble();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(args.out.toString());
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%24.8g %16x %31a",
+ arrayOut[i * 4 + j], Double.doubleToRawLongBits(arrayOut[i * 4 + j]), arrayOut[i * 4 + j]));
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUlong4Double4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsDoubleLong {
+ public double inV;
+ public long out;
+ }
+
+ private void checkConvertDouble2Long2() {
+ Allocation inV = createRandomFloatAllocation(mRS, Element.DataType.FLOAT_64, 2, 0xcbf84dc0430cbe95l, -9.2233720368547747840e+18, 9.2233720368547747840e+18);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 2), INPUTSIZE);
+ script.forEach_testConvertLong2Double2Long2(inV, out);
+ verifyResultsConvertDouble2Long2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong2Double2Long2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertLong2Double2Long2(inV, out);
+ verifyResultsConvertDouble2Long2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong2Double2Long2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertDouble2Long2(Allocation inV, Allocation out, boolean relaxed) {
+ double[] arrayInV = new double[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsDoubleLong args = new ArgumentsDoubleLong();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%24.8g %16x %31a",
+ args.inV, Double.doubleToRawLongBits(args.inV), args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertDouble2Long2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertDouble3Long3() {
+ Allocation inV = createRandomFloatAllocation(mRS, Element.DataType.FLOAT_64, 3, 0xcbf85861a2148389l, -9.2233720368547747840e+18, 9.2233720368547747840e+18);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 3), INPUTSIZE);
+ script.forEach_testConvertLong3Double3Long3(inV, out);
+ verifyResultsConvertDouble3Long3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong3Double3Long3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertLong3Double3Long3(inV, out);
+ verifyResultsConvertDouble3Long3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong3Double3Long3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertDouble3Long3(Allocation inV, Allocation out, boolean relaxed) {
+ double[] arrayInV = new double[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsDoubleLong args = new ArgumentsDoubleLong();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%24.8g %16x %31a",
+ args.inV, Double.doubleToRawLongBits(args.inV), args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertDouble3Long3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertDouble4Long4() {
+ Allocation inV = createRandomFloatAllocation(mRS, Element.DataType.FLOAT_64, 4, 0xcbf86303011c487dl, -9.2233720368547747840e+18, 9.2233720368547747840e+18);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 4), INPUTSIZE);
+ script.forEach_testConvertLong4Double4Long4(inV, out);
+ verifyResultsConvertDouble4Long4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong4Double4Long4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertLong4Double4Long4(inV, out);
+ verifyResultsConvertDouble4Long4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong4Double4Long4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertDouble4Long4(Allocation inV, Allocation out, boolean relaxed) {
+ double[] arrayInV = new double[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsDoubleLong args = new ArgumentsDoubleLong();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%24.8g %16x %31a",
+ args.inV, Double.doubleToRawLongBits(args.inV), args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertDouble4Long4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsLongLong {
+ public long inV;
+ public long out;
+ }
+
+ private void checkConvertLong2Long2() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_64, 2, 0xb570c607c81d242al, true, 63);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 2), INPUTSIZE);
+ script.forEach_testConvertLong2Long2Long2(inV, out);
+ verifyResultsConvertLong2Long2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong2Long2Long2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertLong2Long2Long2(inV, out);
+ verifyResultsConvertLong2Long2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong2Long2Long2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertLong2Long2(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsLongLong args = new ArgumentsLongLong();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertLong2Long2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertLong3Long3() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_64, 3, 0xb570d0a92724e91el, true, 63);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 3), INPUTSIZE);
+ script.forEach_testConvertLong3Long3Long3(inV, out);
+ verifyResultsConvertLong3Long3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong3Long3Long3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertLong3Long3Long3(inV, out);
+ verifyResultsConvertLong3Long3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong3Long3Long3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertLong3Long3(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsLongLong args = new ArgumentsLongLong();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertLong3Long3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertLong4Long4() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_64, 4, 0xb570db4a862cae12l, true, 63);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 4), INPUTSIZE);
+ script.forEach_testConvertLong4Long4Long4(inV, out);
+ verifyResultsConvertLong4Long4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong4Long4Long4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertLong4Long4Long4(inV, out);
+ verifyResultsConvertLong4Long4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong4Long4Long4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertLong4Long4(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsLongLong args = new ArgumentsLongLong();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertLong4Long4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsUlongLong {
+ public long inV;
+ public long out;
+ }
+
+ private void checkConvertUlong2Long2() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_64, 2, 0x5cfe7f555a9f30abl, false, 63);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 2), INPUTSIZE);
+ script.forEach_testConvertLong2Ulong2Long2(inV, out);
+ verifyResultsConvertUlong2Long2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong2Ulong2Long2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertLong2Ulong2Long2(inV, out);
+ verifyResultsConvertUlong2Long2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong2Ulong2Long2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUlong2Long2(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsUlongLong args = new ArgumentsUlongLong();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUlong2Long2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertUlong3Long3() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_64, 3, 0x5cfe89f6b9a6f59fl, false, 63);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 3), INPUTSIZE);
+ script.forEach_testConvertLong3Ulong3Long3(inV, out);
+ verifyResultsConvertUlong3Long3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong3Ulong3Long3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertLong3Ulong3Long3(inV, out);
+ verifyResultsConvertUlong3Long3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong3Ulong3Long3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUlong3Long3(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsUlongLong args = new ArgumentsUlongLong();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUlong3Long3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertUlong4Long4() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_64, 4, 0x5cfe949818aeba93l, false, 63);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 4), INPUTSIZE);
+ script.forEach_testConvertLong4Ulong4Long4(inV, out);
+ verifyResultsConvertUlong4Long4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong4Ulong4Long4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertLong4Ulong4Long4(inV, out);
+ verifyResultsConvertUlong4Long4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong4Ulong4Long4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUlong4Long4(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsUlongLong args = new ArgumentsUlongLong();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUlong4Long4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsDoubleUlong {
+ public double inV;
+ public long out;
+ }
+
+ private void checkConvertDouble2Ulong2() {
+ Allocation inV = createRandomFloatAllocation(mRS, Element.DataType.FLOAT_64, 2, 0x42b56e3b7e12ff5el, 0.0000000000000000000e+00, 1.8446744073709549568e+19);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 2), INPUTSIZE);
+ script.forEach_testConvertUlong2Double2Ulong2(inV, out);
+ verifyResultsConvertDouble2Ulong2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong2Double2Ulong2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUlong2Double2Ulong2(inV, out);
+ verifyResultsConvertDouble2Ulong2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong2Double2Ulong2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertDouble2Ulong2(Allocation inV, Allocation out, boolean relaxed) {
+ double[] arrayInV = new double[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsDoubleUlong args = new ArgumentsDoubleUlong();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%24.8g %16x %31a",
+ args.inV, Double.doubleToRawLongBits(args.inV), args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertDouble2Ulong2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertDouble3Ulong3() {
+ Allocation inV = createRandomFloatAllocation(mRS, Element.DataType.FLOAT_64, 3, 0x42b73756742e203cl, 0.0000000000000000000e+00, 1.8446744073709549568e+19);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 3), INPUTSIZE);
+ script.forEach_testConvertUlong3Double3Ulong3(inV, out);
+ verifyResultsConvertDouble3Ulong3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong3Double3Ulong3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUlong3Double3Ulong3(inV, out);
+ verifyResultsConvertDouble3Ulong3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong3Double3Ulong3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertDouble3Ulong3(Allocation inV, Allocation out, boolean relaxed) {
+ double[] arrayInV = new double[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsDoubleUlong args = new ArgumentsDoubleUlong();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%24.8g %16x %31a",
+ args.inV, Double.doubleToRawLongBits(args.inV), args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertDouble3Ulong3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertDouble4Ulong4() {
+ Allocation inV = createRandomFloatAllocation(mRS, Element.DataType.FLOAT_64, 4, 0x42b900716a49411al, 0.0000000000000000000e+00, 1.8446744073709549568e+19);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 4), INPUTSIZE);
+ script.forEach_testConvertUlong4Double4Ulong4(inV, out);
+ verifyResultsConvertDouble4Ulong4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong4Double4Ulong4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUlong4Double4Ulong4(inV, out);
+ verifyResultsConvertDouble4Ulong4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong4Double4Ulong4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertDouble4Ulong4(Allocation inV, Allocation out, boolean relaxed) {
+ double[] arrayInV = new double[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsDoubleUlong args = new ArgumentsDoubleUlong();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%24.8g %16x %31a",
+ args.inV, Double.doubleToRawLongBits(args.inV), args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertDouble4Ulong4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsLongUlong {
+ public long inV;
+ public long out;
+ }
+
+ private void checkConvertLong2Ulong2() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_64, 2, 0x79f1a23ed7d40f65l, false, 63);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 2), INPUTSIZE);
+ script.forEach_testConvertUlong2Long2Ulong2(inV, out);
+ verifyResultsConvertLong2Ulong2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong2Long2Ulong2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUlong2Long2Ulong2(inV, out);
+ verifyResultsConvertLong2Ulong2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong2Long2Ulong2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertLong2Ulong2(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsLongUlong args = new ArgumentsLongUlong();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertLong2Ulong2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertLong3Ulong3() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_64, 3, 0x79f36b59cdef3043l, false, 63);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 3), INPUTSIZE);
+ script.forEach_testConvertUlong3Long3Ulong3(inV, out);
+ verifyResultsConvertLong3Ulong3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong3Long3Ulong3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUlong3Long3Ulong3(inV, out);
+ verifyResultsConvertLong3Ulong3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong3Long3Ulong3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertLong3Ulong3(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsLongUlong args = new ArgumentsLongUlong();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertLong3Ulong3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertLong4Ulong4() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_64, 4, 0x79f53474c40a5121l, false, 63);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 4), INPUTSIZE);
+ script.forEach_testConvertUlong4Long4Ulong4(inV, out);
+ verifyResultsConvertLong4Ulong4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong4Long4Ulong4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUlong4Long4Ulong4(inV, out);
+ verifyResultsConvertLong4Ulong4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong4Long4Ulong4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertLong4Ulong4(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsLongUlong args = new ArgumentsLongUlong();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertLong4Ulong4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsUlongUlong {
+ public long inV;
+ public long out;
+ }
+
+ private void checkConvertUlong2Ulong2() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_64, 2, 0x9ebfc24673ac2910l, false, 64);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 2), INPUTSIZE);
+ script.forEach_testConvertUlong2Ulong2Ulong2(inV, out);
+ verifyResultsConvertUlong2Ulong2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong2Ulong2Ulong2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUlong2Ulong2Ulong2(inV, out);
+ verifyResultsConvertUlong2Ulong2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong2Ulong2Ulong2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUlong2Ulong2(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsUlongUlong args = new ArgumentsUlongUlong();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUlong2Ulong2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertUlong3Ulong3() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_64, 3, 0x9ec18b6169c749eel, false, 64);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 3), INPUTSIZE);
+ script.forEach_testConvertUlong3Ulong3Ulong3(inV, out);
+ verifyResultsConvertUlong3Ulong3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong3Ulong3Ulong3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUlong3Ulong3Ulong3(inV, out);
+ verifyResultsConvertUlong3Ulong3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong3Ulong3Ulong3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUlong3Ulong3(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsUlongUlong args = new ArgumentsUlongUlong();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUlong3Ulong3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertUlong4Ulong4() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_64, 4, 0x9ec3547c5fe26accl, false, 64);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 4), INPUTSIZE);
+ script.forEach_testConvertUlong4Ulong4Ulong4(inV, out);
+ verifyResultsConvertUlong4Ulong4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong4Ulong4Ulong4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUlong4Ulong4Ulong4(inV, out);
+ verifyResultsConvertUlong4Ulong4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong4Ulong4Ulong4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUlong4Ulong4(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsUlongUlong args = new ArgumentsUlongUlong();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUlong4Ulong4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsDoubleFloat {
+ public double inV;
+ public Floaty out;
+ }
+
+ private void checkConvertDouble2Float2() {
+ Allocation inV = createRandomFloatAllocation(mRS, Element.DataType.FLOAT_64, 2, 0x42b4cec67d6d9a2dl, -1.6163412428744576259e+38, 1.6163412428744576259e+38);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_32, 2), INPUTSIZE);
+ script.forEach_testConvertFloat2Double2Float2(inV, out);
+ verifyResultsConvertDouble2Float2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertFloat2Double2Float2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_32, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertFloat2Double2Float2(inV, out);
+ verifyResultsConvertDouble2Float2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertFloat2Double2Float2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertDouble2Float2(Allocation inV, Allocation out, boolean relaxed) {
+ double[] arrayInV = new double[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ float[] arrayOut = new float[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsDoubleFloat args = new ArgumentsDoubleFloat();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (!args.out.couldBe(arrayOut[i * 2 + j])) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%24.8g %16x %31a",
+ args.inV, Double.doubleToRawLongBits(args.inV), args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(args.out.toString());
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%14.8g %8x %15a",
+ arrayOut[i * 2 + j], Float.floatToRawIntBits(arrayOut[i * 2 + j]), arrayOut[i * 2 + j]));
+ if (!args.out.couldBe(arrayOut[i * 2 + j])) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertDouble2Float2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertDouble3Float3() {
+ Allocation inV = createRandomFloatAllocation(mRS, Element.DataType.FLOAT_64, 3, 0x42b697e17388bb0bl, -1.6163412428744576259e+38, 1.6163412428744576259e+38);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_32, 3), INPUTSIZE);
+ script.forEach_testConvertFloat3Double3Float3(inV, out);
+ verifyResultsConvertDouble3Float3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertFloat3Double3Float3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_32, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertFloat3Double3Float3(inV, out);
+ verifyResultsConvertDouble3Float3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertFloat3Double3Float3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertDouble3Float3(Allocation inV, Allocation out, boolean relaxed) {
+ double[] arrayInV = new double[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ float[] arrayOut = new float[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsDoubleFloat args = new ArgumentsDoubleFloat();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%24.8g %16x %31a",
+ args.inV, Double.doubleToRawLongBits(args.inV), args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(args.out.toString());
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%14.8g %8x %15a",
+ arrayOut[i * 4 + j], Float.floatToRawIntBits(arrayOut[i * 4 + j]), arrayOut[i * 4 + j]));
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertDouble3Float3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertDouble4Float4() {
+ Allocation inV = createRandomFloatAllocation(mRS, Element.DataType.FLOAT_64, 4, 0x42b860fc69a3dbe9l, -1.6163412428744576259e+38, 1.6163412428744576259e+38);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_32, 4), INPUTSIZE);
+ script.forEach_testConvertFloat4Double4Float4(inV, out);
+ verifyResultsConvertDouble4Float4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertFloat4Double4Float4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_32, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertFloat4Double4Float4(inV, out);
+ verifyResultsConvertDouble4Float4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertFloat4Double4Float4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertDouble4Float4(Allocation inV, Allocation out, boolean relaxed) {
+ double[] arrayInV = new double[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ float[] arrayOut = new float[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsDoubleFloat args = new ArgumentsDoubleFloat();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%24.8g %16x %31a",
+ args.inV, Double.doubleToRawLongBits(args.inV), args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(args.out.toString());
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%14.8g %8x %15a",
+ arrayOut[i * 4 + j], Float.floatToRawIntBits(arrayOut[i * 4 + j]), arrayOut[i * 4 + j]));
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertDouble4Float4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsLongFloat {
+ public long inV;
+ public Floaty out;
+ }
+
+ private void checkConvertLong2Float2() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_64, 2, 0x79f102c9d72eaa34l, true, 63);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_32, 2), INPUTSIZE);
+ script.forEach_testConvertFloat2Long2Float2(inV, out);
+ verifyResultsConvertLong2Float2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertFloat2Long2Float2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_32, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertFloat2Long2Float2(inV, out);
+ verifyResultsConvertLong2Float2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertFloat2Long2Float2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertLong2Float2(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ float[] arrayOut = new float[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsLongFloat args = new ArgumentsLongFloat();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (!args.out.couldBe(arrayOut[i * 2 + j])) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(args.out.toString());
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%14.8g %8x %15a",
+ arrayOut[i * 2 + j], Float.floatToRawIntBits(arrayOut[i * 2 + j]), arrayOut[i * 2 + j]));
+ if (!args.out.couldBe(arrayOut[i * 2 + j])) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertLong2Float2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertLong3Float3() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_64, 3, 0x79f2cbe4cd49cb12l, true, 63);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_32, 3), INPUTSIZE);
+ script.forEach_testConvertFloat3Long3Float3(inV, out);
+ verifyResultsConvertLong3Float3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertFloat3Long3Float3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_32, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertFloat3Long3Float3(inV, out);
+ verifyResultsConvertLong3Float3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertFloat3Long3Float3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertLong3Float3(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ float[] arrayOut = new float[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsLongFloat args = new ArgumentsLongFloat();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(args.out.toString());
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%14.8g %8x %15a",
+ arrayOut[i * 4 + j], Float.floatToRawIntBits(arrayOut[i * 4 + j]), arrayOut[i * 4 + j]));
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertLong3Float3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertLong4Float4() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_64, 4, 0x79f494ffc364ebf0l, true, 63);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_32, 4), INPUTSIZE);
+ script.forEach_testConvertFloat4Long4Float4(inV, out);
+ verifyResultsConvertLong4Float4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertFloat4Long4Float4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_32, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertFloat4Long4Float4(inV, out);
+ verifyResultsConvertLong4Float4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertFloat4Long4Float4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertLong4Float4(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ float[] arrayOut = new float[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsLongFloat args = new ArgumentsLongFloat();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(args.out.toString());
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%14.8g %8x %15a",
+ arrayOut[i * 4 + j], Float.floatToRawIntBits(arrayOut[i * 4 + j]), arrayOut[i * 4 + j]));
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertLong4Float4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsUlongFloat {
+ public long inV;
+ public Floaty out;
+ }
+
+ private void checkConvertUlong2Float2() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_64, 2, 0x9ebf22d17306c3dfl, false, 64);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_32, 2), INPUTSIZE);
+ script.forEach_testConvertFloat2Ulong2Float2(inV, out);
+ verifyResultsConvertUlong2Float2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertFloat2Ulong2Float2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_32, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertFloat2Ulong2Float2(inV, out);
+ verifyResultsConvertUlong2Float2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertFloat2Ulong2Float2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUlong2Float2(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ float[] arrayOut = new float[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsUlongFloat args = new ArgumentsUlongFloat();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (!args.out.couldBe(arrayOut[i * 2 + j])) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(args.out.toString());
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%14.8g %8x %15a",
+ arrayOut[i * 2 + j], Float.floatToRawIntBits(arrayOut[i * 2 + j]), arrayOut[i * 2 + j]));
+ if (!args.out.couldBe(arrayOut[i * 2 + j])) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUlong2Float2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertUlong3Float3() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_64, 3, 0x9ec0ebec6921e4bdl, false, 64);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_32, 3), INPUTSIZE);
+ script.forEach_testConvertFloat3Ulong3Float3(inV, out);
+ verifyResultsConvertUlong3Float3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertFloat3Ulong3Float3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_32, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertFloat3Ulong3Float3(inV, out);
+ verifyResultsConvertUlong3Float3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertFloat3Ulong3Float3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUlong3Float3(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ float[] arrayOut = new float[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsUlongFloat args = new ArgumentsUlongFloat();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(args.out.toString());
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%14.8g %8x %15a",
+ arrayOut[i * 4 + j], Float.floatToRawIntBits(arrayOut[i * 4 + j]), arrayOut[i * 4 + j]));
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUlong3Float3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertUlong4Float4() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_64, 4, 0x9ec2b5075f3d059bl, false, 64);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_32, 4), INPUTSIZE);
+ script.forEach_testConvertFloat4Ulong4Float4(inV, out);
+ verifyResultsConvertUlong4Float4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertFloat4Ulong4Float4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_32, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertFloat4Ulong4Float4(inV, out);
+ verifyResultsConvertUlong4Float4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertFloat4Ulong4Float4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUlong4Float4(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ float[] arrayOut = new float[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsUlongFloat args = new ArgumentsUlongFloat();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(args.out.toString());
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%14.8g %8x %15a",
+ arrayOut[i * 4 + j], Float.floatToRawIntBits(arrayOut[i * 4 + j]), arrayOut[i * 4 + j]));
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUlong4Float4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsDoubleChar {
+ public double inV;
+ public byte out;
+ }
+
+ private void checkConvertDouble2Char2() {
+ Allocation inV = createRandomFloatAllocation(mRS, Element.DataType.FLOAT_64, 2, 0xcbf84b7bef094a17l, -1.2800000000000000000e+02, 1.2700000000000000000e+02);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_8, 2), INPUTSIZE);
+ script.forEach_testConvertChar2Double2Char2(inV, out);
+ verifyResultsConvertDouble2Char2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertChar2Double2Char2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_8, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertChar2Double2Char2(inV, out);
+ verifyResultsConvertDouble2Char2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertChar2Double2Char2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertDouble2Char2(Allocation inV, Allocation out, boolean relaxed) {
+ double[] arrayInV = new double[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ byte[] arrayOut = new byte[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsDoubleChar args = new ArgumentsDoubleChar();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%24.8g %16x %31a",
+ args.inV, Double.doubleToRawLongBits(args.inV), args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertDouble2Char2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertDouble3Char3() {
+ Allocation inV = createRandomFloatAllocation(mRS, Element.DataType.FLOAT_64, 3, 0xcbf8561d4e110f0bl, -1.2800000000000000000e+02, 1.2700000000000000000e+02);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_8, 3), INPUTSIZE);
+ script.forEach_testConvertChar3Double3Char3(inV, out);
+ verifyResultsConvertDouble3Char3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertChar3Double3Char3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_8, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertChar3Double3Char3(inV, out);
+ verifyResultsConvertDouble3Char3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertChar3Double3Char3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertDouble3Char3(Allocation inV, Allocation out, boolean relaxed) {
+ double[] arrayInV = new double[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ byte[] arrayOut = new byte[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsDoubleChar args = new ArgumentsDoubleChar();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%24.8g %16x %31a",
+ args.inV, Double.doubleToRawLongBits(args.inV), args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertDouble3Char3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertDouble4Char4() {
+ Allocation inV = createRandomFloatAllocation(mRS, Element.DataType.FLOAT_64, 4, 0xcbf860bead18d3ffl, -1.2800000000000000000e+02, 1.2700000000000000000e+02);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_8, 4), INPUTSIZE);
+ script.forEach_testConvertChar4Double4Char4(inV, out);
+ verifyResultsConvertDouble4Char4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertChar4Double4Char4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_8, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertChar4Double4Char4(inV, out);
+ verifyResultsConvertDouble4Char4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertChar4Double4Char4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertDouble4Char4(Allocation inV, Allocation out, boolean relaxed) {
+ double[] arrayInV = new double[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ byte[] arrayOut = new byte[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsDoubleChar args = new ArgumentsDoubleChar();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%24.8g %16x %31a",
+ args.inV, Double.doubleToRawLongBits(args.inV), args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertDouble4Char4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsLongChar {
+ public long inV;
+ public byte out;
+ }
+
+ private void checkConvertLong2Char2() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_64, 2, 0xb570c3c37419afacl, true, 7);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_8, 2), INPUTSIZE);
+ script.forEach_testConvertChar2Long2Char2(inV, out);
+ verifyResultsConvertLong2Char2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertChar2Long2Char2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_8, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertChar2Long2Char2(inV, out);
+ verifyResultsConvertLong2Char2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertChar2Long2Char2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertLong2Char2(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ byte[] arrayOut = new byte[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsLongChar args = new ArgumentsLongChar();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertLong2Char2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertLong3Char3() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_64, 3, 0xb570ce64d32174a0l, true, 7);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_8, 3), INPUTSIZE);
+ script.forEach_testConvertChar3Long3Char3(inV, out);
+ verifyResultsConvertLong3Char3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertChar3Long3Char3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_8, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertChar3Long3Char3(inV, out);
+ verifyResultsConvertLong3Char3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertChar3Long3Char3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertLong3Char3(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ byte[] arrayOut = new byte[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsLongChar args = new ArgumentsLongChar();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertLong3Char3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertLong4Char4() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_64, 4, 0xb570d90632293994l, true, 7);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_8, 4), INPUTSIZE);
+ script.forEach_testConvertChar4Long4Char4(inV, out);
+ verifyResultsConvertLong4Char4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertChar4Long4Char4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_8, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertChar4Long4Char4(inV, out);
+ verifyResultsConvertLong4Char4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertChar4Long4Char4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertLong4Char4(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ byte[] arrayOut = new byte[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsLongChar args = new ArgumentsLongChar();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertLong4Char4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsUlongChar {
+ public long inV;
+ public byte out;
+ }
+
+ private void checkConvertUlong2Char2() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_64, 2, 0x5cfe7d11069bbc2dl, false, 7);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_8, 2), INPUTSIZE);
+ script.forEach_testConvertChar2Ulong2Char2(inV, out);
+ verifyResultsConvertUlong2Char2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertChar2Ulong2Char2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_8, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertChar2Ulong2Char2(inV, out);
+ verifyResultsConvertUlong2Char2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertChar2Ulong2Char2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUlong2Char2(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ byte[] arrayOut = new byte[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsUlongChar args = new ArgumentsUlongChar();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUlong2Char2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertUlong3Char3() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_64, 3, 0x5cfe87b265a38121l, false, 7);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_8, 3), INPUTSIZE);
+ script.forEach_testConvertChar3Ulong3Char3(inV, out);
+ verifyResultsConvertUlong3Char3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertChar3Ulong3Char3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_8, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertChar3Ulong3Char3(inV, out);
+ verifyResultsConvertUlong3Char3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertChar3Ulong3Char3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUlong3Char3(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ byte[] arrayOut = new byte[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsUlongChar args = new ArgumentsUlongChar();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUlong3Char3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertUlong4Char4() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_64, 4, 0x5cfe9253c4ab4615l, false, 7);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_8, 4), INPUTSIZE);
+ script.forEach_testConvertChar4Ulong4Char4(inV, out);
+ verifyResultsConvertUlong4Char4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertChar4Ulong4Char4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_8, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertChar4Ulong4Char4(inV, out);
+ verifyResultsConvertUlong4Char4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertChar4Ulong4Char4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUlong4Char4(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ byte[] arrayOut = new byte[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsUlongChar args = new ArgumentsUlongChar();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUlong4Char4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsDoubleUchar {
+ public double inV;
+ public byte out;
+ }
+
+ private void checkConvertDouble2Uchar2() {
+ Allocation inV = createRandomFloatAllocation(mRS, Element.DataType.FLOAT_64, 2, 0x42b56bf72a0f8ae0l, 0.0000000000000000000e+00, 2.5500000000000000000e+02);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_8, 2), INPUTSIZE);
+ script.forEach_testConvertUchar2Double2Uchar2(inV, out);
+ verifyResultsConvertDouble2Uchar2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUchar2Double2Uchar2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_8, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUchar2Double2Uchar2(inV, out);
+ verifyResultsConvertDouble2Uchar2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUchar2Double2Uchar2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertDouble2Uchar2(Allocation inV, Allocation out, boolean relaxed) {
+ double[] arrayInV = new double[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ byte[] arrayOut = new byte[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsDoubleUchar args = new ArgumentsDoubleUchar();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%24.8g %16x %31a",
+ args.inV, Double.doubleToRawLongBits(args.inV), args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertDouble2Uchar2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertDouble3Uchar3() {
+ Allocation inV = createRandomFloatAllocation(mRS, Element.DataType.FLOAT_64, 3, 0x42b73512202aabbel, 0.0000000000000000000e+00, 2.5500000000000000000e+02);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_8, 3), INPUTSIZE);
+ script.forEach_testConvertUchar3Double3Uchar3(inV, out);
+ verifyResultsConvertDouble3Uchar3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUchar3Double3Uchar3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_8, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUchar3Double3Uchar3(inV, out);
+ verifyResultsConvertDouble3Uchar3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUchar3Double3Uchar3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertDouble3Uchar3(Allocation inV, Allocation out, boolean relaxed) {
+ double[] arrayInV = new double[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ byte[] arrayOut = new byte[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsDoubleUchar args = new ArgumentsDoubleUchar();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%24.8g %16x %31a",
+ args.inV, Double.doubleToRawLongBits(args.inV), args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertDouble3Uchar3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertDouble4Uchar4() {
+ Allocation inV = createRandomFloatAllocation(mRS, Element.DataType.FLOAT_64, 4, 0x42b8fe2d1645cc9cl, 0.0000000000000000000e+00, 2.5500000000000000000e+02);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_8, 4), INPUTSIZE);
+ script.forEach_testConvertUchar4Double4Uchar4(inV, out);
+ verifyResultsConvertDouble4Uchar4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUchar4Double4Uchar4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_8, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUchar4Double4Uchar4(inV, out);
+ verifyResultsConvertDouble4Uchar4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUchar4Double4Uchar4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertDouble4Uchar4(Allocation inV, Allocation out, boolean relaxed) {
+ double[] arrayInV = new double[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ byte[] arrayOut = new byte[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsDoubleUchar args = new ArgumentsDoubleUchar();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%24.8g %16x %31a",
+ args.inV, Double.doubleToRawLongBits(args.inV), args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertDouble4Uchar4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsLongUchar {
+ public long inV;
+ public byte out;
+ }
+
+ private void checkConvertLong2Uchar2() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_64, 2, 0x79f19ffa83d09ae7l, false, 8);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_8, 2), INPUTSIZE);
+ script.forEach_testConvertUchar2Long2Uchar2(inV, out);
+ verifyResultsConvertLong2Uchar2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUchar2Long2Uchar2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_8, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUchar2Long2Uchar2(inV, out);
+ verifyResultsConvertLong2Uchar2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUchar2Long2Uchar2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertLong2Uchar2(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ byte[] arrayOut = new byte[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsLongUchar args = new ArgumentsLongUchar();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertLong2Uchar2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertLong3Uchar3() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_64, 3, 0x79f3691579ebbbc5l, false, 8);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_8, 3), INPUTSIZE);
+ script.forEach_testConvertUchar3Long3Uchar3(inV, out);
+ verifyResultsConvertLong3Uchar3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUchar3Long3Uchar3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_8, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUchar3Long3Uchar3(inV, out);
+ verifyResultsConvertLong3Uchar3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUchar3Long3Uchar3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertLong3Uchar3(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ byte[] arrayOut = new byte[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsLongUchar args = new ArgumentsLongUchar();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertLong3Uchar3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertLong4Uchar4() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_64, 4, 0x79f532307006dca3l, false, 8);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_8, 4), INPUTSIZE);
+ script.forEach_testConvertUchar4Long4Uchar4(inV, out);
+ verifyResultsConvertLong4Uchar4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUchar4Long4Uchar4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_8, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUchar4Long4Uchar4(inV, out);
+ verifyResultsConvertLong4Uchar4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUchar4Long4Uchar4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertLong4Uchar4(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ byte[] arrayOut = new byte[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsLongUchar args = new ArgumentsLongUchar();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertLong4Uchar4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsUlongUchar {
+ public long inV;
+ public byte out;
+ }
+
+ private void checkConvertUlong2Uchar2() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_64, 2, 0x9ebfc0021fa8b492l, false, 8);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_8, 2), INPUTSIZE);
+ script.forEach_testConvertUchar2Ulong2Uchar2(inV, out);
+ verifyResultsConvertUlong2Uchar2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUchar2Ulong2Uchar2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_8, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUchar2Ulong2Uchar2(inV, out);
+ verifyResultsConvertUlong2Uchar2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUchar2Ulong2Uchar2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUlong2Uchar2(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ byte[] arrayOut = new byte[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsUlongUchar args = new ArgumentsUlongUchar();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUlong2Uchar2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertUlong3Uchar3() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_64, 3, 0x9ec1891d15c3d570l, false, 8);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_8, 3), INPUTSIZE);
+ script.forEach_testConvertUchar3Ulong3Uchar3(inV, out);
+ verifyResultsConvertUlong3Uchar3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUchar3Ulong3Uchar3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_8, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUchar3Ulong3Uchar3(inV, out);
+ verifyResultsConvertUlong3Uchar3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUchar3Ulong3Uchar3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUlong3Uchar3(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ byte[] arrayOut = new byte[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsUlongUchar args = new ArgumentsUlongUchar();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUlong3Uchar3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertUlong4Uchar4() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_64, 4, 0x9ec352380bdef64el, false, 8);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_8, 4), INPUTSIZE);
+ script.forEach_testConvertUchar4Ulong4Uchar4(inV, out);
+ verifyResultsConvertUlong4Uchar4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUchar4Ulong4Uchar4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_8, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUchar4Ulong4Uchar4(inV, out);
+ verifyResultsConvertUlong4Uchar4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUchar4Ulong4Uchar4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUlong4Uchar4(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ byte[] arrayOut = new byte[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsUlongUchar args = new ArgumentsUlongUchar();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUlong4Uchar4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsDoubleShort {
+ public double inV;
+ public short out;
+ }
+
+ private void checkConvertDouble2Short2() {
+ Allocation inV = createRandomFloatAllocation(mRS, Element.DataType.FLOAT_64, 2, 0x42b557fbbf1d55f9l, -3.2768000000000000000e+04, 3.2767000000000000000e+04);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_16, 2), INPUTSIZE);
+ script.forEach_testConvertShort2Double2Short2(inV, out);
+ verifyResultsConvertDouble2Short2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertShort2Double2Short2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_16, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertShort2Double2Short2(inV, out);
+ verifyResultsConvertDouble2Short2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertShort2Double2Short2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertDouble2Short2(Allocation inV, Allocation out, boolean relaxed) {
+ double[] arrayInV = new double[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ short[] arrayOut = new short[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsDoubleShort args = new ArgumentsDoubleShort();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%24.8g %16x %31a",
+ args.inV, Double.doubleToRawLongBits(args.inV), args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertDouble2Short2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertDouble3Short3() {
+ Allocation inV = createRandomFloatAllocation(mRS, Element.DataType.FLOAT_64, 3, 0x42b72116b53876d7l, -3.2768000000000000000e+04, 3.2767000000000000000e+04);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_16, 3), INPUTSIZE);
+ script.forEach_testConvertShort3Double3Short3(inV, out);
+ verifyResultsConvertDouble3Short3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertShort3Double3Short3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_16, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertShort3Double3Short3(inV, out);
+ verifyResultsConvertDouble3Short3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertShort3Double3Short3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertDouble3Short3(Allocation inV, Allocation out, boolean relaxed) {
+ double[] arrayInV = new double[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ short[] arrayOut = new short[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsDoubleShort args = new ArgumentsDoubleShort();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%24.8g %16x %31a",
+ args.inV, Double.doubleToRawLongBits(args.inV), args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertDouble3Short3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertDouble4Short4() {
+ Allocation inV = createRandomFloatAllocation(mRS, Element.DataType.FLOAT_64, 4, 0x42b8ea31ab5397b5l, -3.2768000000000000000e+04, 3.2767000000000000000e+04);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_16, 4), INPUTSIZE);
+ script.forEach_testConvertShort4Double4Short4(inV, out);
+ verifyResultsConvertDouble4Short4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertShort4Double4Short4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_16, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertShort4Double4Short4(inV, out);
+ verifyResultsConvertDouble4Short4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertShort4Double4Short4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertDouble4Short4(Allocation inV, Allocation out, boolean relaxed) {
+ double[] arrayInV = new double[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ short[] arrayOut = new short[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsDoubleShort args = new ArgumentsDoubleShort();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%24.8g %16x %31a",
+ args.inV, Double.doubleToRawLongBits(args.inV), args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertDouble4Short4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsLongShort {
+ public long inV;
+ public short out;
+ }
+
+ private void checkConvertLong2Short2() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_64, 2, 0x79f18bff18de6600l, true, 15);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_16, 2), INPUTSIZE);
+ script.forEach_testConvertShort2Long2Short2(inV, out);
+ verifyResultsConvertLong2Short2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertShort2Long2Short2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_16, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertShort2Long2Short2(inV, out);
+ verifyResultsConvertLong2Short2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertShort2Long2Short2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertLong2Short2(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ short[] arrayOut = new short[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsLongShort args = new ArgumentsLongShort();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertLong2Short2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertLong3Short3() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_64, 3, 0x79f3551a0ef986del, true, 15);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_16, 3), INPUTSIZE);
+ script.forEach_testConvertShort3Long3Short3(inV, out);
+ verifyResultsConvertLong3Short3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertShort3Long3Short3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_16, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertShort3Long3Short3(inV, out);
+ verifyResultsConvertLong3Short3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertShort3Long3Short3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertLong3Short3(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ short[] arrayOut = new short[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsLongShort args = new ArgumentsLongShort();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertLong3Short3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertLong4Short4() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_64, 4, 0x79f51e350514a7bcl, true, 15);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_16, 4), INPUTSIZE);
+ script.forEach_testConvertShort4Long4Short4(inV, out);
+ verifyResultsConvertLong4Short4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertShort4Long4Short4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_16, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertShort4Long4Short4(inV, out);
+ verifyResultsConvertLong4Short4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertShort4Long4Short4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertLong4Short4(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ short[] arrayOut = new short[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsLongShort args = new ArgumentsLongShort();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertLong4Short4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsUlongShort {
+ public long inV;
+ public short out;
+ }
+
+ private void checkConvertUlong2Short2() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_64, 2, 0x9ebfac06b4b67fabl, false, 15);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_16, 2), INPUTSIZE);
+ script.forEach_testConvertShort2Ulong2Short2(inV, out);
+ verifyResultsConvertUlong2Short2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertShort2Ulong2Short2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_16, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertShort2Ulong2Short2(inV, out);
+ verifyResultsConvertUlong2Short2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertShort2Ulong2Short2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUlong2Short2(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ short[] arrayOut = new short[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsUlongShort args = new ArgumentsUlongShort();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUlong2Short2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertUlong3Short3() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_64, 3, 0x9ec17521aad1a089l, false, 15);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_16, 3), INPUTSIZE);
+ script.forEach_testConvertShort3Ulong3Short3(inV, out);
+ verifyResultsConvertUlong3Short3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertShort3Ulong3Short3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_16, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertShort3Ulong3Short3(inV, out);
+ verifyResultsConvertUlong3Short3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertShort3Ulong3Short3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUlong3Short3(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ short[] arrayOut = new short[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsUlongShort args = new ArgumentsUlongShort();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUlong3Short3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertUlong4Short4() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_64, 4, 0x9ec33e3ca0ecc167l, false, 15);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_16, 4), INPUTSIZE);
+ script.forEach_testConvertShort4Ulong4Short4(inV, out);
+ verifyResultsConvertUlong4Short4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertShort4Ulong4Short4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_16, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertShort4Ulong4Short4(inV, out);
+ verifyResultsConvertUlong4Short4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertShort4Ulong4Short4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUlong4Short4(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ short[] arrayOut = new short[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsUlongShort args = new ArgumentsUlongShort();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUlong4Short4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsDoubleUshort {
+ public double inV;
+ public short out;
+ }
+
+ private void checkConvertDouble2Ushort2() {
+ Allocation inV = createRandomFloatAllocation(mRS, Element.DataType.FLOAT_64, 2, 0x3479ccaea92a37bcl, 0.0000000000000000000e+00, 6.5535000000000000000e+04);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_16, 2), INPUTSIZE);
+ script.forEach_testConvertUshort2Double2Ushort2(inV, out);
+ verifyResultsConvertDouble2Ushort2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUshort2Double2Ushort2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_16, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUshort2Double2Ushort2(inV, out);
+ verifyResultsConvertDouble2Ushort2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUshort2Double2Ushort2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertDouble2Ushort2(Allocation inV, Allocation out, boolean relaxed) {
+ double[] arrayInV = new double[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ short[] arrayOut = new short[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsDoubleUshort args = new ArgumentsDoubleUshort();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%24.8g %16x %31a",
+ args.inV, Double.doubleToRawLongBits(args.inV), args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertDouble2Ushort2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertDouble3Ushort3() {
+ Allocation inV = createRandomFloatAllocation(mRS, Element.DataType.FLOAT_64, 3, 0x34c69435ff85c8e8l, 0.0000000000000000000e+00, 6.5535000000000000000e+04);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_16, 3), INPUTSIZE);
+ script.forEach_testConvertUshort3Double3Ushort3(inV, out);
+ verifyResultsConvertDouble3Ushort3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUshort3Double3Ushort3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_16, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUshort3Double3Ushort3(inV, out);
+ verifyResultsConvertDouble3Ushort3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUshort3Double3Ushort3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertDouble3Ushort3(Allocation inV, Allocation out, boolean relaxed) {
+ double[] arrayInV = new double[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ short[] arrayOut = new short[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsDoubleUshort args = new ArgumentsDoubleUshort();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%24.8g %16x %31a",
+ args.inV, Double.doubleToRawLongBits(args.inV), args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertDouble3Ushort3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertDouble4Ushort4() {
+ Allocation inV = createRandomFloatAllocation(mRS, Element.DataType.FLOAT_64, 4, 0x35135bbd55e15a14l, 0.0000000000000000000e+00, 6.5535000000000000000e+04);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_16, 4), INPUTSIZE);
+ script.forEach_testConvertUshort4Double4Ushort4(inV, out);
+ verifyResultsConvertDouble4Ushort4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUshort4Double4Ushort4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_16, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUshort4Double4Ushort4(inV, out);
+ verifyResultsConvertDouble4Ushort4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUshort4Double4Ushort4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertDouble4Ushort4(Allocation inV, Allocation out, boolean relaxed) {
+ double[] arrayInV = new double[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ short[] arrayOut = new short[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsDoubleUshort args = new ArgumentsDoubleUshort();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%24.8g %16x %31a",
+ args.inV, Double.doubleToRawLongBits(args.inV), args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertDouble4Ushort4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsLongUshort {
+ public long inV;
+ public short out;
+ }
+
+ private void checkConvertLong2Ushort2() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_64, 2, 0x7b96893ebc97e8e9l, false, 16);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_16, 2), INPUTSIZE);
+ script.forEach_testConvertUshort2Long2Ushort2(inV, out);
+ verifyResultsConvertLong2Ushort2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUshort2Long2Ushort2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_16, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUshort2Long2Ushort2(inV, out);
+ verifyResultsConvertLong2Ushort2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUshort2Long2Ushort2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertLong2Ushort2(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ short[] arrayOut = new short[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsLongUshort args = new ArgumentsLongUshort();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertLong2Ushort2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertLong3Ushort3() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_64, 3, 0x7be350c612f37a15l, false, 16);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_16, 3), INPUTSIZE);
+ script.forEach_testConvertUshort3Long3Ushort3(inV, out);
+ verifyResultsConvertLong3Ushort3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUshort3Long3Ushort3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_16, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUshort3Long3Ushort3(inV, out);
+ verifyResultsConvertLong3Ushort3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUshort3Long3Ushort3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertLong3Ushort3(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ short[] arrayOut = new short[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsLongUshort args = new ArgumentsLongUshort();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertLong3Ushort3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertLong4Ushort4() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_64, 4, 0x7c30184d694f0b41l, false, 16);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_16, 4), INPUTSIZE);
+ script.forEach_testConvertUshort4Long4Ushort4(inV, out);
+ verifyResultsConvertLong4Ushort4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUshort4Long4Ushort4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_16, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUshort4Long4Ushort4(inV, out);
+ verifyResultsConvertLong4Ushort4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUshort4Long4Ushort4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertLong4Ushort4(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ short[] arrayOut = new short[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsLongUshort args = new ArgumentsLongUshort();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertLong4Ushort4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsUlongUshort {
+ public long inV;
+ public short out;
+ }
+
+ private void checkConvertUlong2Ushort2() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_64, 2, 0xaa35ea85e9e438a2l, false, 16);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_16, 2), INPUTSIZE);
+ script.forEach_testConvertUshort2Ulong2Ushort2(inV, out);
+ verifyResultsConvertUlong2Ushort2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUshort2Ulong2Ushort2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_16, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUshort2Ulong2Ushort2(inV, out);
+ verifyResultsConvertUlong2Ushort2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUshort2Ulong2Ushort2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUlong2Ushort2(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ short[] arrayOut = new short[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsUlongUshort args = new ArgumentsUlongUshort();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUlong2Ushort2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertUlong3Ushort3() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_64, 3, 0xaa82b20d403fc9cel, false, 16);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_16, 3), INPUTSIZE);
+ script.forEach_testConvertUshort3Ulong3Ushort3(inV, out);
+ verifyResultsConvertUlong3Ushort3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUshort3Ulong3Ushort3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_16, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUshort3Ulong3Ushort3(inV, out);
+ verifyResultsConvertUlong3Ushort3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUshort3Ulong3Ushort3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUlong3Ushort3(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ short[] arrayOut = new short[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsUlongUshort args = new ArgumentsUlongUshort();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUlong3Ushort3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertUlong4Ushort4() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_64, 4, 0xaacf7994969b5afal, false, 16);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_16, 4), INPUTSIZE);
+ script.forEach_testConvertUshort4Ulong4Ushort4(inV, out);
+ verifyResultsConvertUlong4Ushort4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUshort4Ulong4Ushort4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_16, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUshort4Ulong4Ushort4(inV, out);
+ verifyResultsConvertUlong4Ushort4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUshort4Ulong4Ushort4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUlong4Ushort4(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ short[] arrayOut = new short[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsUlongUshort args = new ArgumentsUlongUshort();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUlong4Ushort4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsDoubleInt {
+ public double inV;
+ public int out;
+ }
+
+ private void checkConvertDouble2Int2() {
+ Allocation inV = createRandomFloatAllocation(mRS, Element.DataType.FLOAT_64, 2, 0xa57cd81dcaf628fcl, -2.1474836480000000000e+09, 2.1474836470000000000e+09);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_32, 2), INPUTSIZE);
+ script.forEach_testConvertInt2Double2Int2(inV, out);
+ verifyResultsConvertDouble2Int2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertInt2Double2Int2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_32, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertInt2Double2Int2(inV, out);
+ verifyResultsConvertDouble2Int2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertInt2Double2Int2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertDouble2Int2(Allocation inV, Allocation out, boolean relaxed) {
+ double[] arrayInV = new double[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ int[] arrayOut = new int[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsDoubleInt args = new ArgumentsDoubleInt();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%24.8g %16x %31a",
+ args.inV, Double.doubleToRawLongBits(args.inV), args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertDouble2Int2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertDouble3Int3() {
+ Allocation inV = createRandomFloatAllocation(mRS, Element.DataType.FLOAT_64, 3, 0xa57cd85d149e3932l, -2.1474836480000000000e+09, 2.1474836470000000000e+09);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_32, 3), INPUTSIZE);
+ script.forEach_testConvertInt3Double3Int3(inV, out);
+ verifyResultsConvertDouble3Int3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertInt3Double3Int3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_32, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertInt3Double3Int3(inV, out);
+ verifyResultsConvertDouble3Int3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertInt3Double3Int3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertDouble3Int3(Allocation inV, Allocation out, boolean relaxed) {
+ double[] arrayInV = new double[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ int[] arrayOut = new int[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsDoubleInt args = new ArgumentsDoubleInt();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%24.8g %16x %31a",
+ args.inV, Double.doubleToRawLongBits(args.inV), args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertDouble3Int3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertDouble4Int4() {
+ Allocation inV = createRandomFloatAllocation(mRS, Element.DataType.FLOAT_64, 4, 0xa57cd89c5e464968l, -2.1474836480000000000e+09, 2.1474836470000000000e+09);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_32, 4), INPUTSIZE);
+ script.forEach_testConvertInt4Double4Int4(inV, out);
+ verifyResultsConvertDouble4Int4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertInt4Double4Int4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_32, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertInt4Double4Int4(inV, out);
+ verifyResultsConvertDouble4Int4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertInt4Double4Int4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertDouble4Int4(Allocation inV, Allocation out, boolean relaxed) {
+ double[] arrayInV = new double[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ int[] arrayOut = new int[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsDoubleInt args = new ArgumentsDoubleInt();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%24.8g %16x %31a",
+ args.inV, Double.doubleToRawLongBits(args.inV), args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertDouble4Int4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsLongInt {
+ public long inV;
+ public int out;
+ }
+
+ private void checkConvertLong2Int2() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_64, 2, 0xfe441c66e5deba3bl, true, 31);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_32, 2), INPUTSIZE);
+ script.forEach_testConvertInt2Long2Int2(inV, out);
+ verifyResultsConvertLong2Int2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertInt2Long2Int2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_32, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertInt2Long2Int2(inV, out);
+ verifyResultsConvertLong2Int2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertInt2Long2Int2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertLong2Int2(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ int[] arrayOut = new int[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsLongInt args = new ArgumentsLongInt();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertLong2Int2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertLong3Int3() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_64, 3, 0xfe441ca62f86ca71l, true, 31);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_32, 3), INPUTSIZE);
+ script.forEach_testConvertInt3Long3Int3(inV, out);
+ verifyResultsConvertLong3Int3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertInt3Long3Int3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_32, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertInt3Long3Int3(inV, out);
+ verifyResultsConvertLong3Int3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertInt3Long3Int3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertLong3Int3(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ int[] arrayOut = new int[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsLongInt args = new ArgumentsLongInt();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertLong3Int3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertLong4Int4() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_64, 4, 0xfe441ce5792edaa7l, true, 31);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_32, 4), INPUTSIZE);
+ script.forEach_testConvertInt4Long4Int4(inV, out);
+ verifyResultsConvertLong4Int4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertInt4Long4Int4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_32, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertInt4Long4Int4(inV, out);
+ verifyResultsConvertLong4Int4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertInt4Long4Int4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertLong4Int4(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ int[] arrayOut = new int[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsLongInt args = new ArgumentsLongInt();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertLong4Int4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsUlongInt {
+ public long inV;
+ public int out;
+ }
+
+ private void checkConvertUlong2Int2() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_64, 2, 0xe11d350e352de3el, false, 31);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_32, 2), INPUTSIZE);
+ script.forEach_testConvertInt2Ulong2Int2(inV, out);
+ verifyResultsConvertUlong2Int2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertInt2Ulong2Int2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_32, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertInt2Ulong2Int2(inV, out);
+ verifyResultsConvertUlong2Int2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertInt2Ulong2Int2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUlong2Int2(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ int[] arrayOut = new int[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsUlongInt args = new ArgumentsUlongInt();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUlong2Int2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertUlong3Int3() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_64, 3, 0xe11d3902cfaee74l, false, 31);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_32, 3), INPUTSIZE);
+ script.forEach_testConvertInt3Ulong3Int3(inV, out);
+ verifyResultsConvertUlong3Int3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertInt3Ulong3Int3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_32, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertInt3Ulong3Int3(inV, out);
+ verifyResultsConvertUlong3Int3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertInt3Ulong3Int3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUlong3Int3(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ int[] arrayOut = new int[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsUlongInt args = new ArgumentsUlongInt();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUlong3Int3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertUlong4Int4() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_64, 4, 0xe11d3cf76a2feaal, false, 31);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_32, 4), INPUTSIZE);
+ script.forEach_testConvertInt4Ulong4Int4(inV, out);
+ verifyResultsConvertUlong4Int4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertInt4Ulong4Int4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_32, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertInt4Ulong4Int4(inV, out);
+ verifyResultsConvertUlong4Int4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertInt4Ulong4Int4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUlong4Int4(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ int[] arrayOut = new int[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsUlongInt args = new ArgumentsUlongInt();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUlong4Int4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsDoubleUint {
+ public double inV;
+ public int out;
+ }
+
+ private void checkConvertDouble2Uint2() {
+ Allocation inV = createRandomFloatAllocation(mRS, Element.DataType.FLOAT_64, 2, 0xcbf84ff107de7dd7l, 0.0000000000000000000e+00, 4.2949672950000000000e+09);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_32, 2), INPUTSIZE);
+ script.forEach_testConvertUint2Double2Uint2(inV, out);
+ verifyResultsConvertDouble2Uint2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUint2Double2Uint2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_32, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUint2Double2Uint2(inV, out);
+ verifyResultsConvertDouble2Uint2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUint2Double2Uint2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertDouble2Uint2(Allocation inV, Allocation out, boolean relaxed) {
+ double[] arrayInV = new double[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ int[] arrayOut = new int[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsDoubleUint args = new ArgumentsDoubleUint();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%24.8g %16x %31a",
+ args.inV, Double.doubleToRawLongBits(args.inV), args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertDouble2Uint2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertDouble3Uint3() {
+ Allocation inV = createRandomFloatAllocation(mRS, Element.DataType.FLOAT_64, 3, 0xcbf85a9266e642cbl, 0.0000000000000000000e+00, 4.2949672950000000000e+09);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_32, 3), INPUTSIZE);
+ script.forEach_testConvertUint3Double3Uint3(inV, out);
+ verifyResultsConvertDouble3Uint3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUint3Double3Uint3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_32, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUint3Double3Uint3(inV, out);
+ verifyResultsConvertDouble3Uint3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUint3Double3Uint3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertDouble3Uint3(Allocation inV, Allocation out, boolean relaxed) {
+ double[] arrayInV = new double[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ int[] arrayOut = new int[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsDoubleUint args = new ArgumentsDoubleUint();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%24.8g %16x %31a",
+ args.inV, Double.doubleToRawLongBits(args.inV), args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertDouble3Uint3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertDouble4Uint4() {
+ Allocation inV = createRandomFloatAllocation(mRS, Element.DataType.FLOAT_64, 4, 0xcbf86533c5ee07bfl, 0.0000000000000000000e+00, 4.2949672950000000000e+09);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_32, 4), INPUTSIZE);
+ script.forEach_testConvertUint4Double4Uint4(inV, out);
+ verifyResultsConvertDouble4Uint4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUint4Double4Uint4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_32, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUint4Double4Uint4(inV, out);
+ verifyResultsConvertDouble4Uint4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUint4Double4Uint4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertDouble4Uint4(Allocation inV, Allocation out, boolean relaxed) {
+ double[] arrayInV = new double[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ int[] arrayOut = new int[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsDoubleUint args = new ArgumentsDoubleUint();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%24.8g %16x %31a",
+ args.inV, Double.doubleToRawLongBits(args.inV), args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertDouble4Uint4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsLongUint {
+ public long inV;
+ public int out;
+ }
+
+ private void checkConvertLong2Uint2() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_64, 2, 0xb570c8388ceee36cl, false, 32);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_32, 2), INPUTSIZE);
+ script.forEach_testConvertUint2Long2Uint2(inV, out);
+ verifyResultsConvertLong2Uint2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUint2Long2Uint2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_32, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUint2Long2Uint2(inV, out);
+ verifyResultsConvertLong2Uint2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUint2Long2Uint2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertLong2Uint2(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ int[] arrayOut = new int[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsLongUint args = new ArgumentsLongUint();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertLong2Uint2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertLong3Uint3() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_64, 3, 0xb570d2d9ebf6a860l, false, 32);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_32, 3), INPUTSIZE);
+ script.forEach_testConvertUint3Long3Uint3(inV, out);
+ verifyResultsConvertLong3Uint3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUint3Long3Uint3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_32, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUint3Long3Uint3(inV, out);
+ verifyResultsConvertLong3Uint3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUint3Long3Uint3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertLong3Uint3(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ int[] arrayOut = new int[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsLongUint args = new ArgumentsLongUint();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertLong3Uint3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertLong4Uint4() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_64, 4, 0xb570dd7b4afe6d54l, false, 32);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_32, 4), INPUTSIZE);
+ script.forEach_testConvertUint4Long4Uint4(inV, out);
+ verifyResultsConvertLong4Uint4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUint4Long4Uint4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_32, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUint4Long4Uint4(inV, out);
+ verifyResultsConvertLong4Uint4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUint4Long4Uint4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertLong4Uint4(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ int[] arrayOut = new int[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsLongUint args = new ArgumentsLongUint();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertLong4Uint4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsUlongUint {
+ public long inV;
+ public int out;
+ }
+
+ private void checkConvertUlong2Uint2() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_64, 2, 0x5cfe81861f70efedl, false, 32);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_32, 2), INPUTSIZE);
+ script.forEach_testConvertUint2Ulong2Uint2(inV, out);
+ verifyResultsConvertUlong2Uint2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUint2Ulong2Uint2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_32, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUint2Ulong2Uint2(inV, out);
+ verifyResultsConvertUlong2Uint2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUint2Ulong2Uint2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUlong2Uint2(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ int[] arrayOut = new int[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsUlongUint args = new ArgumentsUlongUint();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUlong2Uint2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertUlong3Uint3() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_64, 3, 0x5cfe8c277e78b4e1l, false, 32);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_32, 3), INPUTSIZE);
+ script.forEach_testConvertUint3Ulong3Uint3(inV, out);
+ verifyResultsConvertUlong3Uint3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUint3Ulong3Uint3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_32, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUint3Ulong3Uint3(inV, out);
+ verifyResultsConvertUlong3Uint3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUint3Ulong3Uint3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUlong3Uint3(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ int[] arrayOut = new int[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsUlongUint args = new ArgumentsUlongUint();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUlong3Uint3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertUlong4Uint4() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_64, 4, 0x5cfe96c8dd8079d5l, false, 32);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_32, 4), INPUTSIZE);
+ script.forEach_testConvertUint4Ulong4Uint4(inV, out);
+ verifyResultsConvertUlong4Uint4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUint4Ulong4Uint4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_32, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUint4Ulong4Uint4(inV, out);
+ verifyResultsConvertUlong4Uint4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUint4Ulong4Uint4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUlong4Uint4(Allocation inV, Allocation out, boolean relaxed) {
+ long[] arrayInV = new long[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ int[] arrayOut = new int[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsUlongUint args = new ArgumentsUlongUint();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUlong4Uint4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsFloatDouble {
+ public float inV;
+ public Floaty out;
+ }
+
+ private void checkConvertFloat2Double2() {
+ Allocation inV = createRandomFloatAllocation(mRS, Element.DataType.FLOAT_32, 2, 0x36c6372446e08221l, -1.6163412428744576259e+38, 1.6163412428744576259e+38);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 2), INPUTSIZE);
+ script.forEach_testConvertDouble2Float2Double2(inV, out);
+ verifyResultsConvertFloat2Double2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble2Float2Double2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertDouble2Float2Double2(inV, out);
+ verifyResultsConvertFloat2Double2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble2Float2Double2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertFloat2Double2(Allocation inV, Allocation out, boolean relaxed) {
+ float[] arrayInV = new float[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ double[] arrayOut = new double[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsFloatDouble args = new ArgumentsFloatDouble();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (!args.out.couldBe(arrayOut[i * 2 + j])) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%14.8g %8x %15a",
+ args.inV, Float.floatToRawIntBits(args.inV), args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(args.out.toString());
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%24.8g %16x %31a",
+ arrayOut[i * 2 + j], Double.doubleToRawLongBits(arrayOut[i * 2 + j]), arrayOut[i * 2 + j]));
+ if (!args.out.couldBe(arrayOut[i * 2 + j])) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertFloat2Double2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertFloat3Double3() {
+ Allocation inV = createRandomFloatAllocation(mRS, Element.DataType.FLOAT_32, 3, 0x3712feab9d3c134dl, -1.6163412428744576259e+38, 1.6163412428744576259e+38);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 3), INPUTSIZE);
+ script.forEach_testConvertDouble3Float3Double3(inV, out);
+ verifyResultsConvertFloat3Double3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble3Float3Double3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertDouble3Float3Double3(inV, out);
+ verifyResultsConvertFloat3Double3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble3Float3Double3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertFloat3Double3(Allocation inV, Allocation out, boolean relaxed) {
+ float[] arrayInV = new float[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ double[] arrayOut = new double[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsFloatDouble args = new ArgumentsFloatDouble();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%14.8g %8x %15a",
+ args.inV, Float.floatToRawIntBits(args.inV), args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(args.out.toString());
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%24.8g %16x %31a",
+ arrayOut[i * 4 + j], Double.doubleToRawLongBits(arrayOut[i * 4 + j]), arrayOut[i * 4 + j]));
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertFloat3Double3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertFloat4Double4() {
+ Allocation inV = createRandomFloatAllocation(mRS, Element.DataType.FLOAT_32, 4, 0x375fc632f397a479l, -1.6163412428744576259e+38, 1.6163412428744576259e+38);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 4), INPUTSIZE);
+ script.forEach_testConvertDouble4Float4Double4(inV, out);
+ verifyResultsConvertFloat4Double4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble4Float4Double4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertDouble4Float4Double4(inV, out);
+ verifyResultsConvertFloat4Double4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble4Float4Double4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertFloat4Double4(Allocation inV, Allocation out, boolean relaxed) {
+ float[] arrayInV = new float[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ double[] arrayOut = new double[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsFloatDouble args = new ArgumentsFloatDouble();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%14.8g %8x %15a",
+ args.inV, Float.floatToRawIntBits(args.inV), args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(args.out.toString());
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%24.8g %16x %31a",
+ arrayOut[i * 4 + j], Double.doubleToRawLongBits(arrayOut[i * 4 + j]), arrayOut[i * 4 + j]));
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertFloat4Double4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsCharDouble {
+ public byte inV;
+ public Floaty out;
+ }
+
+ private void checkConvertChar2Double2() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_8, 2, 0xd86d88e268ca2f61l, true, 7);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 2), INPUTSIZE);
+ script.forEach_testConvertDouble2Char2Double2(inV, out);
+ verifyResultsConvertChar2Double2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble2Char2Double2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertDouble2Char2Double2(inV, out);
+ verifyResultsConvertChar2Double2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble2Char2Double2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertChar2Double2(Allocation inV, Allocation out, boolean relaxed) {
+ byte[] arrayInV = new byte[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ double[] arrayOut = new double[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsCharDouble args = new ArgumentsCharDouble();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (!args.out.couldBe(arrayOut[i * 2 + j])) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(args.out.toString());
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%24.8g %16x %31a",
+ arrayOut[i * 2 + j], Double.doubleToRawLongBits(arrayOut[i * 2 + j]), arrayOut[i * 2 + j]));
+ if (!args.out.couldBe(arrayOut[i * 2 + j])) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertChar2Double2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertChar3Double3() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_8, 3, 0xd8ba5069bf25c08dl, true, 7);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 3), INPUTSIZE);
+ script.forEach_testConvertDouble3Char3Double3(inV, out);
+ verifyResultsConvertChar3Double3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble3Char3Double3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertDouble3Char3Double3(inV, out);
+ verifyResultsConvertChar3Double3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble3Char3Double3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertChar3Double3(Allocation inV, Allocation out, boolean relaxed) {
+ byte[] arrayInV = new byte[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ double[] arrayOut = new double[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsCharDouble args = new ArgumentsCharDouble();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(args.out.toString());
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%24.8g %16x %31a",
+ arrayOut[i * 4 + j], Double.doubleToRawLongBits(arrayOut[i * 4 + j]), arrayOut[i * 4 + j]));
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertChar3Double3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertChar4Double4() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_8, 4, 0xd90717f1158151b9l, true, 7);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 4), INPUTSIZE);
+ script.forEach_testConvertDouble4Char4Double4(inV, out);
+ verifyResultsConvertChar4Double4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble4Char4Double4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertDouble4Char4Double4(inV, out);
+ verifyResultsConvertChar4Double4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble4Char4Double4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertChar4Double4(Allocation inV, Allocation out, boolean relaxed) {
+ byte[] arrayInV = new byte[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ double[] arrayOut = new double[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsCharDouble args = new ArgumentsCharDouble();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(args.out.toString());
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%24.8g %16x %31a",
+ arrayOut[i * 4 + j], Double.doubleToRawLongBits(arrayOut[i * 4 + j]), arrayOut[i * 4 + j]));
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertChar4Double4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsUcharDouble {
+ public byte inV;
+ public Floaty out;
+ }
+
+ private void checkConvertUchar2Double2() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_8, 2, 0x70cea2996167f1al, false, 8);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 2), INPUTSIZE);
+ script.forEach_testConvertDouble2Uchar2Double2(inV, out);
+ verifyResultsConvertUchar2Double2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble2Uchar2Double2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertDouble2Uchar2Double2(inV, out);
+ verifyResultsConvertUchar2Double2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble2Uchar2Double2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUchar2Double2(Allocation inV, Allocation out, boolean relaxed) {
+ byte[] arrayInV = new byte[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ double[] arrayOut = new double[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsUcharDouble args = new ArgumentsUcharDouble();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (!args.out.couldBe(arrayOut[i * 2 + j])) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(args.out.toString());
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%24.8g %16x %31a",
+ arrayOut[i * 2 + j], Double.doubleToRawLongBits(arrayOut[i * 2 + j]), arrayOut[i * 2 + j]));
+ if (!args.out.couldBe(arrayOut[i * 2 + j])) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUchar2Double2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertUchar3Double3() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_8, 3, 0x759b1b0ec721046l, false, 8);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 3), INPUTSIZE);
+ script.forEach_testConvertDouble3Uchar3Double3(inV, out);
+ verifyResultsConvertUchar3Double3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble3Uchar3Double3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertDouble3Uchar3Double3(inV, out);
+ verifyResultsConvertUchar3Double3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble3Uchar3Double3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUchar3Double3(Allocation inV, Allocation out, boolean relaxed) {
+ byte[] arrayInV = new byte[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ double[] arrayOut = new double[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsUcharDouble args = new ArgumentsUcharDouble();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(args.out.toString());
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%24.8g %16x %31a",
+ arrayOut[i * 4 + j], Double.doubleToRawLongBits(arrayOut[i * 4 + j]), arrayOut[i * 4 + j]));
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUchar3Double3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertUchar4Double4() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_8, 4, 0x7a6793842cda172l, false, 8);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 4), INPUTSIZE);
+ script.forEach_testConvertDouble4Uchar4Double4(inV, out);
+ verifyResultsConvertUchar4Double4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble4Uchar4Double4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertDouble4Uchar4Double4(inV, out);
+ verifyResultsConvertUchar4Double4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble4Uchar4Double4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUchar4Double4(Allocation inV, Allocation out, boolean relaxed) {
+ byte[] arrayInV = new byte[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ double[] arrayOut = new double[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsUcharDouble args = new ArgumentsUcharDouble();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(args.out.toString());
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%24.8g %16x %31a",
+ arrayOut[i * 4 + j], Double.doubleToRawLongBits(arrayOut[i * 4 + j]), arrayOut[i * 4 + j]));
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUchar4Double4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsShortDouble {
+ public short inV;
+ public Floaty out;
+ }
+
+ private void checkConvertShort2Double2() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_16, 2, 0xfdeea470023d0105l, true, 15);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 2), INPUTSIZE);
+ script.forEach_testConvertDouble2Short2Double2(inV, out);
+ verifyResultsConvertShort2Double2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble2Short2Double2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertDouble2Short2Double2(inV, out);
+ verifyResultsConvertShort2Double2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble2Short2Double2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertShort2Double2(Allocation inV, Allocation out, boolean relaxed) {
+ short[] arrayInV = new short[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ double[] arrayOut = new double[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsShortDouble args = new ArgumentsShortDouble();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (!args.out.couldBe(arrayOut[i * 2 + j])) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(args.out.toString());
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%24.8g %16x %31a",
+ arrayOut[i * 2 + j], Double.doubleToRawLongBits(arrayOut[i * 2 + j]), arrayOut[i * 2 + j]));
+ if (!args.out.couldBe(arrayOut[i * 2 + j])) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertShort2Double2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertShort3Double3() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_16, 3, 0xfe3b6bf758989231l, true, 15);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 3), INPUTSIZE);
+ script.forEach_testConvertDouble3Short3Double3(inV, out);
+ verifyResultsConvertShort3Double3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble3Short3Double3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertDouble3Short3Double3(inV, out);
+ verifyResultsConvertShort3Double3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble3Short3Double3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertShort3Double3(Allocation inV, Allocation out, boolean relaxed) {
+ short[] arrayInV = new short[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ double[] arrayOut = new double[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsShortDouble args = new ArgumentsShortDouble();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(args.out.toString());
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%24.8g %16x %31a",
+ arrayOut[i * 4 + j], Double.doubleToRawLongBits(arrayOut[i * 4 + j]), arrayOut[i * 4 + j]));
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertShort3Double3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertShort4Double4() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_16, 4, 0xfe88337eaef4235dl, true, 15);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 4), INPUTSIZE);
+ script.forEach_testConvertDouble4Short4Double4(inV, out);
+ verifyResultsConvertShort4Double4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble4Short4Double4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertDouble4Short4Double4(inV, out);
+ verifyResultsConvertShort4Double4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble4Short4Double4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertShort4Double4(Allocation inV, Allocation out, boolean relaxed) {
+ short[] arrayInV = new short[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ double[] arrayOut = new double[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsShortDouble args = new ArgumentsShortDouble();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(args.out.toString());
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%24.8g %16x %31a",
+ arrayOut[i * 4 + j], Double.doubleToRawLongBits(arrayOut[i * 4 + j]), arrayOut[i * 4 + j]));
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertShort4Double4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsUshortDouble {
+ public short inV;
+ public Floaty out;
+ }
+
+ private void checkConvertUshort2Double2() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_16, 2, 0xd2b3fb649e0e6518l, false, 16);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 2), INPUTSIZE);
+ script.forEach_testConvertDouble2Ushort2Double2(inV, out);
+ verifyResultsConvertUshort2Double2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble2Ushort2Double2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertDouble2Ushort2Double2(inV, out);
+ verifyResultsConvertUshort2Double2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble2Ushort2Double2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUshort2Double2(Allocation inV, Allocation out, boolean relaxed) {
+ short[] arrayInV = new short[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ double[] arrayOut = new double[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsUshortDouble args = new ArgumentsUshortDouble();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (!args.out.couldBe(arrayOut[i * 2 + j])) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(args.out.toString());
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%24.8g %16x %31a",
+ arrayOut[i * 2 + j], Double.doubleToRawLongBits(arrayOut[i * 2 + j]), arrayOut[i * 2 + j]));
+ if (!args.out.couldBe(arrayOut[i * 2 + j])) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUshort2Double2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertUshort3Double3() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_16, 3, 0xd300c2ebf469f644l, false, 16);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 3), INPUTSIZE);
+ script.forEach_testConvertDouble3Ushort3Double3(inV, out);
+ verifyResultsConvertUshort3Double3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble3Ushort3Double3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertDouble3Ushort3Double3(inV, out);
+ verifyResultsConvertUshort3Double3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble3Ushort3Double3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUshort3Double3(Allocation inV, Allocation out, boolean relaxed) {
+ short[] arrayInV = new short[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ double[] arrayOut = new double[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsUshortDouble args = new ArgumentsUshortDouble();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(args.out.toString());
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%24.8g %16x %31a",
+ arrayOut[i * 4 + j], Double.doubleToRawLongBits(arrayOut[i * 4 + j]), arrayOut[i * 4 + j]));
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUshort3Double3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertUshort4Double4() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_16, 4, 0xd34d8a734ac58770l, false, 16);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 4), INPUTSIZE);
+ script.forEach_testConvertDouble4Ushort4Double4(inV, out);
+ verifyResultsConvertUshort4Double4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble4Ushort4Double4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertDouble4Ushort4Double4(inV, out);
+ verifyResultsConvertUshort4Double4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble4Ushort4Double4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUshort4Double4(Allocation inV, Allocation out, boolean relaxed) {
+ short[] arrayInV = new short[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ double[] arrayOut = new double[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsUshortDouble args = new ArgumentsUshortDouble();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(args.out.toString());
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%24.8g %16x %31a",
+ arrayOut[i * 4 + j], Double.doubleToRawLongBits(arrayOut[i * 4 + j]), arrayOut[i * 4 + j]));
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUshort4Double4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsIntDouble {
+ public int inV;
+ public Floaty out;
+ }
+
+ private void checkConvertInt2Double2() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_32, 2, 0x1be423b7a40fc8f6l, true, 31);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 2), INPUTSIZE);
+ script.forEach_testConvertDouble2Int2Double2(inV, out);
+ verifyResultsConvertInt2Double2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble2Int2Double2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertDouble2Int2Double2(inV, out);
+ verifyResultsConvertInt2Double2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble2Int2Double2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertInt2Double2(Allocation inV, Allocation out, boolean relaxed) {
+ int[] arrayInV = new int[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ double[] arrayOut = new double[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsIntDouble args = new ArgumentsIntDouble();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (!args.out.couldBe(arrayOut[i * 2 + j])) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(args.out.toString());
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%24.8g %16x %31a",
+ arrayOut[i * 2 + j], Double.doubleToRawLongBits(arrayOut[i * 2 + j]), arrayOut[i * 2 + j]));
+ if (!args.out.couldBe(arrayOut[i * 2 + j])) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertInt2Double2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertInt3Double3() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_32, 3, 0x1c30eb3efa6b5a22l, true, 31);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 3), INPUTSIZE);
+ script.forEach_testConvertDouble3Int3Double3(inV, out);
+ verifyResultsConvertInt3Double3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble3Int3Double3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertDouble3Int3Double3(inV, out);
+ verifyResultsConvertInt3Double3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble3Int3Double3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertInt3Double3(Allocation inV, Allocation out, boolean relaxed) {
+ int[] arrayInV = new int[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ double[] arrayOut = new double[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsIntDouble args = new ArgumentsIntDouble();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(args.out.toString());
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%24.8g %16x %31a",
+ arrayOut[i * 4 + j], Double.doubleToRawLongBits(arrayOut[i * 4 + j]), arrayOut[i * 4 + j]));
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertInt3Double3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertInt4Double4() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_32, 4, 0x1c7db2c650c6eb4el, true, 31);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 4), INPUTSIZE);
+ script.forEach_testConvertDouble4Int4Double4(inV, out);
+ verifyResultsConvertInt4Double4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble4Int4Double4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertDouble4Int4Double4(inV, out);
+ verifyResultsConvertInt4Double4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble4Int4Double4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertInt4Double4(Allocation inV, Allocation out, boolean relaxed) {
+ int[] arrayInV = new int[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ double[] arrayOut = new double[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsIntDouble args = new ArgumentsIntDouble();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(args.out.toString());
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%24.8g %16x %31a",
+ arrayOut[i * 4 + j], Double.doubleToRawLongBits(arrayOut[i * 4 + j]), arrayOut[i * 4 + j]));
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertInt4Double4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsUintDouble {
+ public int inV;
+ public Floaty out;
+ }
+
+ private void checkConvertUint2Double2() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_32, 2, 0x40b243bf3fe7e2a1l, false, 32);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 2), INPUTSIZE);
+ script.forEach_testConvertDouble2Uint2Double2(inV, out);
+ verifyResultsConvertUint2Double2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble2Uint2Double2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertDouble2Uint2Double2(inV, out);
+ verifyResultsConvertUint2Double2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble2Uint2Double2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUint2Double2(Allocation inV, Allocation out, boolean relaxed) {
+ int[] arrayInV = new int[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ double[] arrayOut = new double[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsUintDouble args = new ArgumentsUintDouble();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (!args.out.couldBe(arrayOut[i * 2 + j])) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(args.out.toString());
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%24.8g %16x %31a",
+ arrayOut[i * 2 + j], Double.doubleToRawLongBits(arrayOut[i * 2 + j]), arrayOut[i * 2 + j]));
+ if (!args.out.couldBe(arrayOut[i * 2 + j])) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUint2Double2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertUint3Double3() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_32, 3, 0x40ff0b46964373cdl, false, 32);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 3), INPUTSIZE);
+ script.forEach_testConvertDouble3Uint3Double3(inV, out);
+ verifyResultsConvertUint3Double3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble3Uint3Double3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertDouble3Uint3Double3(inV, out);
+ verifyResultsConvertUint3Double3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble3Uint3Double3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUint3Double3(Allocation inV, Allocation out, boolean relaxed) {
+ int[] arrayInV = new int[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ double[] arrayOut = new double[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsUintDouble args = new ArgumentsUintDouble();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(args.out.toString());
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%24.8g %16x %31a",
+ arrayOut[i * 4 + j], Double.doubleToRawLongBits(arrayOut[i * 4 + j]), arrayOut[i * 4 + j]));
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUint3Double3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertUint4Double4() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_32, 4, 0x414bd2cdec9f04f9l, false, 32);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 4), INPUTSIZE);
+ script.forEach_testConvertDouble4Uint4Double4(inV, out);
+ verifyResultsConvertUint4Double4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble4Uint4Double4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_64, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertDouble4Uint4Double4(inV, out);
+ verifyResultsConvertUint4Double4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertDouble4Uint4Double4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUint4Double4(Allocation inV, Allocation out, boolean relaxed) {
+ int[] arrayInV = new int[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ double[] arrayOut = new double[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsUintDouble args = new ArgumentsUintDouble();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(args.out.toString());
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%24.8g %16x %31a",
+ arrayOut[i * 4 + j], Double.doubleToRawLongBits(arrayOut[i * 4 + j]), arrayOut[i * 4 + j]));
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUint4Double4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsFloatLong {
+ public float inV;
+ public long out;
+ }
+
+ private void checkConvertFloat2Long2() {
+ Allocation inV = createRandomFloatAllocation(mRS, Element.DataType.FLOAT_32, 2, 0x239cb49c7d7c0ae0l, -9.2233714870989619200e+18, 9.2233714870989619200e+18);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 2), INPUTSIZE);
+ script.forEach_testConvertLong2Float2Long2(inV, out);
+ verifyResultsConvertFloat2Long2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong2Float2Long2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertLong2Float2Long2(inV, out);
+ verifyResultsConvertFloat2Long2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong2Float2Long2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertFloat2Long2(Allocation inV, Allocation out, boolean relaxed) {
+ float[] arrayInV = new float[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsFloatLong args = new ArgumentsFloatLong();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%14.8g %8x %15a",
+ args.inV, Float.floatToRawIntBits(args.inV), args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertFloat2Long2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertFloat3Long3() {
+ Allocation inV = createRandomFloatAllocation(mRS, Element.DataType.FLOAT_32, 3, 0x239cbf3ddc83cfd4l, -9.2233714870989619200e+18, 9.2233714870989619200e+18);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 3), INPUTSIZE);
+ script.forEach_testConvertLong3Float3Long3(inV, out);
+ verifyResultsConvertFloat3Long3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong3Float3Long3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertLong3Float3Long3(inV, out);
+ verifyResultsConvertFloat3Long3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong3Float3Long3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertFloat3Long3(Allocation inV, Allocation out, boolean relaxed) {
+ float[] arrayInV = new float[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsFloatLong args = new ArgumentsFloatLong();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%14.8g %8x %15a",
+ args.inV, Float.floatToRawIntBits(args.inV), args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertFloat3Long3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertFloat4Long4() {
+ Allocation inV = createRandomFloatAllocation(mRS, Element.DataType.FLOAT_32, 4, 0x239cc9df3b8b94c8l, -9.2233714870989619200e+18, 9.2233714870989619200e+18);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 4), INPUTSIZE);
+ script.forEach_testConvertLong4Float4Long4(inV, out);
+ verifyResultsConvertFloat4Long4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong4Float4Long4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertLong4Float4Long4(inV, out);
+ verifyResultsConvertFloat4Long4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong4Float4Long4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertFloat4Long4(Allocation inV, Allocation out, boolean relaxed) {
+ float[] arrayInV = new float[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsFloatLong args = new ArgumentsFloatLong();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%14.8g %8x %15a",
+ args.inV, Float.floatToRawIntBits(args.inV), args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertFloat4Long4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsCharLong {
+ public byte inV;
+ public long out;
+ }
+
+ private void checkConvertChar2Long2() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_8, 2, 0xd86189bc290be220l, true, 7);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 2), INPUTSIZE);
+ script.forEach_testConvertLong2Char2Long2(inV, out);
+ verifyResultsConvertChar2Long2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong2Char2Long2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertLong2Char2Long2(inV, out);
+ verifyResultsConvertChar2Long2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong2Char2Long2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertChar2Long2(Allocation inV, Allocation out, boolean relaxed) {
+ byte[] arrayInV = new byte[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsCharLong args = new ArgumentsCharLong();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertChar2Long2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertChar3Long3() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_8, 3, 0xd861945d8813a714l, true, 7);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 3), INPUTSIZE);
+ script.forEach_testConvertLong3Char3Long3(inV, out);
+ verifyResultsConvertChar3Long3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong3Char3Long3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertLong3Char3Long3(inV, out);
+ verifyResultsConvertChar3Long3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong3Char3Long3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertChar3Long3(Allocation inV, Allocation out, boolean relaxed) {
+ byte[] arrayInV = new byte[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsCharLong args = new ArgumentsCharLong();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertChar3Long3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertChar4Long4() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_8, 4, 0xd8619efee71b6c08l, true, 7);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 4), INPUTSIZE);
+ script.forEach_testConvertLong4Char4Long4(inV, out);
+ verifyResultsConvertChar4Long4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong4Char4Long4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertLong4Char4Long4(inV, out);
+ verifyResultsConvertChar4Long4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong4Char4Long4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertChar4Long4(Allocation inV, Allocation out, boolean relaxed) {
+ byte[] arrayInV = new byte[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsCharLong args = new ArgumentsCharLong();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertChar4Long4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsUcharLong {
+ public byte inV;
+ public long out;
+ }
+
+ private void checkConvertUchar2Long2() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_8, 2, 0x7fef4309bb8deea1l, false, 8);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 2), INPUTSIZE);
+ script.forEach_testConvertLong2Uchar2Long2(inV, out);
+ verifyResultsConvertUchar2Long2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong2Uchar2Long2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertLong2Uchar2Long2(inV, out);
+ verifyResultsConvertUchar2Long2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong2Uchar2Long2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUchar2Long2(Allocation inV, Allocation out, boolean relaxed) {
+ byte[] arrayInV = new byte[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsUcharLong args = new ArgumentsUcharLong();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUchar2Long2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertUchar3Long3() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_8, 3, 0x7fef4dab1a95b395l, false, 8);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 3), INPUTSIZE);
+ script.forEach_testConvertLong3Uchar3Long3(inV, out);
+ verifyResultsConvertUchar3Long3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong3Uchar3Long3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertLong3Uchar3Long3(inV, out);
+ verifyResultsConvertUchar3Long3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong3Uchar3Long3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUchar3Long3(Allocation inV, Allocation out, boolean relaxed) {
+ byte[] arrayInV = new byte[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsUcharLong args = new ArgumentsUcharLong();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUchar3Long3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertUchar4Long4() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_8, 4, 0x7fef584c799d7889l, false, 8);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 4), INPUTSIZE);
+ script.forEach_testConvertLong4Uchar4Long4(inV, out);
+ verifyResultsConvertUchar4Long4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong4Uchar4Long4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertLong4Uchar4Long4(inV, out);
+ verifyResultsConvertUchar4Long4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong4Uchar4Long4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUchar4Long4(Allocation inV, Allocation out, boolean relaxed) {
+ byte[] arrayInV = new byte[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsUcharLong args = new ArgumentsUcharLong();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUchar4Long4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsShortLong {
+ public short inV;
+ public long out;
+ }
+
+ private void checkConvertShort2Long2() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_16, 2, 0x68ab674669c97ce4l, true, 15);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 2), INPUTSIZE);
+ script.forEach_testConvertLong2Short2Long2(inV, out);
+ verifyResultsConvertShort2Long2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong2Short2Long2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertLong2Short2Long2(inV, out);
+ verifyResultsConvertShort2Long2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong2Short2Long2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertShort2Long2(Allocation inV, Allocation out, boolean relaxed) {
+ short[] arrayInV = new short[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsShortLong args = new ArgumentsShortLong();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertShort2Long2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertShort3Long3() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_16, 3, 0x68ab71e7c8d141d8l, true, 15);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 3), INPUTSIZE);
+ script.forEach_testConvertLong3Short3Long3(inV, out);
+ verifyResultsConvertShort3Long3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong3Short3Long3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertLong3Short3Long3(inV, out);
+ verifyResultsConvertShort3Long3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong3Short3Long3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertShort3Long3(Allocation inV, Allocation out, boolean relaxed) {
+ short[] arrayInV = new short[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsShortLong args = new ArgumentsShortLong();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertShort3Long3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertShort4Long4() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_16, 4, 0x68ab7c8927d906ccl, true, 15);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 4), INPUTSIZE);
+ script.forEach_testConvertLong4Short4Long4(inV, out);
+ verifyResultsConvertShort4Long4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong4Short4Long4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertLong4Short4Long4(inV, out);
+ verifyResultsConvertShort4Long4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong4Short4Long4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertShort4Long4(Allocation inV, Allocation out, boolean relaxed) {
+ short[] arrayInV = new short[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsShortLong args = new ArgumentsShortLong();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertShort4Long4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsUshortLong {
+ public short inV;
+ public long out;
+ }
+
+ private void checkConvertUshort2Long2() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_16, 2, 0x8d79874e05a1968fl, false, 16);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 2), INPUTSIZE);
+ script.forEach_testConvertLong2Ushort2Long2(inV, out);
+ verifyResultsConvertUshort2Long2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong2Ushort2Long2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertLong2Ushort2Long2(inV, out);
+ verifyResultsConvertUshort2Long2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong2Ushort2Long2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUshort2Long2(Allocation inV, Allocation out, boolean relaxed) {
+ short[] arrayInV = new short[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsUshortLong args = new ArgumentsUshortLong();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUshort2Long2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertUshort3Long3() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_16, 3, 0x8d7991ef64a95b83l, false, 16);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 3), INPUTSIZE);
+ script.forEach_testConvertLong3Ushort3Long3(inV, out);
+ verifyResultsConvertUshort3Long3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong3Ushort3Long3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertLong3Ushort3Long3(inV, out);
+ verifyResultsConvertUshort3Long3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong3Ushort3Long3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUshort3Long3(Allocation inV, Allocation out, boolean relaxed) {
+ short[] arrayInV = new short[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsUshortLong args = new ArgumentsUshortLong();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUshort3Long3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertUshort4Long4() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_16, 4, 0x8d799c90c3b12077l, false, 16);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 4), INPUTSIZE);
+ script.forEach_testConvertLong4Ushort4Long4(inV, out);
+ verifyResultsConvertUshort4Long4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong4Ushort4Long4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertLong4Ushort4Long4(inV, out);
+ verifyResultsConvertUshort4Long4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong4Ushort4Long4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUshort4Long4(Allocation inV, Allocation out, boolean relaxed) {
+ short[] arrayInV = new short[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsUshortLong args = new ArgumentsUshortLong();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUshort4Long4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsIntLong {
+ public int inV;
+ public long out;
+ }
+
+ private void checkConvertInt2Long2() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_32, 2, 0xd74f538b8a45cb5dl, true, 31);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 2), INPUTSIZE);
+ script.forEach_testConvertLong2Int2Long2(inV, out);
+ verifyResultsConvertInt2Long2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong2Int2Long2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertLong2Int2Long2(inV, out);
+ verifyResultsConvertInt2Long2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong2Int2Long2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertInt2Long2(Allocation inV, Allocation out, boolean relaxed) {
+ int[] arrayInV = new int[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsIntLong args = new ArgumentsIntLong();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertInt2Long2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertInt3Long3() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_32, 3, 0xd74f5e2ce94d9051l, true, 31);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 3), INPUTSIZE);
+ script.forEach_testConvertLong3Int3Long3(inV, out);
+ verifyResultsConvertInt3Long3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong3Int3Long3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertLong3Int3Long3(inV, out);
+ verifyResultsConvertInt3Long3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong3Int3Long3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertInt3Long3(Allocation inV, Allocation out, boolean relaxed) {
+ int[] arrayInV = new int[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsIntLong args = new ArgumentsIntLong();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertInt3Long3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertInt4Long4() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_32, 4, 0xd74f68ce48555545l, true, 31);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 4), INPUTSIZE);
+ script.forEach_testConvertLong4Int4Long4(inV, out);
+ verifyResultsConvertInt4Long4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong4Int4Long4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertLong4Int4Long4(inV, out);
+ verifyResultsConvertInt4Long4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong4Int4Long4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertInt4Long4(Allocation inV, Allocation out, boolean relaxed) {
+ int[] arrayInV = new int[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsIntLong args = new ArgumentsIntLong();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertInt4Long4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsUintLong {
+ public int inV;
+ public long out;
+ }
+
+ private void checkConvertUint2Long2() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_32, 2, 0xe71d0a7587b9ef60l, false, 32);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 2), INPUTSIZE);
+ script.forEach_testConvertLong2Uint2Long2(inV, out);
+ verifyResultsConvertUint2Long2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong2Uint2Long2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertLong2Uint2Long2(inV, out);
+ verifyResultsConvertUint2Long2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong2Uint2Long2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUint2Long2(Allocation inV, Allocation out, boolean relaxed) {
+ int[] arrayInV = new int[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsUintLong args = new ArgumentsUintLong();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUint2Long2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertUint3Long3() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_32, 3, 0xe71d1516e6c1b454l, false, 32);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 3), INPUTSIZE);
+ script.forEach_testConvertLong3Uint3Long3(inV, out);
+ verifyResultsConvertUint3Long3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong3Uint3Long3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertLong3Uint3Long3(inV, out);
+ verifyResultsConvertUint3Long3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong3Uint3Long3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUint3Long3(Allocation inV, Allocation out, boolean relaxed) {
+ int[] arrayInV = new int[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsUintLong args = new ArgumentsUintLong();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUint3Long3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertUint4Long4() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_32, 4, 0xe71d1fb845c97948l, false, 32);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 4), INPUTSIZE);
+ script.forEach_testConvertLong4Uint4Long4(inV, out);
+ verifyResultsConvertUint4Long4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong4Uint4Long4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertLong4Uint4Long4(inV, out);
+ verifyResultsConvertUint4Long4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertLong4Uint4Long4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUint4Long4(Allocation inV, Allocation out, boolean relaxed) {
+ int[] arrayInV = new int[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsUintLong args = new ArgumentsUintLong();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUint4Long4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsFloatUlong {
+ public float inV;
+ public long out;
+ }
+
+ private void checkConvertFloat2Ulong2() {
+ Allocation inV = createRandomFloatAllocation(mRS, Element.DataType.FLOAT_32, 2, 0xfb52b5394ec4cff7l, 0.0000000000000000000e+00, 1.8446742974197923840e+19);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 2), INPUTSIZE);
+ script.forEach_testConvertUlong2Float2Ulong2(inV, out);
+ verifyResultsConvertFloat2Ulong2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong2Float2Ulong2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUlong2Float2Ulong2(inV, out);
+ verifyResultsConvertFloat2Ulong2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong2Float2Ulong2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertFloat2Ulong2(Allocation inV, Allocation out, boolean relaxed) {
+ float[] arrayInV = new float[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsFloatUlong args = new ArgumentsFloatUlong();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%14.8g %8x %15a",
+ args.inV, Float.floatToRawIntBits(args.inV), args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertFloat2Ulong2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertFloat3Ulong3() {
+ Allocation inV = createRandomFloatAllocation(mRS, Element.DataType.FLOAT_32, 3, 0xfb547e5444dff0d5l, 0.0000000000000000000e+00, 1.8446742974197923840e+19);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 3), INPUTSIZE);
+ script.forEach_testConvertUlong3Float3Ulong3(inV, out);
+ verifyResultsConvertFloat3Ulong3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong3Float3Ulong3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUlong3Float3Ulong3(inV, out);
+ verifyResultsConvertFloat3Ulong3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong3Float3Ulong3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertFloat3Ulong3(Allocation inV, Allocation out, boolean relaxed) {
+ float[] arrayInV = new float[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsFloatUlong args = new ArgumentsFloatUlong();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%14.8g %8x %15a",
+ args.inV, Float.floatToRawIntBits(args.inV), args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertFloat3Ulong3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertFloat4Ulong4() {
+ Allocation inV = createRandomFloatAllocation(mRS, Element.DataType.FLOAT_32, 4, 0xfb56476f3afb11b3l, 0.0000000000000000000e+00, 1.8446742974197923840e+19);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 4), INPUTSIZE);
+ script.forEach_testConvertUlong4Float4Ulong4(inV, out);
+ verifyResultsConvertFloat4Ulong4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong4Float4Ulong4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUlong4Float4Ulong4(inV, out);
+ verifyResultsConvertFloat4Ulong4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong4Float4Ulong4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertFloat4Ulong4(Allocation inV, Allocation out, boolean relaxed) {
+ float[] arrayInV = new float[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsFloatUlong args = new ArgumentsFloatUlong();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%14.8g %8x %15a",
+ args.inV, Float.floatToRawIntBits(args.inV), args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertFloat4Ulong4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsCharUlong {
+ public byte inV;
+ public long out;
+ }
+
+ private void checkConvertChar2Ulong2() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_8, 2, 0x5862818b1fedf7b7l, false, 7);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 2), INPUTSIZE);
+ script.forEach_testConvertUlong2Char2Ulong2(inV, out);
+ verifyResultsConvertChar2Ulong2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong2Char2Ulong2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUlong2Char2Ulong2(inV, out);
+ verifyResultsConvertChar2Ulong2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong2Char2Ulong2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertChar2Ulong2(Allocation inV, Allocation out, boolean relaxed) {
+ byte[] arrayInV = new byte[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsCharUlong args = new ArgumentsCharUlong();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertChar2Ulong2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertChar3Ulong3() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_8, 3, 0x58644aa616091895l, false, 7);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 3), INPUTSIZE);
+ script.forEach_testConvertUlong3Char3Ulong3(inV, out);
+ verifyResultsConvertChar3Ulong3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong3Char3Ulong3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUlong3Char3Ulong3(inV, out);
+ verifyResultsConvertChar3Ulong3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong3Char3Ulong3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertChar3Ulong3(Allocation inV, Allocation out, boolean relaxed) {
+ byte[] arrayInV = new byte[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsCharUlong args = new ArgumentsCharUlong();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertChar3Ulong3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertChar4Ulong4() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_8, 4, 0x586613c10c243973l, false, 7);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 4), INPUTSIZE);
+ script.forEach_testConvertUlong4Char4Ulong4(inV, out);
+ verifyResultsConvertChar4Ulong4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong4Char4Ulong4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUlong4Char4Ulong4(inV, out);
+ verifyResultsConvertChar4Ulong4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong4Char4Ulong4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertChar4Ulong4(Allocation inV, Allocation out, boolean relaxed) {
+ byte[] arrayInV = new byte[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsCharUlong args = new ArgumentsCharUlong();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertChar4Ulong4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsUcharUlong {
+ public byte inV;
+ public long out;
+ }
+
+ private void checkConvertUchar2Ulong2() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_8, 2, 0x7d30a192bbc61162l, false, 8);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 2), INPUTSIZE);
+ script.forEach_testConvertUlong2Uchar2Ulong2(inV, out);
+ verifyResultsConvertUchar2Ulong2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong2Uchar2Ulong2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUlong2Uchar2Ulong2(inV, out);
+ verifyResultsConvertUchar2Ulong2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong2Uchar2Ulong2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUchar2Ulong2(Allocation inV, Allocation out, boolean relaxed) {
+ byte[] arrayInV = new byte[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsUcharUlong args = new ArgumentsUcharUlong();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUchar2Ulong2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertUchar3Ulong3() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_8, 3, 0x7d326aadb1e13240l, false, 8);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 3), INPUTSIZE);
+ script.forEach_testConvertUlong3Uchar3Ulong3(inV, out);
+ verifyResultsConvertUchar3Ulong3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong3Uchar3Ulong3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUlong3Uchar3Ulong3(inV, out);
+ verifyResultsConvertUchar3Ulong3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong3Uchar3Ulong3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUchar3Ulong3(Allocation inV, Allocation out, boolean relaxed) {
+ byte[] arrayInV = new byte[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsUcharUlong args = new ArgumentsUcharUlong();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUchar3Ulong3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertUchar4Ulong4() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_8, 4, 0x7d3433c8a7fc531el, false, 8);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 4), INPUTSIZE);
+ script.forEach_testConvertUlong4Uchar4Ulong4(inV, out);
+ verifyResultsConvertUchar4Ulong4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong4Uchar4Ulong4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUlong4Uchar4Ulong4(inV, out);
+ verifyResultsConvertUchar4Ulong4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong4Uchar4Ulong4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUchar4Ulong4(Allocation inV, Allocation out, boolean relaxed) {
+ byte[] arrayInV = new byte[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsUcharUlong args = new ArgumentsUcharUlong();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUchar4Ulong4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsShortUlong {
+ public short inV;
+ public long out;
+ }
+
+ private void checkConvertShort2Ulong2() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_16, 2, 0x94cab7c3ffc6f6a3l, false, 15);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 2), INPUTSIZE);
+ script.forEach_testConvertUlong2Short2Ulong2(inV, out);
+ verifyResultsConvertShort2Ulong2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong2Short2Ulong2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUlong2Short2Ulong2(inV, out);
+ verifyResultsConvertShort2Ulong2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong2Short2Ulong2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertShort2Ulong2(Allocation inV, Allocation out, boolean relaxed) {
+ short[] arrayInV = new short[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsShortUlong args = new ArgumentsShortUlong();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertShort2Ulong2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertShort3Ulong3() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_16, 3, 0x94cc80def5e21781l, false, 15);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 3), INPUTSIZE);
+ script.forEach_testConvertUlong3Short3Ulong3(inV, out);
+ verifyResultsConvertShort3Ulong3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong3Short3Ulong3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUlong3Short3Ulong3(inV, out);
+ verifyResultsConvertShort3Ulong3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong3Short3Ulong3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertShort3Ulong3(Allocation inV, Allocation out, boolean relaxed) {
+ short[] arrayInV = new short[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsShortUlong args = new ArgumentsShortUlong();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertShort3Ulong3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertShort4Ulong4() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_16, 4, 0x94ce49f9ebfd385fl, false, 15);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 4), INPUTSIZE);
+ script.forEach_testConvertUlong4Short4Ulong4(inV, out);
+ verifyResultsConvertShort4Ulong4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong4Short4Ulong4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUlong4Short4Ulong4(inV, out);
+ verifyResultsConvertShort4Ulong4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong4Short4Ulong4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertShort4Ulong4(Allocation inV, Allocation out, boolean relaxed) {
+ short[] arrayInV = new short[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsShortUlong args = new ArgumentsShortUlong();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertShort4Ulong4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsUshortUlong {
+ public short inV;
+ public long out;
+ }
+
+ private void checkConvertUshort2Ulong2() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_16, 2, 0xc36a190b2d13465cl, false, 16);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 2), INPUTSIZE);
+ script.forEach_testConvertUlong2Ushort2Ulong2(inV, out);
+ verifyResultsConvertUshort2Ulong2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong2Ushort2Ulong2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUlong2Ushort2Ulong2(inV, out);
+ verifyResultsConvertUshort2Ulong2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong2Ushort2Ulong2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUshort2Ulong2(Allocation inV, Allocation out, boolean relaxed) {
+ short[] arrayInV = new short[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsUshortUlong args = new ArgumentsUshortUlong();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUshort2Ulong2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertUshort3Ulong3() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_16, 3, 0xc36be226232e673al, false, 16);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 3), INPUTSIZE);
+ script.forEach_testConvertUlong3Ushort3Ulong3(inV, out);
+ verifyResultsConvertUshort3Ulong3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong3Ushort3Ulong3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUlong3Ushort3Ulong3(inV, out);
+ verifyResultsConvertUshort3Ulong3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong3Ushort3Ulong3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUshort3Ulong3(Allocation inV, Allocation out, boolean relaxed) {
+ short[] arrayInV = new short[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsUshortUlong args = new ArgumentsUshortUlong();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUshort3Ulong3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertUshort4Ulong4() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_16, 4, 0xc36dab4119498818l, false, 16);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 4), INPUTSIZE);
+ script.forEach_testConvertUlong4Ushort4Ulong4(inV, out);
+ verifyResultsConvertUshort4Ulong4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong4Ushort4Ulong4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUlong4Ushort4Ulong4(inV, out);
+ verifyResultsConvertUshort4Ulong4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong4Ushort4Ulong4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUshort4Ulong4(Allocation inV, Allocation out, boolean relaxed) {
+ short[] arrayInV = new short[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsUshortUlong args = new ArgumentsUshortUlong();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUshort4Ulong4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsIntUlong {
+ public int inV;
+ public long out;
+ }
+
+ private void checkConvertInt2Ulong2() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_32, 2, 0x2a53676074a824f6l, false, 31);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 2), INPUTSIZE);
+ script.forEach_testConvertUlong2Int2Ulong2(inV, out);
+ verifyResultsConvertInt2Ulong2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong2Int2Ulong2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUlong2Int2Ulong2(inV, out);
+ verifyResultsConvertInt2Ulong2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong2Int2Ulong2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertInt2Ulong2(Allocation inV, Allocation out, boolean relaxed) {
+ int[] arrayInV = new int[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsIntUlong args = new ArgumentsIntUlong();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertInt2Ulong2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertInt3Ulong3() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_32, 3, 0x2a55307b6ac345d4l, false, 31);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 3), INPUTSIZE);
+ script.forEach_testConvertUlong3Int3Ulong3(inV, out);
+ verifyResultsConvertInt3Ulong3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong3Int3Ulong3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUlong3Int3Ulong3(inV, out);
+ verifyResultsConvertInt3Ulong3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong3Int3Ulong3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertInt3Ulong3(Allocation inV, Allocation out, boolean relaxed) {
+ int[] arrayInV = new int[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsIntUlong args = new ArgumentsIntUlong();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertInt3Ulong3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertInt4Ulong4() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.SIGNED_32, 4, 0x2a56f99660de66b2l, false, 31);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 4), INPUTSIZE);
+ script.forEach_testConvertUlong4Int4Ulong4(inV, out);
+ verifyResultsConvertInt4Ulong4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong4Int4Ulong4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUlong4Int4Ulong4(inV, out);
+ verifyResultsConvertInt4Ulong4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong4Int4Ulong4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertInt4Ulong4(Allocation inV, Allocation out, boolean relaxed) {
+ int[] arrayInV = new int[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsIntUlong args = new ArgumentsIntUlong();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("%d", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertInt4Ulong4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsUintUlong {
+ public int inV;
+ public long out;
+ }
+
+ private void checkConvertUint2Ulong2() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_32, 2, 0xd1e120ae072a3177l, false, 32);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 2), INPUTSIZE);
+ script.forEach_testConvertUlong2Uint2Ulong2(inV, out);
+ verifyResultsConvertUint2Ulong2(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong2Uint2Ulong2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 2), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUlong2Uint2Ulong2(inV, out);
+ verifyResultsConvertUint2Ulong2(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong2Uint2Ulong2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUint2Ulong2(Allocation inV, Allocation out, boolean relaxed) {
+ int[] arrayInV = new int[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsUintUlong args = new ArgumentsUintUlong();
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUint2Ulong2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertUint3Ulong3() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_32, 3, 0xd1e2e9c8fd455255l, false, 32);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 3), INPUTSIZE);
+ script.forEach_testConvertUlong3Uint3Ulong3(inV, out);
+ verifyResultsConvertUint3Ulong3(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong3Uint3Ulong3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 3), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUlong3Uint3Ulong3(inV, out);
+ verifyResultsConvertUint3Ulong3(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong3Uint3Ulong3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUint3Ulong3(Allocation inV, Allocation out, boolean relaxed) {
+ int[] arrayInV = new int[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsUintUlong args = new ArgumentsUintUlong();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUint3Ulong3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkConvertUint4Ulong4() {
+ Allocation inV = createRandomIntegerAllocation(mRS, Element.DataType.UNSIGNED_32, 4, 0xd1e4b2e3f3607333l, false, 32);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 4), INPUTSIZE);
+ script.forEach_testConvertUlong4Uint4Ulong4(inV, out);
+ verifyResultsConvertUint4Ulong4(inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong4Uint4Ulong4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 4), INPUTSIZE);
+ scriptRelaxed.forEach_testConvertUlong4Uint4Ulong4(inV, out);
+ verifyResultsConvertUint4Ulong4(inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testConvertUlong4Uint4Ulong4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsConvertUint4Ulong4(Allocation inV, Allocation out, boolean relaxed) {
+ int[] arrayInV = new int[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsUintUlong args = new ArgumentsUintUlong();
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeConvert(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV: ");
+ message.append(String.format("0x%x", args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkConvertUint4Ulong4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
public void testConvert() {
checkConvertFloat2Float2();
checkConvertFloat3Float3();
@@ -8848,5 +17902,158 @@
checkConvertUint2Uint2();
checkConvertUint3Uint3();
checkConvertUint4Uint4();
+ checkConvertDouble2Double2();
+ checkConvertDouble3Double3();
+ checkConvertDouble4Double4();
+ checkConvertLong2Double2();
+ checkConvertLong3Double3();
+ checkConvertLong4Double4();
+ checkConvertUlong2Double2();
+ checkConvertUlong3Double3();
+ checkConvertUlong4Double4();
+ checkConvertDouble2Long2();
+ checkConvertDouble3Long3();
+ checkConvertDouble4Long4();
+ checkConvertLong2Long2();
+ checkConvertLong3Long3();
+ checkConvertLong4Long4();
+ checkConvertUlong2Long2();
+ checkConvertUlong3Long3();
+ checkConvertUlong4Long4();
+ checkConvertDouble2Ulong2();
+ checkConvertDouble3Ulong3();
+ checkConvertDouble4Ulong4();
+ checkConvertLong2Ulong2();
+ checkConvertLong3Ulong3();
+ checkConvertLong4Ulong4();
+ checkConvertUlong2Ulong2();
+ checkConvertUlong3Ulong3();
+ checkConvertUlong4Ulong4();
+ checkConvertDouble2Float2();
+ checkConvertDouble3Float3();
+ checkConvertDouble4Float4();
+ checkConvertLong2Float2();
+ checkConvertLong3Float3();
+ checkConvertLong4Float4();
+ checkConvertUlong2Float2();
+ checkConvertUlong3Float3();
+ checkConvertUlong4Float4();
+ checkConvertDouble2Char2();
+ checkConvertDouble3Char3();
+ checkConvertDouble4Char4();
+ checkConvertLong2Char2();
+ checkConvertLong3Char3();
+ checkConvertLong4Char4();
+ checkConvertUlong2Char2();
+ checkConvertUlong3Char3();
+ checkConvertUlong4Char4();
+ checkConvertDouble2Uchar2();
+ checkConvertDouble3Uchar3();
+ checkConvertDouble4Uchar4();
+ checkConvertLong2Uchar2();
+ checkConvertLong3Uchar3();
+ checkConvertLong4Uchar4();
+ checkConvertUlong2Uchar2();
+ checkConvertUlong3Uchar3();
+ checkConvertUlong4Uchar4();
+ checkConvertDouble2Short2();
+ checkConvertDouble3Short3();
+ checkConvertDouble4Short4();
+ checkConvertLong2Short2();
+ checkConvertLong3Short3();
+ checkConvertLong4Short4();
+ checkConvertUlong2Short2();
+ checkConvertUlong3Short3();
+ checkConvertUlong4Short4();
+ checkConvertDouble2Ushort2();
+ checkConvertDouble3Ushort3();
+ checkConvertDouble4Ushort4();
+ checkConvertLong2Ushort2();
+ checkConvertLong3Ushort3();
+ checkConvertLong4Ushort4();
+ checkConvertUlong2Ushort2();
+ checkConvertUlong3Ushort3();
+ checkConvertUlong4Ushort4();
+ checkConvertDouble2Int2();
+ checkConvertDouble3Int3();
+ checkConvertDouble4Int4();
+ checkConvertLong2Int2();
+ checkConvertLong3Int3();
+ checkConvertLong4Int4();
+ checkConvertUlong2Int2();
+ checkConvertUlong3Int3();
+ checkConvertUlong4Int4();
+ checkConvertDouble2Uint2();
+ checkConvertDouble3Uint3();
+ checkConvertDouble4Uint4();
+ checkConvertLong2Uint2();
+ checkConvertLong3Uint3();
+ checkConvertLong4Uint4();
+ checkConvertUlong2Uint2();
+ checkConvertUlong3Uint3();
+ checkConvertUlong4Uint4();
+ checkConvertFloat2Double2();
+ checkConvertFloat3Double3();
+ checkConvertFloat4Double4();
+ checkConvertChar2Double2();
+ checkConvertChar3Double3();
+ checkConvertChar4Double4();
+ checkConvertUchar2Double2();
+ checkConvertUchar3Double3();
+ checkConvertUchar4Double4();
+ checkConvertShort2Double2();
+ checkConvertShort3Double3();
+ checkConvertShort4Double4();
+ checkConvertUshort2Double2();
+ checkConvertUshort3Double3();
+ checkConvertUshort4Double4();
+ checkConvertInt2Double2();
+ checkConvertInt3Double3();
+ checkConvertInt4Double4();
+ checkConvertUint2Double2();
+ checkConvertUint3Double3();
+ checkConvertUint4Double4();
+ checkConvertFloat2Long2();
+ checkConvertFloat3Long3();
+ checkConvertFloat4Long4();
+ checkConvertChar2Long2();
+ checkConvertChar3Long3();
+ checkConvertChar4Long4();
+ checkConvertUchar2Long2();
+ checkConvertUchar3Long3();
+ checkConvertUchar4Long4();
+ checkConvertShort2Long2();
+ checkConvertShort3Long3();
+ checkConvertShort4Long4();
+ checkConvertUshort2Long2();
+ checkConvertUshort3Long3();
+ checkConvertUshort4Long4();
+ checkConvertInt2Long2();
+ checkConvertInt3Long3();
+ checkConvertInt4Long4();
+ checkConvertUint2Long2();
+ checkConvertUint3Long3();
+ checkConvertUint4Long4();
+ checkConvertFloat2Ulong2();
+ checkConvertFloat3Ulong3();
+ checkConvertFloat4Ulong4();
+ checkConvertChar2Ulong2();
+ checkConvertChar3Ulong3();
+ checkConvertChar4Ulong4();
+ checkConvertUchar2Ulong2();
+ checkConvertUchar3Ulong3();
+ checkConvertUchar4Ulong4();
+ checkConvertShort2Ulong2();
+ checkConvertShort3Ulong3();
+ checkConvertShort4Ulong4();
+ checkConvertUshort2Ulong2();
+ checkConvertUshort3Ulong3();
+ checkConvertUshort4Ulong4();
+ checkConvertInt2Ulong2();
+ checkConvertInt3Ulong3();
+ checkConvertInt4Ulong4();
+ checkConvertUint2Ulong2();
+ checkConvertUint3Ulong3();
+ checkConvertUint4Ulong4();
}
}
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/TestMax.java b/tests/tests/renderscript/src/android/renderscript/cts/TestMax.java
index 7df768d..b20cbcb 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/TestMax.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/TestMax.java
@@ -388,6 +388,204 @@
}
}
+ private void checkMaxChar2Char2Char2() {
+ Allocation inV1 = createRandomAllocation(mRS, Element.DataType.SIGNED_8, 2, 0x12084b25952bc64l, false);
+ Allocation inV2 = createRandomAllocation(mRS, Element.DataType.SIGNED_8, 2, 0x12084b25952bc65l, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_8, 2), INPUTSIZE);
+ script.set_gAllocInV2(inV2);
+ script.forEach_testMaxChar2Char2Char2(inV1, out);
+ verifyResultsMaxChar2Char2Char2(inV1, inV2, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxChar2Char2Char2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_8, 2), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV2(inV2);
+ scriptRelaxed.forEach_testMaxChar2Char2Char2(inV1, out);
+ verifyResultsMaxChar2Char2Char2(inV1, inV2, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxChar2Char2Char2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsMaxChar2Char2Char2(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
+ byte[] arrayInV1 = new byte[INPUTSIZE * 2];
+ inV1.copyTo(arrayInV1);
+ byte[] arrayInV2 = new byte[INPUTSIZE * 2];
+ inV2.copyTo(arrayInV2);
+ byte[] arrayOut = new byte[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsCharCharChar args = new ArgumentsCharCharChar();
+ args.inV1 = arrayInV1[i * 2 + j];
+ args.inV2 = arrayInV2[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeMax(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV1: ");
+ message.append(String.format("%d", args.inV1));
+ message.append("\n");
+ message.append("Input inV2: ");
+ message.append(String.format("%d", args.inV2));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkMaxChar2Char2Char2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkMaxChar3Char3Char3() {
+ Allocation inV1 = createRandomAllocation(mRS, Element.DataType.SIGNED_8, 3, 0x567200e53e0a8f29l, false);
+ Allocation inV2 = createRandomAllocation(mRS, Element.DataType.SIGNED_8, 3, 0x567200e53e0a8f2al, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_8, 3), INPUTSIZE);
+ script.set_gAllocInV2(inV2);
+ script.forEach_testMaxChar3Char3Char3(inV1, out);
+ verifyResultsMaxChar3Char3Char3(inV1, inV2, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxChar3Char3Char3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_8, 3), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV2(inV2);
+ scriptRelaxed.forEach_testMaxChar3Char3Char3(inV1, out);
+ verifyResultsMaxChar3Char3Char3(inV1, inV2, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxChar3Char3Char3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsMaxChar3Char3Char3(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
+ byte[] arrayInV1 = new byte[INPUTSIZE * 4];
+ inV1.copyTo(arrayInV1);
+ byte[] arrayInV2 = new byte[INPUTSIZE * 4];
+ inV2.copyTo(arrayInV2);
+ byte[] arrayOut = new byte[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsCharCharChar args = new ArgumentsCharCharChar();
+ args.inV1 = arrayInV1[i * 4 + j];
+ args.inV2 = arrayInV2[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeMax(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV1: ");
+ message.append(String.format("%d", args.inV1));
+ message.append("\n");
+ message.append("Input inV2: ");
+ message.append(String.format("%d", args.inV2));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkMaxChar3Char3Char3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkMaxChar4Char4Char4() {
+ Allocation inV1 = createRandomAllocation(mRS, Element.DataType.SIGNED_8, 4, 0xabc37d1822c261eel, false);
+ Allocation inV2 = createRandomAllocation(mRS, Element.DataType.SIGNED_8, 4, 0xabc37d1822c261efl, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_8, 4), INPUTSIZE);
+ script.set_gAllocInV2(inV2);
+ script.forEach_testMaxChar4Char4Char4(inV1, out);
+ verifyResultsMaxChar4Char4Char4(inV1, inV2, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxChar4Char4Char4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_8, 4), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV2(inV2);
+ scriptRelaxed.forEach_testMaxChar4Char4Char4(inV1, out);
+ verifyResultsMaxChar4Char4Char4(inV1, inV2, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxChar4Char4Char4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsMaxChar4Char4Char4(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
+ byte[] arrayInV1 = new byte[INPUTSIZE * 4];
+ inV1.copyTo(arrayInV1);
+ byte[] arrayInV2 = new byte[INPUTSIZE * 4];
+ inV2.copyTo(arrayInV2);
+ byte[] arrayOut = new byte[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsCharCharChar args = new ArgumentsCharCharChar();
+ args.inV1 = arrayInV1[i * 4 + j];
+ args.inV2 = arrayInV2[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeMax(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV1: ");
+ message.append(String.format("%d", args.inV1));
+ message.append("\n");
+ message.append("Input inV2: ");
+ message.append(String.format("%d", args.inV2));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkMaxChar4Char4Char4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
public class ArgumentsUcharUcharUchar {
public byte inV1;
public byte inV2;
@@ -460,6 +658,204 @@
}
}
+ private void checkMaxUchar2Uchar2Uchar2() {
+ Allocation inV1 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_8, 2, 0x75eda605e43f8b81l, false);
+ Allocation inV2 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_8, 2, 0x75eda605e43f8b82l, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_8, 2), INPUTSIZE);
+ script.set_gAllocInV2(inV2);
+ script.forEach_testMaxUchar2Uchar2Uchar2(inV1, out);
+ verifyResultsMaxUchar2Uchar2Uchar2(inV1, inV2, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxUchar2Uchar2Uchar2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_8, 2), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV2(inV2);
+ scriptRelaxed.forEach_testMaxUchar2Uchar2Uchar2(inV1, out);
+ verifyResultsMaxUchar2Uchar2Uchar2(inV1, inV2, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxUchar2Uchar2Uchar2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsMaxUchar2Uchar2Uchar2(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
+ byte[] arrayInV1 = new byte[INPUTSIZE * 2];
+ inV1.copyTo(arrayInV1);
+ byte[] arrayInV2 = new byte[INPUTSIZE * 2];
+ inV2.copyTo(arrayInV2);
+ byte[] arrayOut = new byte[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsUcharUcharUchar args = new ArgumentsUcharUcharUchar();
+ args.inV1 = arrayInV1[i * 2 + j];
+ args.inV2 = arrayInV2[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeMax(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV1: ");
+ message.append(String.format("0x%x", args.inV1));
+ message.append("\n");
+ message.append("Input inV2: ");
+ message.append(String.format("0x%x", args.inV2));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkMaxUchar2Uchar2Uchar2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkMaxUchar3Uchar3Uchar3() {
+ Allocation inV1 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_8, 3, 0xa2def5663489d18cl, false);
+ Allocation inV2 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_8, 3, 0xa2def5663489d18dl, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_8, 3), INPUTSIZE);
+ script.set_gAllocInV2(inV2);
+ script.forEach_testMaxUchar3Uchar3Uchar3(inV1, out);
+ verifyResultsMaxUchar3Uchar3Uchar3(inV1, inV2, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxUchar3Uchar3Uchar3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_8, 3), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV2(inV2);
+ scriptRelaxed.forEach_testMaxUchar3Uchar3Uchar3(inV1, out);
+ verifyResultsMaxUchar3Uchar3Uchar3(inV1, inV2, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxUchar3Uchar3Uchar3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsMaxUchar3Uchar3Uchar3(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
+ byte[] arrayInV1 = new byte[INPUTSIZE * 4];
+ inV1.copyTo(arrayInV1);
+ byte[] arrayInV2 = new byte[INPUTSIZE * 4];
+ inV2.copyTo(arrayInV2);
+ byte[] arrayOut = new byte[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsUcharUcharUchar args = new ArgumentsUcharUcharUchar();
+ args.inV1 = arrayInV1[i * 4 + j];
+ args.inV2 = arrayInV2[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeMax(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV1: ");
+ message.append(String.format("0x%x", args.inV1));
+ message.append("\n");
+ message.append("Input inV2: ");
+ message.append(String.format("0x%x", args.inV2));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkMaxUchar3Uchar3Uchar3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkMaxUchar4Uchar4Uchar4() {
+ Allocation inV1 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_8, 4, 0xcfd044c684d41797l, false);
+ Allocation inV2 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_8, 4, 0xcfd044c684d41798l, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_8, 4), INPUTSIZE);
+ script.set_gAllocInV2(inV2);
+ script.forEach_testMaxUchar4Uchar4Uchar4(inV1, out);
+ verifyResultsMaxUchar4Uchar4Uchar4(inV1, inV2, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxUchar4Uchar4Uchar4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_8, 4), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV2(inV2);
+ scriptRelaxed.forEach_testMaxUchar4Uchar4Uchar4(inV1, out);
+ verifyResultsMaxUchar4Uchar4Uchar4(inV1, inV2, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxUchar4Uchar4Uchar4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsMaxUchar4Uchar4Uchar4(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
+ byte[] arrayInV1 = new byte[INPUTSIZE * 4];
+ inV1.copyTo(arrayInV1);
+ byte[] arrayInV2 = new byte[INPUTSIZE * 4];
+ inV2.copyTo(arrayInV2);
+ byte[] arrayOut = new byte[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsUcharUcharUchar args = new ArgumentsUcharUcharUchar();
+ args.inV1 = arrayInV1[i * 4 + j];
+ args.inV2 = arrayInV2[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeMax(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV1: ");
+ message.append(String.format("0x%x", args.inV1));
+ message.append("\n");
+ message.append("Input inV2: ");
+ message.append(String.format("0x%x", args.inV2));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkMaxUchar4Uchar4Uchar4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
public class ArgumentsShortShortShort {
public short inV1;
public short inV2;
@@ -532,6 +928,204 @@
}
}
+ private void checkMaxShort2Short2Short2() {
+ Allocation inV1 = createRandomAllocation(mRS, Element.DataType.SIGNED_16, 2, 0x3d46ae0799c33c02l, false);
+ Allocation inV2 = createRandomAllocation(mRS, Element.DataType.SIGNED_16, 2, 0x3d46ae0799c33c03l, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_16, 2), INPUTSIZE);
+ script.set_gAllocInV2(inV2);
+ script.forEach_testMaxShort2Short2Short2(inV1, out);
+ verifyResultsMaxShort2Short2Short2(inV1, inV2, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxShort2Short2Short2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_16, 2), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV2(inV2);
+ scriptRelaxed.forEach_testMaxShort2Short2Short2(inV1, out);
+ verifyResultsMaxShort2Short2Short2(inV1, inV2, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxShort2Short2Short2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsMaxShort2Short2Short2(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
+ short[] arrayInV1 = new short[INPUTSIZE * 2];
+ inV1.copyTo(arrayInV1);
+ short[] arrayInV2 = new short[INPUTSIZE * 2];
+ inV2.copyTo(arrayInV2);
+ short[] arrayOut = new short[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsShortShortShort args = new ArgumentsShortShortShort();
+ args.inV1 = arrayInV1[i * 2 + j];
+ args.inV2 = arrayInV2[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeMax(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV1: ");
+ message.append(String.format("%d", args.inV1));
+ message.append("\n");
+ message.append("Input inV2: ");
+ message.append(String.format("%d", args.inV2));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkMaxShort2Short2Short2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkMaxShort3Short3Short3() {
+ Allocation inV1 = createRandomAllocation(mRS, Element.DataType.SIGNED_16, 3, 0x6a37fd67ea0d820dl, false);
+ Allocation inV2 = createRandomAllocation(mRS, Element.DataType.SIGNED_16, 3, 0x6a37fd67ea0d820el, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_16, 3), INPUTSIZE);
+ script.set_gAllocInV2(inV2);
+ script.forEach_testMaxShort3Short3Short3(inV1, out);
+ verifyResultsMaxShort3Short3Short3(inV1, inV2, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxShort3Short3Short3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_16, 3), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV2(inV2);
+ scriptRelaxed.forEach_testMaxShort3Short3Short3(inV1, out);
+ verifyResultsMaxShort3Short3Short3(inV1, inV2, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxShort3Short3Short3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsMaxShort3Short3Short3(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
+ short[] arrayInV1 = new short[INPUTSIZE * 4];
+ inV1.copyTo(arrayInV1);
+ short[] arrayInV2 = new short[INPUTSIZE * 4];
+ inV2.copyTo(arrayInV2);
+ short[] arrayOut = new short[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsShortShortShort args = new ArgumentsShortShortShort();
+ args.inV1 = arrayInV1[i * 4 + j];
+ args.inV2 = arrayInV2[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeMax(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV1: ");
+ message.append(String.format("%d", args.inV1));
+ message.append("\n");
+ message.append("Input inV2: ");
+ message.append(String.format("%d", args.inV2));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkMaxShort3Short3Short3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkMaxShort4Short4Short4() {
+ Allocation inV1 = createRandomAllocation(mRS, Element.DataType.SIGNED_16, 4, 0x97294cc83a57c818l, false);
+ Allocation inV2 = createRandomAllocation(mRS, Element.DataType.SIGNED_16, 4, 0x97294cc83a57c819l, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_16, 4), INPUTSIZE);
+ script.set_gAllocInV2(inV2);
+ script.forEach_testMaxShort4Short4Short4(inV1, out);
+ verifyResultsMaxShort4Short4Short4(inV1, inV2, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxShort4Short4Short4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_16, 4), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV2(inV2);
+ scriptRelaxed.forEach_testMaxShort4Short4Short4(inV1, out);
+ verifyResultsMaxShort4Short4Short4(inV1, inV2, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxShort4Short4Short4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsMaxShort4Short4Short4(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
+ short[] arrayInV1 = new short[INPUTSIZE * 4];
+ inV1.copyTo(arrayInV1);
+ short[] arrayInV2 = new short[INPUTSIZE * 4];
+ inV2.copyTo(arrayInV2);
+ short[] arrayOut = new short[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsShortShortShort args = new ArgumentsShortShortShort();
+ args.inV1 = arrayInV1[i * 4 + j];
+ args.inV2 = arrayInV2[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeMax(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV1: ");
+ message.append(String.format("%d", args.inV1));
+ message.append("\n");
+ message.append("Input inV2: ");
+ message.append(String.format("%d", args.inV2));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkMaxShort4Short4Short4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
public class ArgumentsUshortUshortUshort {
public short inV1;
public short inV2;
@@ -604,6 +1198,204 @@
}
}
+ private void checkMaxUshort2Ushort2Ushort2() {
+ Allocation inV1 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_16, 2, 0xf42196a588de51bfl, false);
+ Allocation inV2 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_16, 2, 0xf42196a588de51c0l, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_16, 2), INPUTSIZE);
+ script.set_gAllocInV2(inV2);
+ script.forEach_testMaxUshort2Ushort2Ushort2(inV1, out);
+ verifyResultsMaxUshort2Ushort2Ushort2(inV1, inV2, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxUshort2Ushort2Ushort2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_16, 2), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV2(inV2);
+ scriptRelaxed.forEach_testMaxUshort2Ushort2Ushort2(inV1, out);
+ verifyResultsMaxUshort2Ushort2Ushort2(inV1, inV2, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxUshort2Ushort2Ushort2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsMaxUshort2Ushort2Ushort2(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
+ short[] arrayInV1 = new short[INPUTSIZE * 2];
+ inV1.copyTo(arrayInV1);
+ short[] arrayInV2 = new short[INPUTSIZE * 2];
+ inV2.copyTo(arrayInV2);
+ short[] arrayOut = new short[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsUshortUshortUshort args = new ArgumentsUshortUshortUshort();
+ args.inV1 = arrayInV1[i * 2 + j];
+ args.inV2 = arrayInV2[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeMax(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV1: ");
+ message.append(String.format("0x%x", args.inV1));
+ message.append("\n");
+ message.append("Input inV2: ");
+ message.append(String.format("0x%x", args.inV2));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkMaxUshort2Ushort2Ushort2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkMaxUshort3Ushort3Ushort3() {
+ Allocation inV1 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_16, 3, 0x71604884c752e61cl, false);
+ Allocation inV2 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_16, 3, 0x71604884c752e61dl, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_16, 3), INPUTSIZE);
+ script.set_gAllocInV2(inV2);
+ script.forEach_testMaxUshort3Ushort3Ushort3(inV1, out);
+ verifyResultsMaxUshort3Ushort3Ushort3(inV1, inV2, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxUshort3Ushort3Ushort3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_16, 3), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV2(inV2);
+ scriptRelaxed.forEach_testMaxUshort3Ushort3Ushort3(inV1, out);
+ verifyResultsMaxUshort3Ushort3Ushort3(inV1, inV2, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxUshort3Ushort3Ushort3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsMaxUshort3Ushort3Ushort3(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
+ short[] arrayInV1 = new short[INPUTSIZE * 4];
+ inV1.copyTo(arrayInV1);
+ short[] arrayInV2 = new short[INPUTSIZE * 4];
+ inV2.copyTo(arrayInV2);
+ short[] arrayOut = new short[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsUshortUshortUshort args = new ArgumentsUshortUshortUshort();
+ args.inV1 = arrayInV1[i * 4 + j];
+ args.inV2 = arrayInV2[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeMax(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV1: ");
+ message.append(String.format("0x%x", args.inV1));
+ message.append("\n");
+ message.append("Input inV2: ");
+ message.append(String.format("0x%x", args.inV2));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkMaxUshort3Ushort3Ushort3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkMaxUshort4Ushort4Ushort4() {
+ Allocation inV1 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_16, 4, 0xee9efa6405c77a79l, false);
+ Allocation inV2 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_16, 4, 0xee9efa6405c77a7al, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_16, 4), INPUTSIZE);
+ script.set_gAllocInV2(inV2);
+ script.forEach_testMaxUshort4Ushort4Ushort4(inV1, out);
+ verifyResultsMaxUshort4Ushort4Ushort4(inV1, inV2, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxUshort4Ushort4Ushort4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_16, 4), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV2(inV2);
+ scriptRelaxed.forEach_testMaxUshort4Ushort4Ushort4(inV1, out);
+ verifyResultsMaxUshort4Ushort4Ushort4(inV1, inV2, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxUshort4Ushort4Ushort4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsMaxUshort4Ushort4Ushort4(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
+ short[] arrayInV1 = new short[INPUTSIZE * 4];
+ inV1.copyTo(arrayInV1);
+ short[] arrayInV2 = new short[INPUTSIZE * 4];
+ inV2.copyTo(arrayInV2);
+ short[] arrayOut = new short[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsUshortUshortUshort args = new ArgumentsUshortUshortUshort();
+ args.inV1 = arrayInV1[i * 4 + j];
+ args.inV2 = arrayInV2[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeMax(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV1: ");
+ message.append(String.format("0x%x", args.inV1));
+ message.append("\n");
+ message.append("Input inV2: ");
+ message.append(String.format("0x%x", args.inV2));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkMaxUshort4Ushort4Ushort4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
public class ArgumentsIntIntInt {
public int inV1;
public int inV2;
@@ -676,342 +1468,6 @@
}
}
- public class ArgumentsUintUintUint {
- public int inV1;
- public int inV2;
- public int out;
- }
-
- private void checkMaxUintUintUint() {
- Allocation inV1 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_32, 1, 0x75328d17808776cal, false);
- Allocation inV2 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_32, 1, 0x75328d17808776cbl, false);
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_32, 1), INPUTSIZE);
- script.set_gAllocInV2(inV2);
- script.forEach_testMaxUintUintUint(inV1, out);
- verifyResultsMaxUintUintUint(inV1, inV2, out, false);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxUintUintUint: " + e.toString());
- }
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_32, 1), INPUTSIZE);
- scriptRelaxed.set_gAllocInV2(inV2);
- scriptRelaxed.forEach_testMaxUintUintUint(inV1, out);
- verifyResultsMaxUintUintUint(inV1, inV2, out, true);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxUintUintUint: " + e.toString());
- }
- }
-
- private void verifyResultsMaxUintUintUint(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
- int[] arrayInV1 = new int[INPUTSIZE * 1];
- inV1.copyTo(arrayInV1);
- int[] arrayInV2 = new int[INPUTSIZE * 1];
- inV2.copyTo(arrayInV2);
- int[] arrayOut = new int[INPUTSIZE * 1];
- out.copyTo(arrayOut);
- for (int i = 0; i < INPUTSIZE; i++) {
- for (int j = 0; j < 1 ; j++) {
- // Extract the inputs.
- ArgumentsUintUintUint args = new ArgumentsUintUintUint();
- args.inV1 = arrayInV1[i];
- args.inV2 = arrayInV2[i];
- // Figure out what the outputs should have been.
- Floaty.setRelaxed(relaxed);
- CoreMathVerifier.computeMax(args);
- // Figure out what the outputs should have been.
- boolean valid = true;
- if (args.out != arrayOut[i * 1 + j]) {
- valid = false;
- }
- if (!valid) {
- StringBuilder message = new StringBuilder();
- message.append("Input inV1: ");
- message.append(String.format("0x%x", args.inV1));
- message.append("\n");
- message.append("Input inV2: ");
- message.append(String.format("0x%x", args.inV2));
- message.append("\n");
- message.append("Expected output out: ");
- message.append(String.format("0x%x", args.out));
- message.append("\n");
- message.append("Actual output out: ");
- message.append(String.format("0x%x", arrayOut[i * 1 + j]));
- if (args.out != arrayOut[i * 1 + j]) {
- message.append(" FAIL");
- }
- message.append("\n");
- assertTrue("Incorrect output for checkMaxUintUintUint" +
- (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
- }
- }
- }
- }
-
- private void checkMaxChar2Char2Char2() {
- Allocation inV1 = createRandomAllocation(mRS, Element.DataType.SIGNED_8, 2, 0x12084b25952bc64l, false);
- Allocation inV2 = createRandomAllocation(mRS, Element.DataType.SIGNED_8, 2, 0x12084b25952bc65l, false);
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_8, 2), INPUTSIZE);
- script.set_gAllocInV2(inV2);
- script.forEach_testMaxChar2Char2Char2(inV1, out);
- verifyResultsMaxChar2Char2Char2(inV1, inV2, out, false);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxChar2Char2Char2: " + e.toString());
- }
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_8, 2), INPUTSIZE);
- scriptRelaxed.set_gAllocInV2(inV2);
- scriptRelaxed.forEach_testMaxChar2Char2Char2(inV1, out);
- verifyResultsMaxChar2Char2Char2(inV1, inV2, out, true);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxChar2Char2Char2: " + e.toString());
- }
- }
-
- private void verifyResultsMaxChar2Char2Char2(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
- byte[] arrayInV1 = new byte[INPUTSIZE * 2];
- inV1.copyTo(arrayInV1);
- byte[] arrayInV2 = new byte[INPUTSIZE * 2];
- inV2.copyTo(arrayInV2);
- byte[] arrayOut = new byte[INPUTSIZE * 2];
- out.copyTo(arrayOut);
- for (int i = 0; i < INPUTSIZE; i++) {
- for (int j = 0; j < 2 ; j++) {
- // Extract the inputs.
- ArgumentsCharCharChar args = new ArgumentsCharCharChar();
- args.inV1 = arrayInV1[i * 2 + j];
- args.inV2 = arrayInV2[i * 2 + j];
- // Figure out what the outputs should have been.
- Floaty.setRelaxed(relaxed);
- CoreMathVerifier.computeMax(args);
- // Figure out what the outputs should have been.
- boolean valid = true;
- if (args.out != arrayOut[i * 2 + j]) {
- valid = false;
- }
- if (!valid) {
- StringBuilder message = new StringBuilder();
- message.append("Input inV1: ");
- message.append(String.format("%d", args.inV1));
- message.append("\n");
- message.append("Input inV2: ");
- message.append(String.format("%d", args.inV2));
- message.append("\n");
- message.append("Expected output out: ");
- message.append(String.format("%d", args.out));
- message.append("\n");
- message.append("Actual output out: ");
- message.append(String.format("%d", arrayOut[i * 2 + j]));
- if (args.out != arrayOut[i * 2 + j]) {
- message.append(" FAIL");
- }
- message.append("\n");
- assertTrue("Incorrect output for checkMaxChar2Char2Char2" +
- (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
- }
- }
- }
- }
-
- private void checkMaxUchar2Uchar2Uchar2() {
- Allocation inV1 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_8, 2, 0x75eda605e43f8b81l, false);
- Allocation inV2 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_8, 2, 0x75eda605e43f8b82l, false);
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_8, 2), INPUTSIZE);
- script.set_gAllocInV2(inV2);
- script.forEach_testMaxUchar2Uchar2Uchar2(inV1, out);
- verifyResultsMaxUchar2Uchar2Uchar2(inV1, inV2, out, false);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxUchar2Uchar2Uchar2: " + e.toString());
- }
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_8, 2), INPUTSIZE);
- scriptRelaxed.set_gAllocInV2(inV2);
- scriptRelaxed.forEach_testMaxUchar2Uchar2Uchar2(inV1, out);
- verifyResultsMaxUchar2Uchar2Uchar2(inV1, inV2, out, true);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxUchar2Uchar2Uchar2: " + e.toString());
- }
- }
-
- private void verifyResultsMaxUchar2Uchar2Uchar2(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
- byte[] arrayInV1 = new byte[INPUTSIZE * 2];
- inV1.copyTo(arrayInV1);
- byte[] arrayInV2 = new byte[INPUTSIZE * 2];
- inV2.copyTo(arrayInV2);
- byte[] arrayOut = new byte[INPUTSIZE * 2];
- out.copyTo(arrayOut);
- for (int i = 0; i < INPUTSIZE; i++) {
- for (int j = 0; j < 2 ; j++) {
- // Extract the inputs.
- ArgumentsUcharUcharUchar args = new ArgumentsUcharUcharUchar();
- args.inV1 = arrayInV1[i * 2 + j];
- args.inV2 = arrayInV2[i * 2 + j];
- // Figure out what the outputs should have been.
- Floaty.setRelaxed(relaxed);
- CoreMathVerifier.computeMax(args);
- // Figure out what the outputs should have been.
- boolean valid = true;
- if (args.out != arrayOut[i * 2 + j]) {
- valid = false;
- }
- if (!valid) {
- StringBuilder message = new StringBuilder();
- message.append("Input inV1: ");
- message.append(String.format("0x%x", args.inV1));
- message.append("\n");
- message.append("Input inV2: ");
- message.append(String.format("0x%x", args.inV2));
- message.append("\n");
- message.append("Expected output out: ");
- message.append(String.format("0x%x", args.out));
- message.append("\n");
- message.append("Actual output out: ");
- message.append(String.format("0x%x", arrayOut[i * 2 + j]));
- if (args.out != arrayOut[i * 2 + j]) {
- message.append(" FAIL");
- }
- message.append("\n");
- assertTrue("Incorrect output for checkMaxUchar2Uchar2Uchar2" +
- (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
- }
- }
- }
- }
-
- private void checkMaxShort2Short2Short2() {
- Allocation inV1 = createRandomAllocation(mRS, Element.DataType.SIGNED_16, 2, 0x3d46ae0799c33c02l, false);
- Allocation inV2 = createRandomAllocation(mRS, Element.DataType.SIGNED_16, 2, 0x3d46ae0799c33c03l, false);
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_16, 2), INPUTSIZE);
- script.set_gAllocInV2(inV2);
- script.forEach_testMaxShort2Short2Short2(inV1, out);
- verifyResultsMaxShort2Short2Short2(inV1, inV2, out, false);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxShort2Short2Short2: " + e.toString());
- }
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_16, 2), INPUTSIZE);
- scriptRelaxed.set_gAllocInV2(inV2);
- scriptRelaxed.forEach_testMaxShort2Short2Short2(inV1, out);
- verifyResultsMaxShort2Short2Short2(inV1, inV2, out, true);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxShort2Short2Short2: " + e.toString());
- }
- }
-
- private void verifyResultsMaxShort2Short2Short2(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
- short[] arrayInV1 = new short[INPUTSIZE * 2];
- inV1.copyTo(arrayInV1);
- short[] arrayInV2 = new short[INPUTSIZE * 2];
- inV2.copyTo(arrayInV2);
- short[] arrayOut = new short[INPUTSIZE * 2];
- out.copyTo(arrayOut);
- for (int i = 0; i < INPUTSIZE; i++) {
- for (int j = 0; j < 2 ; j++) {
- // Extract the inputs.
- ArgumentsShortShortShort args = new ArgumentsShortShortShort();
- args.inV1 = arrayInV1[i * 2 + j];
- args.inV2 = arrayInV2[i * 2 + j];
- // Figure out what the outputs should have been.
- Floaty.setRelaxed(relaxed);
- CoreMathVerifier.computeMax(args);
- // Figure out what the outputs should have been.
- boolean valid = true;
- if (args.out != arrayOut[i * 2 + j]) {
- valid = false;
- }
- if (!valid) {
- StringBuilder message = new StringBuilder();
- message.append("Input inV1: ");
- message.append(String.format("%d", args.inV1));
- message.append("\n");
- message.append("Input inV2: ");
- message.append(String.format("%d", args.inV2));
- message.append("\n");
- message.append("Expected output out: ");
- message.append(String.format("%d", args.out));
- message.append("\n");
- message.append("Actual output out: ");
- message.append(String.format("%d", arrayOut[i * 2 + j]));
- if (args.out != arrayOut[i * 2 + j]) {
- message.append(" FAIL");
- }
- message.append("\n");
- assertTrue("Incorrect output for checkMaxShort2Short2Short2" +
- (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
- }
- }
- }
- }
-
- private void checkMaxUshort2Ushort2Ushort2() {
- Allocation inV1 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_16, 2, 0xf42196a588de51bfl, false);
- Allocation inV2 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_16, 2, 0xf42196a588de51c0l, false);
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_16, 2), INPUTSIZE);
- script.set_gAllocInV2(inV2);
- script.forEach_testMaxUshort2Ushort2Ushort2(inV1, out);
- verifyResultsMaxUshort2Ushort2Ushort2(inV1, inV2, out, false);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxUshort2Ushort2Ushort2: " + e.toString());
- }
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_16, 2), INPUTSIZE);
- scriptRelaxed.set_gAllocInV2(inV2);
- scriptRelaxed.forEach_testMaxUshort2Ushort2Ushort2(inV1, out);
- verifyResultsMaxUshort2Ushort2Ushort2(inV1, inV2, out, true);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxUshort2Ushort2Ushort2: " + e.toString());
- }
- }
-
- private void verifyResultsMaxUshort2Ushort2Ushort2(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
- short[] arrayInV1 = new short[INPUTSIZE * 2];
- inV1.copyTo(arrayInV1);
- short[] arrayInV2 = new short[INPUTSIZE * 2];
- inV2.copyTo(arrayInV2);
- short[] arrayOut = new short[INPUTSIZE * 2];
- out.copyTo(arrayOut);
- for (int i = 0; i < INPUTSIZE; i++) {
- for (int j = 0; j < 2 ; j++) {
- // Extract the inputs.
- ArgumentsUshortUshortUshort args = new ArgumentsUshortUshortUshort();
- args.inV1 = arrayInV1[i * 2 + j];
- args.inV2 = arrayInV2[i * 2 + j];
- // Figure out what the outputs should have been.
- Floaty.setRelaxed(relaxed);
- CoreMathVerifier.computeMax(args);
- // Figure out what the outputs should have been.
- boolean valid = true;
- if (args.out != arrayOut[i * 2 + j]) {
- valid = false;
- }
- if (!valid) {
- StringBuilder message = new StringBuilder();
- message.append("Input inV1: ");
- message.append(String.format("0x%x", args.inV1));
- message.append("\n");
- message.append("Input inV2: ");
- message.append(String.format("0x%x", args.inV2));
- message.append("\n");
- message.append("Expected output out: ");
- message.append(String.format("0x%x", args.out));
- message.append("\n");
- message.append("Actual output out: ");
- message.append(String.format("0x%x", arrayOut[i * 2 + j]));
- if (args.out != arrayOut[i * 2 + j]) {
- message.append(" FAIL");
- }
- message.append("\n");
- assertTrue("Incorrect output for checkMaxUshort2Ushort2Ushort2" +
- (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
- }
- }
- }
- }
-
private void checkMaxInt2Int2Int2() {
Allocation inV1 = createRandomAllocation(mRS, Element.DataType.SIGNED_32, 2, 0x7bba1e4a83816bd5l, false);
Allocation inV2 = createRandomAllocation(mRS, Element.DataType.SIGNED_32, 2, 0x7bba1e4a83816bd6l, false);
@@ -1078,336 +1534,6 @@
}
}
- private void checkMaxUint2Uint2Uint2() {
- Allocation inV1 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_32, 2, 0xcda90384705016a4l, false);
- Allocation inV2 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_32, 2, 0xcda90384705016a5l, false);
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_32, 2), INPUTSIZE);
- script.set_gAllocInV2(inV2);
- script.forEach_testMaxUint2Uint2Uint2(inV1, out);
- verifyResultsMaxUint2Uint2Uint2(inV1, inV2, out, false);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxUint2Uint2Uint2: " + e.toString());
- }
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_32, 2), INPUTSIZE);
- scriptRelaxed.set_gAllocInV2(inV2);
- scriptRelaxed.forEach_testMaxUint2Uint2Uint2(inV1, out);
- verifyResultsMaxUint2Uint2Uint2(inV1, inV2, out, true);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxUint2Uint2Uint2: " + e.toString());
- }
- }
-
- private void verifyResultsMaxUint2Uint2Uint2(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
- int[] arrayInV1 = new int[INPUTSIZE * 2];
- inV1.copyTo(arrayInV1);
- int[] arrayInV2 = new int[INPUTSIZE * 2];
- inV2.copyTo(arrayInV2);
- int[] arrayOut = new int[INPUTSIZE * 2];
- out.copyTo(arrayOut);
- for (int i = 0; i < INPUTSIZE; i++) {
- for (int j = 0; j < 2 ; j++) {
- // Extract the inputs.
- ArgumentsUintUintUint args = new ArgumentsUintUintUint();
- args.inV1 = arrayInV1[i * 2 + j];
- args.inV2 = arrayInV2[i * 2 + j];
- // Figure out what the outputs should have been.
- Floaty.setRelaxed(relaxed);
- CoreMathVerifier.computeMax(args);
- // Figure out what the outputs should have been.
- boolean valid = true;
- if (args.out != arrayOut[i * 2 + j]) {
- valid = false;
- }
- if (!valid) {
- StringBuilder message = new StringBuilder();
- message.append("Input inV1: ");
- message.append(String.format("0x%x", args.inV1));
- message.append("\n");
- message.append("Input inV2: ");
- message.append(String.format("0x%x", args.inV2));
- message.append("\n");
- message.append("Expected output out: ");
- message.append(String.format("0x%x", args.out));
- message.append("\n");
- message.append("Actual output out: ");
- message.append(String.format("0x%x", arrayOut[i * 2 + j]));
- if (args.out != arrayOut[i * 2 + j]) {
- message.append(" FAIL");
- }
- message.append("\n");
- assertTrue("Incorrect output for checkMaxUint2Uint2Uint2" +
- (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
- }
- }
- }
- }
-
- private void checkMaxChar3Char3Char3() {
- Allocation inV1 = createRandomAllocation(mRS, Element.DataType.SIGNED_8, 3, 0x567200e53e0a8f29l, false);
- Allocation inV2 = createRandomAllocation(mRS, Element.DataType.SIGNED_8, 3, 0x567200e53e0a8f2al, false);
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_8, 3), INPUTSIZE);
- script.set_gAllocInV2(inV2);
- script.forEach_testMaxChar3Char3Char3(inV1, out);
- verifyResultsMaxChar3Char3Char3(inV1, inV2, out, false);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxChar3Char3Char3: " + e.toString());
- }
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_8, 3), INPUTSIZE);
- scriptRelaxed.set_gAllocInV2(inV2);
- scriptRelaxed.forEach_testMaxChar3Char3Char3(inV1, out);
- verifyResultsMaxChar3Char3Char3(inV1, inV2, out, true);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxChar3Char3Char3: " + e.toString());
- }
- }
-
- private void verifyResultsMaxChar3Char3Char3(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
- byte[] arrayInV1 = new byte[INPUTSIZE * 4];
- inV1.copyTo(arrayInV1);
- byte[] arrayInV2 = new byte[INPUTSIZE * 4];
- inV2.copyTo(arrayInV2);
- byte[] arrayOut = new byte[INPUTSIZE * 4];
- out.copyTo(arrayOut);
- for (int i = 0; i < INPUTSIZE; i++) {
- for (int j = 0; j < 3 ; j++) {
- // Extract the inputs.
- ArgumentsCharCharChar args = new ArgumentsCharCharChar();
- args.inV1 = arrayInV1[i * 4 + j];
- args.inV2 = arrayInV2[i * 4 + j];
- // Figure out what the outputs should have been.
- Floaty.setRelaxed(relaxed);
- CoreMathVerifier.computeMax(args);
- // Figure out what the outputs should have been.
- boolean valid = true;
- if (args.out != arrayOut[i * 4 + j]) {
- valid = false;
- }
- if (!valid) {
- StringBuilder message = new StringBuilder();
- message.append("Input inV1: ");
- message.append(String.format("%d", args.inV1));
- message.append("\n");
- message.append("Input inV2: ");
- message.append(String.format("%d", args.inV2));
- message.append("\n");
- message.append("Expected output out: ");
- message.append(String.format("%d", args.out));
- message.append("\n");
- message.append("Actual output out: ");
- message.append(String.format("%d", arrayOut[i * 4 + j]));
- if (args.out != arrayOut[i * 4 + j]) {
- message.append(" FAIL");
- }
- message.append("\n");
- assertTrue("Incorrect output for checkMaxChar3Char3Char3" +
- (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
- }
- }
- }
- }
-
- private void checkMaxUchar3Uchar3Uchar3() {
- Allocation inV1 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_8, 3, 0xa2def5663489d18cl, false);
- Allocation inV2 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_8, 3, 0xa2def5663489d18dl, false);
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_8, 3), INPUTSIZE);
- script.set_gAllocInV2(inV2);
- script.forEach_testMaxUchar3Uchar3Uchar3(inV1, out);
- verifyResultsMaxUchar3Uchar3Uchar3(inV1, inV2, out, false);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxUchar3Uchar3Uchar3: " + e.toString());
- }
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_8, 3), INPUTSIZE);
- scriptRelaxed.set_gAllocInV2(inV2);
- scriptRelaxed.forEach_testMaxUchar3Uchar3Uchar3(inV1, out);
- verifyResultsMaxUchar3Uchar3Uchar3(inV1, inV2, out, true);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxUchar3Uchar3Uchar3: " + e.toString());
- }
- }
-
- private void verifyResultsMaxUchar3Uchar3Uchar3(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
- byte[] arrayInV1 = new byte[INPUTSIZE * 4];
- inV1.copyTo(arrayInV1);
- byte[] arrayInV2 = new byte[INPUTSIZE * 4];
- inV2.copyTo(arrayInV2);
- byte[] arrayOut = new byte[INPUTSIZE * 4];
- out.copyTo(arrayOut);
- for (int i = 0; i < INPUTSIZE; i++) {
- for (int j = 0; j < 3 ; j++) {
- // Extract the inputs.
- ArgumentsUcharUcharUchar args = new ArgumentsUcharUcharUchar();
- args.inV1 = arrayInV1[i * 4 + j];
- args.inV2 = arrayInV2[i * 4 + j];
- // Figure out what the outputs should have been.
- Floaty.setRelaxed(relaxed);
- CoreMathVerifier.computeMax(args);
- // Figure out what the outputs should have been.
- boolean valid = true;
- if (args.out != arrayOut[i * 4 + j]) {
- valid = false;
- }
- if (!valid) {
- StringBuilder message = new StringBuilder();
- message.append("Input inV1: ");
- message.append(String.format("0x%x", args.inV1));
- message.append("\n");
- message.append("Input inV2: ");
- message.append(String.format("0x%x", args.inV2));
- message.append("\n");
- message.append("Expected output out: ");
- message.append(String.format("0x%x", args.out));
- message.append("\n");
- message.append("Actual output out: ");
- message.append(String.format("0x%x", arrayOut[i * 4 + j]));
- if (args.out != arrayOut[i * 4 + j]) {
- message.append(" FAIL");
- }
- message.append("\n");
- assertTrue("Incorrect output for checkMaxUchar3Uchar3Uchar3" +
- (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
- }
- }
- }
- }
-
- private void checkMaxShort3Short3Short3() {
- Allocation inV1 = createRandomAllocation(mRS, Element.DataType.SIGNED_16, 3, 0x6a37fd67ea0d820dl, false);
- Allocation inV2 = createRandomAllocation(mRS, Element.DataType.SIGNED_16, 3, 0x6a37fd67ea0d820el, false);
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_16, 3), INPUTSIZE);
- script.set_gAllocInV2(inV2);
- script.forEach_testMaxShort3Short3Short3(inV1, out);
- verifyResultsMaxShort3Short3Short3(inV1, inV2, out, false);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxShort3Short3Short3: " + e.toString());
- }
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_16, 3), INPUTSIZE);
- scriptRelaxed.set_gAllocInV2(inV2);
- scriptRelaxed.forEach_testMaxShort3Short3Short3(inV1, out);
- verifyResultsMaxShort3Short3Short3(inV1, inV2, out, true);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxShort3Short3Short3: " + e.toString());
- }
- }
-
- private void verifyResultsMaxShort3Short3Short3(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
- short[] arrayInV1 = new short[INPUTSIZE * 4];
- inV1.copyTo(arrayInV1);
- short[] arrayInV2 = new short[INPUTSIZE * 4];
- inV2.copyTo(arrayInV2);
- short[] arrayOut = new short[INPUTSIZE * 4];
- out.copyTo(arrayOut);
- for (int i = 0; i < INPUTSIZE; i++) {
- for (int j = 0; j < 3 ; j++) {
- // Extract the inputs.
- ArgumentsShortShortShort args = new ArgumentsShortShortShort();
- args.inV1 = arrayInV1[i * 4 + j];
- args.inV2 = arrayInV2[i * 4 + j];
- // Figure out what the outputs should have been.
- Floaty.setRelaxed(relaxed);
- CoreMathVerifier.computeMax(args);
- // Figure out what the outputs should have been.
- boolean valid = true;
- if (args.out != arrayOut[i * 4 + j]) {
- valid = false;
- }
- if (!valid) {
- StringBuilder message = new StringBuilder();
- message.append("Input inV1: ");
- message.append(String.format("%d", args.inV1));
- message.append("\n");
- message.append("Input inV2: ");
- message.append(String.format("%d", args.inV2));
- message.append("\n");
- message.append("Expected output out: ");
- message.append(String.format("%d", args.out));
- message.append("\n");
- message.append("Actual output out: ");
- message.append(String.format("%d", arrayOut[i * 4 + j]));
- if (args.out != arrayOut[i * 4 + j]) {
- message.append(" FAIL");
- }
- message.append("\n");
- assertTrue("Incorrect output for checkMaxShort3Short3Short3" +
- (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
- }
- }
- }
- }
-
- private void checkMaxUshort3Ushort3Ushort3() {
- Allocation inV1 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_16, 3, 0x71604884c752e61cl, false);
- Allocation inV2 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_16, 3, 0x71604884c752e61dl, false);
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_16, 3), INPUTSIZE);
- script.set_gAllocInV2(inV2);
- script.forEach_testMaxUshort3Ushort3Ushort3(inV1, out);
- verifyResultsMaxUshort3Ushort3Ushort3(inV1, inV2, out, false);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxUshort3Ushort3Ushort3: " + e.toString());
- }
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_16, 3), INPUTSIZE);
- scriptRelaxed.set_gAllocInV2(inV2);
- scriptRelaxed.forEach_testMaxUshort3Ushort3Ushort3(inV1, out);
- verifyResultsMaxUshort3Ushort3Ushort3(inV1, inV2, out, true);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxUshort3Ushort3Ushort3: " + e.toString());
- }
- }
-
- private void verifyResultsMaxUshort3Ushort3Ushort3(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
- short[] arrayInV1 = new short[INPUTSIZE * 4];
- inV1.copyTo(arrayInV1);
- short[] arrayInV2 = new short[INPUTSIZE * 4];
- inV2.copyTo(arrayInV2);
- short[] arrayOut = new short[INPUTSIZE * 4];
- out.copyTo(arrayOut);
- for (int i = 0; i < INPUTSIZE; i++) {
- for (int j = 0; j < 3 ; j++) {
- // Extract the inputs.
- ArgumentsUshortUshortUshort args = new ArgumentsUshortUshortUshort();
- args.inV1 = arrayInV1[i * 4 + j];
- args.inV2 = arrayInV2[i * 4 + j];
- // Figure out what the outputs should have been.
- Floaty.setRelaxed(relaxed);
- CoreMathVerifier.computeMax(args);
- // Figure out what the outputs should have been.
- boolean valid = true;
- if (args.out != arrayOut[i * 4 + j]) {
- valid = false;
- }
- if (!valid) {
- StringBuilder message = new StringBuilder();
- message.append("Input inV1: ");
- message.append(String.format("0x%x", args.inV1));
- message.append("\n");
- message.append("Input inV2: ");
- message.append(String.format("0x%x", args.inV2));
- message.append("\n");
- message.append("Expected output out: ");
- message.append(String.format("0x%x", args.out));
- message.append("\n");
- message.append("Actual output out: ");
- message.append(String.format("0x%x", arrayOut[i * 4 + j]));
- if (args.out != arrayOut[i * 4 + j]) {
- message.append(" FAIL");
- }
- message.append("\n");
- assertTrue("Incorrect output for checkMaxUshort3Ushort3Ushort3" +
- (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
- }
- }
- }
- }
-
private void checkMaxInt3Int3Int3() {
Allocation inV1 = createRandomAllocation(mRS, Element.DataType.SIGNED_32, 3, 0xa647496a95547ff8l, false);
Allocation inV2 = createRandomAllocation(mRS, Element.DataType.SIGNED_32, 3, 0xa647496a95547ff9l, false);
@@ -1474,336 +1600,6 @@
}
}
- private void checkMaxUint3Uint3Uint3() {
- Allocation inV1 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_32, 3, 0x22fa7fb75507e969l, false);
- Allocation inV2 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_32, 3, 0x22fa7fb75507e96al, false);
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_32, 3), INPUTSIZE);
- script.set_gAllocInV2(inV2);
- script.forEach_testMaxUint3Uint3Uint3(inV1, out);
- verifyResultsMaxUint3Uint3Uint3(inV1, inV2, out, false);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxUint3Uint3Uint3: " + e.toString());
- }
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_32, 3), INPUTSIZE);
- scriptRelaxed.set_gAllocInV2(inV2);
- scriptRelaxed.forEach_testMaxUint3Uint3Uint3(inV1, out);
- verifyResultsMaxUint3Uint3Uint3(inV1, inV2, out, true);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxUint3Uint3Uint3: " + e.toString());
- }
- }
-
- private void verifyResultsMaxUint3Uint3Uint3(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
- int[] arrayInV1 = new int[INPUTSIZE * 4];
- inV1.copyTo(arrayInV1);
- int[] arrayInV2 = new int[INPUTSIZE * 4];
- inV2.copyTo(arrayInV2);
- int[] arrayOut = new int[INPUTSIZE * 4];
- out.copyTo(arrayOut);
- for (int i = 0; i < INPUTSIZE; i++) {
- for (int j = 0; j < 3 ; j++) {
- // Extract the inputs.
- ArgumentsUintUintUint args = new ArgumentsUintUintUint();
- args.inV1 = arrayInV1[i * 4 + j];
- args.inV2 = arrayInV2[i * 4 + j];
- // Figure out what the outputs should have been.
- Floaty.setRelaxed(relaxed);
- CoreMathVerifier.computeMax(args);
- // Figure out what the outputs should have been.
- boolean valid = true;
- if (args.out != arrayOut[i * 4 + j]) {
- valid = false;
- }
- if (!valid) {
- StringBuilder message = new StringBuilder();
- message.append("Input inV1: ");
- message.append(String.format("0x%x", args.inV1));
- message.append("\n");
- message.append("Input inV2: ");
- message.append(String.format("0x%x", args.inV2));
- message.append("\n");
- message.append("Expected output out: ");
- message.append(String.format("0x%x", args.out));
- message.append("\n");
- message.append("Actual output out: ");
- message.append(String.format("0x%x", arrayOut[i * 4 + j]));
- if (args.out != arrayOut[i * 4 + j]) {
- message.append(" FAIL");
- }
- message.append("\n");
- assertTrue("Incorrect output for checkMaxUint3Uint3Uint3" +
- (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
- }
- }
- }
- }
-
- private void checkMaxChar4Char4Char4() {
- Allocation inV1 = createRandomAllocation(mRS, Element.DataType.SIGNED_8, 4, 0xabc37d1822c261eel, false);
- Allocation inV2 = createRandomAllocation(mRS, Element.DataType.SIGNED_8, 4, 0xabc37d1822c261efl, false);
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_8, 4), INPUTSIZE);
- script.set_gAllocInV2(inV2);
- script.forEach_testMaxChar4Char4Char4(inV1, out);
- verifyResultsMaxChar4Char4Char4(inV1, inV2, out, false);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxChar4Char4Char4: " + e.toString());
- }
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_8, 4), INPUTSIZE);
- scriptRelaxed.set_gAllocInV2(inV2);
- scriptRelaxed.forEach_testMaxChar4Char4Char4(inV1, out);
- verifyResultsMaxChar4Char4Char4(inV1, inV2, out, true);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxChar4Char4Char4: " + e.toString());
- }
- }
-
- private void verifyResultsMaxChar4Char4Char4(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
- byte[] arrayInV1 = new byte[INPUTSIZE * 4];
- inV1.copyTo(arrayInV1);
- byte[] arrayInV2 = new byte[INPUTSIZE * 4];
- inV2.copyTo(arrayInV2);
- byte[] arrayOut = new byte[INPUTSIZE * 4];
- out.copyTo(arrayOut);
- for (int i = 0; i < INPUTSIZE; i++) {
- for (int j = 0; j < 4 ; j++) {
- // Extract the inputs.
- ArgumentsCharCharChar args = new ArgumentsCharCharChar();
- args.inV1 = arrayInV1[i * 4 + j];
- args.inV2 = arrayInV2[i * 4 + j];
- // Figure out what the outputs should have been.
- Floaty.setRelaxed(relaxed);
- CoreMathVerifier.computeMax(args);
- // Figure out what the outputs should have been.
- boolean valid = true;
- if (args.out != arrayOut[i * 4 + j]) {
- valid = false;
- }
- if (!valid) {
- StringBuilder message = new StringBuilder();
- message.append("Input inV1: ");
- message.append(String.format("%d", args.inV1));
- message.append("\n");
- message.append("Input inV2: ");
- message.append(String.format("%d", args.inV2));
- message.append("\n");
- message.append("Expected output out: ");
- message.append(String.format("%d", args.out));
- message.append("\n");
- message.append("Actual output out: ");
- message.append(String.format("%d", arrayOut[i * 4 + j]));
- if (args.out != arrayOut[i * 4 + j]) {
- message.append(" FAIL");
- }
- message.append("\n");
- assertTrue("Incorrect output for checkMaxChar4Char4Char4" +
- (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
- }
- }
- }
- }
-
- private void checkMaxUchar4Uchar4Uchar4() {
- Allocation inV1 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_8, 4, 0xcfd044c684d41797l, false);
- Allocation inV2 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_8, 4, 0xcfd044c684d41798l, false);
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_8, 4), INPUTSIZE);
- script.set_gAllocInV2(inV2);
- script.forEach_testMaxUchar4Uchar4Uchar4(inV1, out);
- verifyResultsMaxUchar4Uchar4Uchar4(inV1, inV2, out, false);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxUchar4Uchar4Uchar4: " + e.toString());
- }
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_8, 4), INPUTSIZE);
- scriptRelaxed.set_gAllocInV2(inV2);
- scriptRelaxed.forEach_testMaxUchar4Uchar4Uchar4(inV1, out);
- verifyResultsMaxUchar4Uchar4Uchar4(inV1, inV2, out, true);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxUchar4Uchar4Uchar4: " + e.toString());
- }
- }
-
- private void verifyResultsMaxUchar4Uchar4Uchar4(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
- byte[] arrayInV1 = new byte[INPUTSIZE * 4];
- inV1.copyTo(arrayInV1);
- byte[] arrayInV2 = new byte[INPUTSIZE * 4];
- inV2.copyTo(arrayInV2);
- byte[] arrayOut = new byte[INPUTSIZE * 4];
- out.copyTo(arrayOut);
- for (int i = 0; i < INPUTSIZE; i++) {
- for (int j = 0; j < 4 ; j++) {
- // Extract the inputs.
- ArgumentsUcharUcharUchar args = new ArgumentsUcharUcharUchar();
- args.inV1 = arrayInV1[i * 4 + j];
- args.inV2 = arrayInV2[i * 4 + j];
- // Figure out what the outputs should have been.
- Floaty.setRelaxed(relaxed);
- CoreMathVerifier.computeMax(args);
- // Figure out what the outputs should have been.
- boolean valid = true;
- if (args.out != arrayOut[i * 4 + j]) {
- valid = false;
- }
- if (!valid) {
- StringBuilder message = new StringBuilder();
- message.append("Input inV1: ");
- message.append(String.format("0x%x", args.inV1));
- message.append("\n");
- message.append("Input inV2: ");
- message.append(String.format("0x%x", args.inV2));
- message.append("\n");
- message.append("Expected output out: ");
- message.append(String.format("0x%x", args.out));
- message.append("\n");
- message.append("Actual output out: ");
- message.append(String.format("0x%x", arrayOut[i * 4 + j]));
- if (args.out != arrayOut[i * 4 + j]) {
- message.append(" FAIL");
- }
- message.append("\n");
- assertTrue("Incorrect output for checkMaxUchar4Uchar4Uchar4" +
- (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
- }
- }
- }
- }
-
- private void checkMaxShort4Short4Short4() {
- Allocation inV1 = createRandomAllocation(mRS, Element.DataType.SIGNED_16, 4, 0x97294cc83a57c818l, false);
- Allocation inV2 = createRandomAllocation(mRS, Element.DataType.SIGNED_16, 4, 0x97294cc83a57c819l, false);
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_16, 4), INPUTSIZE);
- script.set_gAllocInV2(inV2);
- script.forEach_testMaxShort4Short4Short4(inV1, out);
- verifyResultsMaxShort4Short4Short4(inV1, inV2, out, false);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxShort4Short4Short4: " + e.toString());
- }
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_16, 4), INPUTSIZE);
- scriptRelaxed.set_gAllocInV2(inV2);
- scriptRelaxed.forEach_testMaxShort4Short4Short4(inV1, out);
- verifyResultsMaxShort4Short4Short4(inV1, inV2, out, true);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxShort4Short4Short4: " + e.toString());
- }
- }
-
- private void verifyResultsMaxShort4Short4Short4(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
- short[] arrayInV1 = new short[INPUTSIZE * 4];
- inV1.copyTo(arrayInV1);
- short[] arrayInV2 = new short[INPUTSIZE * 4];
- inV2.copyTo(arrayInV2);
- short[] arrayOut = new short[INPUTSIZE * 4];
- out.copyTo(arrayOut);
- for (int i = 0; i < INPUTSIZE; i++) {
- for (int j = 0; j < 4 ; j++) {
- // Extract the inputs.
- ArgumentsShortShortShort args = new ArgumentsShortShortShort();
- args.inV1 = arrayInV1[i * 4 + j];
- args.inV2 = arrayInV2[i * 4 + j];
- // Figure out what the outputs should have been.
- Floaty.setRelaxed(relaxed);
- CoreMathVerifier.computeMax(args);
- // Figure out what the outputs should have been.
- boolean valid = true;
- if (args.out != arrayOut[i * 4 + j]) {
- valid = false;
- }
- if (!valid) {
- StringBuilder message = new StringBuilder();
- message.append("Input inV1: ");
- message.append(String.format("%d", args.inV1));
- message.append("\n");
- message.append("Input inV2: ");
- message.append(String.format("%d", args.inV2));
- message.append("\n");
- message.append("Expected output out: ");
- message.append(String.format("%d", args.out));
- message.append("\n");
- message.append("Actual output out: ");
- message.append(String.format("%d", arrayOut[i * 4 + j]));
- if (args.out != arrayOut[i * 4 + j]) {
- message.append(" FAIL");
- }
- message.append("\n");
- assertTrue("Incorrect output for checkMaxShort4Short4Short4" +
- (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
- }
- }
- }
- }
-
- private void checkMaxUshort4Ushort4Ushort4() {
- Allocation inV1 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_16, 4, 0xee9efa6405c77a79l, false);
- Allocation inV2 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_16, 4, 0xee9efa6405c77a7al, false);
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_16, 4), INPUTSIZE);
- script.set_gAllocInV2(inV2);
- script.forEach_testMaxUshort4Ushort4Ushort4(inV1, out);
- verifyResultsMaxUshort4Ushort4Ushort4(inV1, inV2, out, false);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxUshort4Ushort4Ushort4: " + e.toString());
- }
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_16, 4), INPUTSIZE);
- scriptRelaxed.set_gAllocInV2(inV2);
- scriptRelaxed.forEach_testMaxUshort4Ushort4Ushort4(inV1, out);
- verifyResultsMaxUshort4Ushort4Ushort4(inV1, inV2, out, true);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxUshort4Ushort4Ushort4: " + e.toString());
- }
- }
-
- private void verifyResultsMaxUshort4Ushort4Ushort4(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
- short[] arrayInV1 = new short[INPUTSIZE * 4];
- inV1.copyTo(arrayInV1);
- short[] arrayInV2 = new short[INPUTSIZE * 4];
- inV2.copyTo(arrayInV2);
- short[] arrayOut = new short[INPUTSIZE * 4];
- out.copyTo(arrayOut);
- for (int i = 0; i < INPUTSIZE; i++) {
- for (int j = 0; j < 4 ; j++) {
- // Extract the inputs.
- ArgumentsUshortUshortUshort args = new ArgumentsUshortUshortUshort();
- args.inV1 = arrayInV1[i * 4 + j];
- args.inV2 = arrayInV2[i * 4 + j];
- // Figure out what the outputs should have been.
- Floaty.setRelaxed(relaxed);
- CoreMathVerifier.computeMax(args);
- // Figure out what the outputs should have been.
- boolean valid = true;
- if (args.out != arrayOut[i * 4 + j]) {
- valid = false;
- }
- if (!valid) {
- StringBuilder message = new StringBuilder();
- message.append("Input inV1: ");
- message.append(String.format("0x%x", args.inV1));
- message.append("\n");
- message.append("Input inV2: ");
- message.append(String.format("0x%x", args.inV2));
- message.append("\n");
- message.append("Expected output out: ");
- message.append(String.format("0x%x", args.out));
- message.append("\n");
- message.append("Actual output out: ");
- message.append(String.format("0x%x", arrayOut[i * 4 + j]));
- if (args.out != arrayOut[i * 4 + j]) {
- message.append(" FAIL");
- }
- message.append("\n");
- assertTrue("Incorrect output for checkMaxUshort4Ushort4Ushort4" +
- (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
- }
- }
- }
- }
-
private void checkMaxInt4Int4Int4() {
Allocation inV1 = createRandomAllocation(mRS, Element.DataType.SIGNED_32, 4, 0xd0d4748aa727941bl, false);
Allocation inV2 = createRandomAllocation(mRS, Element.DataType.SIGNED_32, 4, 0xd0d4748aa727941cl, false);
@@ -1870,6 +1666,210 @@
}
}
+ public class ArgumentsUintUintUint {
+ public int inV1;
+ public int inV2;
+ public int out;
+ }
+
+ private void checkMaxUintUintUint() {
+ Allocation inV1 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_32, 1, 0x75328d17808776cal, false);
+ Allocation inV2 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_32, 1, 0x75328d17808776cbl, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_32, 1), INPUTSIZE);
+ script.set_gAllocInV2(inV2);
+ script.forEach_testMaxUintUintUint(inV1, out);
+ verifyResultsMaxUintUintUint(inV1, inV2, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxUintUintUint: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_32, 1), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV2(inV2);
+ scriptRelaxed.forEach_testMaxUintUintUint(inV1, out);
+ verifyResultsMaxUintUintUint(inV1, inV2, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxUintUintUint: " + e.toString());
+ }
+ }
+
+ private void verifyResultsMaxUintUintUint(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
+ int[] arrayInV1 = new int[INPUTSIZE * 1];
+ inV1.copyTo(arrayInV1);
+ int[] arrayInV2 = new int[INPUTSIZE * 1];
+ inV2.copyTo(arrayInV2);
+ int[] arrayOut = new int[INPUTSIZE * 1];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 1 ; j++) {
+ // Extract the inputs.
+ ArgumentsUintUintUint args = new ArgumentsUintUintUint();
+ args.inV1 = arrayInV1[i];
+ args.inV2 = arrayInV2[i];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeMax(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 1 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV1: ");
+ message.append(String.format("0x%x", args.inV1));
+ message.append("\n");
+ message.append("Input inV2: ");
+ message.append(String.format("0x%x", args.inV2));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 1 + j]));
+ if (args.out != arrayOut[i * 1 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkMaxUintUintUint" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkMaxUint2Uint2Uint2() {
+ Allocation inV1 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_32, 2, 0xcda90384705016a4l, false);
+ Allocation inV2 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_32, 2, 0xcda90384705016a5l, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_32, 2), INPUTSIZE);
+ script.set_gAllocInV2(inV2);
+ script.forEach_testMaxUint2Uint2Uint2(inV1, out);
+ verifyResultsMaxUint2Uint2Uint2(inV1, inV2, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxUint2Uint2Uint2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_32, 2), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV2(inV2);
+ scriptRelaxed.forEach_testMaxUint2Uint2Uint2(inV1, out);
+ verifyResultsMaxUint2Uint2Uint2(inV1, inV2, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxUint2Uint2Uint2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsMaxUint2Uint2Uint2(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
+ int[] arrayInV1 = new int[INPUTSIZE * 2];
+ inV1.copyTo(arrayInV1);
+ int[] arrayInV2 = new int[INPUTSIZE * 2];
+ inV2.copyTo(arrayInV2);
+ int[] arrayOut = new int[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsUintUintUint args = new ArgumentsUintUintUint();
+ args.inV1 = arrayInV1[i * 2 + j];
+ args.inV2 = arrayInV2[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeMax(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV1: ");
+ message.append(String.format("0x%x", args.inV1));
+ message.append("\n");
+ message.append("Input inV2: ");
+ message.append(String.format("0x%x", args.inV2));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkMaxUint2Uint2Uint2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkMaxUint3Uint3Uint3() {
+ Allocation inV1 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_32, 3, 0x22fa7fb75507e969l, false);
+ Allocation inV2 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_32, 3, 0x22fa7fb75507e96al, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_32, 3), INPUTSIZE);
+ script.set_gAllocInV2(inV2);
+ script.forEach_testMaxUint3Uint3Uint3(inV1, out);
+ verifyResultsMaxUint3Uint3Uint3(inV1, inV2, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxUint3Uint3Uint3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_32, 3), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV2(inV2);
+ scriptRelaxed.forEach_testMaxUint3Uint3Uint3(inV1, out);
+ verifyResultsMaxUint3Uint3Uint3(inV1, inV2, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxUint3Uint3Uint3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsMaxUint3Uint3Uint3(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
+ int[] arrayInV1 = new int[INPUTSIZE * 4];
+ inV1.copyTo(arrayInV1);
+ int[] arrayInV2 = new int[INPUTSIZE * 4];
+ inV2.copyTo(arrayInV2);
+ int[] arrayOut = new int[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsUintUintUint args = new ArgumentsUintUintUint();
+ args.inV1 = arrayInV1[i * 4 + j];
+ args.inV2 = arrayInV2[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeMax(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV1: ");
+ message.append(String.format("0x%x", args.inV1));
+ message.append("\n");
+ message.append("Input inV2: ");
+ message.append(String.format("0x%x", args.inV2));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkMaxUint3Uint3Uint3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
private void checkMaxUint4Uint4Uint4() {
Allocation inV1 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_32, 4, 0x784bfbea39bfbc2el, false);
Allocation inV2 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_32, 4, 0x784bfbea39bfbc2fl, false);
@@ -1936,34 +1936,582 @@
}
}
+ public class ArgumentsLongLongLong {
+ public long inV1;
+ public long inV2;
+ public long out;
+ }
+
+ private void checkMaxLongLongLong() {
+ Allocation inV1 = createRandomAllocation(mRS, Element.DataType.SIGNED_64, 1, 0xe224db3c7ecb92e4l, false);
+ Allocation inV2 = createRandomAllocation(mRS, Element.DataType.SIGNED_64, 1, 0xe224db3c7ecb92e5l, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 1), INPUTSIZE);
+ script.set_gAllocInV2(inV2);
+ script.forEach_testMaxLongLongLong(inV1, out);
+ verifyResultsMaxLongLongLong(inV1, inV2, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxLongLongLong: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 1), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV2(inV2);
+ scriptRelaxed.forEach_testMaxLongLongLong(inV1, out);
+ verifyResultsMaxLongLongLong(inV1, inV2, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxLongLongLong: " + e.toString());
+ }
+ }
+
+ private void verifyResultsMaxLongLongLong(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
+ long[] arrayInV1 = new long[INPUTSIZE * 1];
+ inV1.copyTo(arrayInV1);
+ long[] arrayInV2 = new long[INPUTSIZE * 1];
+ inV2.copyTo(arrayInV2);
+ long[] arrayOut = new long[INPUTSIZE * 1];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 1 ; j++) {
+ // Extract the inputs.
+ ArgumentsLongLongLong args = new ArgumentsLongLongLong();
+ args.inV1 = arrayInV1[i];
+ args.inV2 = arrayInV2[i];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeMax(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 1 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV1: ");
+ message.append(String.format("%d", args.inV1));
+ message.append("\n");
+ message.append("Input inV2: ");
+ message.append(String.format("%d", args.inV2));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 1 + j]));
+ if (args.out != arrayOut[i * 1 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkMaxLongLongLong" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkMaxLong2Long2Long2() {
+ Allocation inV1 = createRandomAllocation(mRS, Element.DataType.SIGNED_64, 2, 0x375f5f0ca264eb56l, false);
+ Allocation inV2 = createRandomAllocation(mRS, Element.DataType.SIGNED_64, 2, 0x375f5f0ca264eb57l, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 2), INPUTSIZE);
+ script.set_gAllocInV2(inV2);
+ script.forEach_testMaxLong2Long2Long2(inV1, out);
+ verifyResultsMaxLong2Long2Long2(inV1, inV2, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxLong2Long2Long2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 2), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV2(inV2);
+ scriptRelaxed.forEach_testMaxLong2Long2Long2(inV1, out);
+ verifyResultsMaxLong2Long2Long2(inV1, inV2, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxLong2Long2Long2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsMaxLong2Long2Long2(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
+ long[] arrayInV1 = new long[INPUTSIZE * 2];
+ inV1.copyTo(arrayInV1);
+ long[] arrayInV2 = new long[INPUTSIZE * 2];
+ inV2.copyTo(arrayInV2);
+ long[] arrayOut = new long[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsLongLongLong args = new ArgumentsLongLongLong();
+ args.inV1 = arrayInV1[i * 2 + j];
+ args.inV2 = arrayInV2[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeMax(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV1: ");
+ message.append(String.format("%d", args.inV1));
+ message.append("\n");
+ message.append("Input inV2: ");
+ message.append(String.format("%d", args.inV2));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkMaxLong2Long2Long2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkMaxLong3Long3Long3() {
+ Allocation inV1 = createRandomAllocation(mRS, Element.DataType.SIGNED_64, 3, 0x8cb0db3f871cbe1bl, false);
+ Allocation inV2 = createRandomAllocation(mRS, Element.DataType.SIGNED_64, 3, 0x8cb0db3f871cbe1cl, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 3), INPUTSIZE);
+ script.set_gAllocInV2(inV2);
+ script.forEach_testMaxLong3Long3Long3(inV1, out);
+ verifyResultsMaxLong3Long3Long3(inV1, inV2, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxLong3Long3Long3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 3), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV2(inV2);
+ scriptRelaxed.forEach_testMaxLong3Long3Long3(inV1, out);
+ verifyResultsMaxLong3Long3Long3(inV1, inV2, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxLong3Long3Long3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsMaxLong3Long3Long3(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
+ long[] arrayInV1 = new long[INPUTSIZE * 4];
+ inV1.copyTo(arrayInV1);
+ long[] arrayInV2 = new long[INPUTSIZE * 4];
+ inV2.copyTo(arrayInV2);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsLongLongLong args = new ArgumentsLongLongLong();
+ args.inV1 = arrayInV1[i * 4 + j];
+ args.inV2 = arrayInV2[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeMax(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV1: ");
+ message.append(String.format("%d", args.inV1));
+ message.append("\n");
+ message.append("Input inV2: ");
+ message.append(String.format("%d", args.inV2));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkMaxLong3Long3Long3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkMaxLong4Long4Long4() {
+ Allocation inV1 = createRandomAllocation(mRS, Element.DataType.SIGNED_64, 4, 0xe20257726bd490e0l, false);
+ Allocation inV2 = createRandomAllocation(mRS, Element.DataType.SIGNED_64, 4, 0xe20257726bd490e1l, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 4), INPUTSIZE);
+ script.set_gAllocInV2(inV2);
+ script.forEach_testMaxLong4Long4Long4(inV1, out);
+ verifyResultsMaxLong4Long4Long4(inV1, inV2, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxLong4Long4Long4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 4), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV2(inV2);
+ scriptRelaxed.forEach_testMaxLong4Long4Long4(inV1, out);
+ verifyResultsMaxLong4Long4Long4(inV1, inV2, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxLong4Long4Long4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsMaxLong4Long4Long4(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
+ long[] arrayInV1 = new long[INPUTSIZE * 4];
+ inV1.copyTo(arrayInV1);
+ long[] arrayInV2 = new long[INPUTSIZE * 4];
+ inV2.copyTo(arrayInV2);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsLongLongLong args = new ArgumentsLongLongLong();
+ args.inV1 = arrayInV1[i * 4 + j];
+ args.inV2 = arrayInV2[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeMax(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV1: ");
+ message.append(String.format("%d", args.inV1));
+ message.append("\n");
+ message.append("Input inV2: ");
+ message.append(String.format("%d", args.inV2));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkMaxLong4Long4Long4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsUlongUlongUlong {
+ public long inV1;
+ public long inV2;
+ public long out;
+ }
+
+ private void checkMaxUlongUlongUlong() {
+ Allocation inV1 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_64, 1, 0xb38270e909275f1dl, false);
+ Allocation inV2 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_64, 1, 0xb38270e909275f1el, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 1), INPUTSIZE);
+ script.set_gAllocInV2(inV2);
+ script.forEach_testMaxUlongUlongUlong(inV1, out);
+ verifyResultsMaxUlongUlongUlong(inV1, inV2, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxUlongUlongUlong: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 1), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV2(inV2);
+ scriptRelaxed.forEach_testMaxUlongUlongUlong(inV1, out);
+ verifyResultsMaxUlongUlongUlong(inV1, inV2, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxUlongUlongUlong: " + e.toString());
+ }
+ }
+
+ private void verifyResultsMaxUlongUlongUlong(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
+ long[] arrayInV1 = new long[INPUTSIZE * 1];
+ inV1.copyTo(arrayInV1);
+ long[] arrayInV2 = new long[INPUTSIZE * 1];
+ inV2.copyTo(arrayInV2);
+ long[] arrayOut = new long[INPUTSIZE * 1];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 1 ; j++) {
+ // Extract the inputs.
+ ArgumentsUlongUlongUlong args = new ArgumentsUlongUlongUlong();
+ args.inV1 = arrayInV1[i];
+ args.inV2 = arrayInV2[i];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeMax(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 1 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV1: ");
+ message.append(String.format("0x%x", args.inV1));
+ message.append("\n");
+ message.append("Input inV2: ");
+ message.append(String.format("0x%x", args.inV2));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 1 + j]));
+ if (args.out != arrayOut[i * 1 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkMaxUlongUlongUlong" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkMaxUlong2Ulong2Ulong2() {
+ Allocation inV1 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_64, 2, 0x7f6c5ec5fee1a8afl, false);
+ Allocation inV2 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_64, 2, 0x7f6c5ec5fee1a8b0l, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 2), INPUTSIZE);
+ script.set_gAllocInV2(inV2);
+ script.forEach_testMaxUlong2Ulong2Ulong2(inV1, out);
+ verifyResultsMaxUlong2Ulong2Ulong2(inV1, inV2, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxUlong2Ulong2Ulong2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 2), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV2(inV2);
+ scriptRelaxed.forEach_testMaxUlong2Ulong2Ulong2(inV1, out);
+ verifyResultsMaxUlong2Ulong2Ulong2(inV1, inV2, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxUlong2Ulong2Ulong2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsMaxUlong2Ulong2Ulong2(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
+ long[] arrayInV1 = new long[INPUTSIZE * 2];
+ inV1.copyTo(arrayInV1);
+ long[] arrayInV2 = new long[INPUTSIZE * 2];
+ inV2.copyTo(arrayInV2);
+ long[] arrayOut = new long[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsUlongUlongUlong args = new ArgumentsUlongUlongUlong();
+ args.inV1 = arrayInV1[i * 2 + j];
+ args.inV2 = arrayInV2[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeMax(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV1: ");
+ message.append(String.format("0x%x", args.inV1));
+ message.append("\n");
+ message.append("Input inV2: ");
+ message.append(String.format("0x%x", args.inV2));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkMaxUlong2Ulong2Ulong2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkMaxUlong3Ulong3Ulong3() {
+ Allocation inV1 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_64, 3, 0xac5dae264f2beebal, false);
+ Allocation inV2 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_64, 3, 0xac5dae264f2beebbl, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 3), INPUTSIZE);
+ script.set_gAllocInV2(inV2);
+ script.forEach_testMaxUlong3Ulong3Ulong3(inV1, out);
+ verifyResultsMaxUlong3Ulong3Ulong3(inV1, inV2, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxUlong3Ulong3Ulong3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 3), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV2(inV2);
+ scriptRelaxed.forEach_testMaxUlong3Ulong3Ulong3(inV1, out);
+ verifyResultsMaxUlong3Ulong3Ulong3(inV1, inV2, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxUlong3Ulong3Ulong3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsMaxUlong3Ulong3Ulong3(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
+ long[] arrayInV1 = new long[INPUTSIZE * 4];
+ inV1.copyTo(arrayInV1);
+ long[] arrayInV2 = new long[INPUTSIZE * 4];
+ inV2.copyTo(arrayInV2);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsUlongUlongUlong args = new ArgumentsUlongUlongUlong();
+ args.inV1 = arrayInV1[i * 4 + j];
+ args.inV2 = arrayInV2[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeMax(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV1: ");
+ message.append(String.format("0x%x", args.inV1));
+ message.append("\n");
+ message.append("Input inV2: ");
+ message.append(String.format("0x%x", args.inV2));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkMaxUlong3Ulong3Ulong3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkMaxUlong4Ulong4Ulong4() {
+ Allocation inV1 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_64, 4, 0xd94efd869f7634c5l, false);
+ Allocation inV2 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_64, 4, 0xd94efd869f7634c6l, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 4), INPUTSIZE);
+ script.set_gAllocInV2(inV2);
+ script.forEach_testMaxUlong4Ulong4Ulong4(inV1, out);
+ verifyResultsMaxUlong4Ulong4Ulong4(inV1, inV2, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxUlong4Ulong4Ulong4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 4), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV2(inV2);
+ scriptRelaxed.forEach_testMaxUlong4Ulong4Ulong4(inV1, out);
+ verifyResultsMaxUlong4Ulong4Ulong4(inV1, inV2, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxUlong4Ulong4Ulong4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsMaxUlong4Ulong4Ulong4(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
+ long[] arrayInV1 = new long[INPUTSIZE * 4];
+ inV1.copyTo(arrayInV1);
+ long[] arrayInV2 = new long[INPUTSIZE * 4];
+ inV2.copyTo(arrayInV2);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsUlongUlongUlong args = new ArgumentsUlongUlongUlong();
+ args.inV1 = arrayInV1[i * 4 + j];
+ args.inV2 = arrayInV2[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeMax(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV1: ");
+ message.append(String.format("0x%x", args.inV1));
+ message.append("\n");
+ message.append("Input inV2: ");
+ message.append(String.format("0x%x", args.inV2));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkMaxUlong4Ulong4Ulong4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
public void testMax() {
checkMaxFloatFloatFloat();
checkMaxFloat2Float2Float2();
checkMaxFloat3Float3Float3();
checkMaxFloat4Float4Float4();
checkMaxCharCharChar();
- checkMaxUcharUcharUchar();
- checkMaxShortShortShort();
- checkMaxUshortUshortUshort();
- checkMaxIntIntInt();
- checkMaxUintUintUint();
checkMaxChar2Char2Char2();
- checkMaxUchar2Uchar2Uchar2();
- checkMaxShort2Short2Short2();
- checkMaxUshort2Ushort2Ushort2();
- checkMaxInt2Int2Int2();
- checkMaxUint2Uint2Uint2();
checkMaxChar3Char3Char3();
- checkMaxUchar3Uchar3Uchar3();
- checkMaxShort3Short3Short3();
- checkMaxUshort3Ushort3Ushort3();
- checkMaxInt3Int3Int3();
- checkMaxUint3Uint3Uint3();
checkMaxChar4Char4Char4();
+ checkMaxUcharUcharUchar();
+ checkMaxUchar2Uchar2Uchar2();
+ checkMaxUchar3Uchar3Uchar3();
checkMaxUchar4Uchar4Uchar4();
+ checkMaxShortShortShort();
+ checkMaxShort2Short2Short2();
+ checkMaxShort3Short3Short3();
checkMaxShort4Short4Short4();
+ checkMaxUshortUshortUshort();
+ checkMaxUshort2Ushort2Ushort2();
+ checkMaxUshort3Ushort3Ushort3();
checkMaxUshort4Ushort4Ushort4();
+ checkMaxIntIntInt();
+ checkMaxInt2Int2Int2();
+ checkMaxInt3Int3Int3();
checkMaxInt4Int4Int4();
+ checkMaxUintUintUint();
+ checkMaxUint2Uint2Uint2();
+ checkMaxUint3Uint3Uint3();
checkMaxUint4Uint4Uint4();
+ checkMaxLongLongLong();
+ checkMaxLong2Long2Long2();
+ checkMaxLong3Long3Long3();
+ checkMaxLong4Long4Long4();
+ checkMaxUlongUlongUlong();
+ checkMaxUlong2Ulong2Ulong2();
+ checkMaxUlong3Ulong3Ulong3();
+ checkMaxUlong4Ulong4Ulong4();
}
}
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/TestMin.java b/tests/tests/renderscript/src/android/renderscript/cts/TestMin.java
index 08434f3..aaf6549 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/TestMin.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/TestMin.java
@@ -388,6 +388,204 @@
}
}
+ private void checkMinChar2Char2Char2() {
+ Allocation inV1 = createRandomAllocation(mRS, Element.DataType.SIGNED_8, 2, 0xec4705afc03447ael, false);
+ Allocation inV2 = createRandomAllocation(mRS, Element.DataType.SIGNED_8, 2, 0xec4705afc03447afl, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_8, 2), INPUTSIZE);
+ script.set_gAllocInV2(inV2);
+ script.forEach_testMinChar2Char2Char2(inV1, out);
+ verifyResultsMinChar2Char2Char2(inV1, inV2, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinChar2Char2Char2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_8, 2), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV2(inV2);
+ scriptRelaxed.forEach_testMinChar2Char2Char2(inV1, out);
+ verifyResultsMinChar2Char2Char2(inV1, inV2, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinChar2Char2Char2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsMinChar2Char2Char2(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
+ byte[] arrayInV1 = new byte[INPUTSIZE * 2];
+ inV1.copyTo(arrayInV1);
+ byte[] arrayInV2 = new byte[INPUTSIZE * 2];
+ inV2.copyTo(arrayInV2);
+ byte[] arrayOut = new byte[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsCharCharChar args = new ArgumentsCharCharChar();
+ args.inV1 = arrayInV1[i * 2 + j];
+ args.inV2 = arrayInV2[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeMin(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV1: ");
+ message.append(String.format("%d", args.inV1));
+ message.append("\n");
+ message.append("Input inV2: ");
+ message.append(String.format("%d", args.inV2));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkMinChar2Char2Char2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkMinChar3Char3Char3() {
+ Allocation inV1 = createRandomAllocation(mRS, Element.DataType.SIGNED_8, 3, 0x419881e2a4ec1a73l, false);
+ Allocation inV2 = createRandomAllocation(mRS, Element.DataType.SIGNED_8, 3, 0x419881e2a4ec1a74l, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_8, 3), INPUTSIZE);
+ script.set_gAllocInV2(inV2);
+ script.forEach_testMinChar3Char3Char3(inV1, out);
+ verifyResultsMinChar3Char3Char3(inV1, inV2, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinChar3Char3Char3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_8, 3), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV2(inV2);
+ scriptRelaxed.forEach_testMinChar3Char3Char3(inV1, out);
+ verifyResultsMinChar3Char3Char3(inV1, inV2, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinChar3Char3Char3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsMinChar3Char3Char3(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
+ byte[] arrayInV1 = new byte[INPUTSIZE * 4];
+ inV1.copyTo(arrayInV1);
+ byte[] arrayInV2 = new byte[INPUTSIZE * 4];
+ inV2.copyTo(arrayInV2);
+ byte[] arrayOut = new byte[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsCharCharChar args = new ArgumentsCharCharChar();
+ args.inV1 = arrayInV1[i * 4 + j];
+ args.inV2 = arrayInV2[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeMin(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV1: ");
+ message.append(String.format("%d", args.inV1));
+ message.append("\n");
+ message.append("Input inV2: ");
+ message.append(String.format("%d", args.inV2));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkMinChar3Char3Char3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkMinChar4Char4Char4() {
+ Allocation inV1 = createRandomAllocation(mRS, Element.DataType.SIGNED_8, 4, 0x96e9fe1589a3ed38l, false);
+ Allocation inV2 = createRandomAllocation(mRS, Element.DataType.SIGNED_8, 4, 0x96e9fe1589a3ed39l, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_8, 4), INPUTSIZE);
+ script.set_gAllocInV2(inV2);
+ script.forEach_testMinChar4Char4Char4(inV1, out);
+ verifyResultsMinChar4Char4Char4(inV1, inV2, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinChar4Char4Char4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_8, 4), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV2(inV2);
+ scriptRelaxed.forEach_testMinChar4Char4Char4(inV1, out);
+ verifyResultsMinChar4Char4Char4(inV1, inV2, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinChar4Char4Char4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsMinChar4Char4Char4(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
+ byte[] arrayInV1 = new byte[INPUTSIZE * 4];
+ inV1.copyTo(arrayInV1);
+ byte[] arrayInV2 = new byte[INPUTSIZE * 4];
+ inV2.copyTo(arrayInV2);
+ byte[] arrayOut = new byte[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsCharCharChar args = new ArgumentsCharCharChar();
+ args.inV1 = arrayInV1[i * 4 + j];
+ args.inV2 = arrayInV2[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeMin(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV1: ");
+ message.append(String.format("%d", args.inV1));
+ message.append("\n");
+ message.append("Input inV2: ");
+ message.append(String.format("%d", args.inV2));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkMinChar4Char4Char4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
public class ArgumentsUcharUcharUchar {
public byte inV1;
public byte inV2;
@@ -460,6 +658,204 @@
}
}
+ private void checkMinUchar2Uchar2Uchar2() {
+ Allocation inV1 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_8, 2, 0x1d3c921d166e22ffl, false);
+ Allocation inV2 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_8, 2, 0x1d3c921d166e2300l, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_8, 2), INPUTSIZE);
+ script.set_gAllocInV2(inV2);
+ script.forEach_testMinUchar2Uchar2Uchar2(inV1, out);
+ verifyResultsMinUchar2Uchar2Uchar2(inV1, inV2, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinUchar2Uchar2Uchar2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_8, 2), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV2(inV2);
+ scriptRelaxed.forEach_testMinUchar2Uchar2Uchar2(inV1, out);
+ verifyResultsMinUchar2Uchar2Uchar2(inV1, inV2, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinUchar2Uchar2Uchar2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsMinUchar2Uchar2Uchar2(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
+ byte[] arrayInV1 = new byte[INPUTSIZE * 2];
+ inV1.copyTo(arrayInV1);
+ byte[] arrayInV2 = new byte[INPUTSIZE * 2];
+ inV2.copyTo(arrayInV2);
+ byte[] arrayOut = new byte[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsUcharUcharUchar args = new ArgumentsUcharUcharUchar();
+ args.inV1 = arrayInV1[i * 2 + j];
+ args.inV2 = arrayInV2[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeMin(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV1: ");
+ message.append(String.format("0x%x", args.inV1));
+ message.append("\n");
+ message.append("Input inV2: ");
+ message.append(String.format("0x%x", args.inV2));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkMinUchar2Uchar2Uchar2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkMinUchar3Uchar3Uchar3() {
+ Allocation inV1 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_8, 3, 0x4a2de17d66b8690al, false);
+ Allocation inV2 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_8, 3, 0x4a2de17d66b8690bl, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_8, 3), INPUTSIZE);
+ script.set_gAllocInV2(inV2);
+ script.forEach_testMinUchar3Uchar3Uchar3(inV1, out);
+ verifyResultsMinUchar3Uchar3Uchar3(inV1, inV2, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinUchar3Uchar3Uchar3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_8, 3), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV2(inV2);
+ scriptRelaxed.forEach_testMinUchar3Uchar3Uchar3(inV1, out);
+ verifyResultsMinUchar3Uchar3Uchar3(inV1, inV2, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinUchar3Uchar3Uchar3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsMinUchar3Uchar3Uchar3(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
+ byte[] arrayInV1 = new byte[INPUTSIZE * 4];
+ inV1.copyTo(arrayInV1);
+ byte[] arrayInV2 = new byte[INPUTSIZE * 4];
+ inV2.copyTo(arrayInV2);
+ byte[] arrayOut = new byte[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsUcharUcharUchar args = new ArgumentsUcharUcharUchar();
+ args.inV1 = arrayInV1[i * 4 + j];
+ args.inV2 = arrayInV2[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeMin(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV1: ");
+ message.append(String.format("0x%x", args.inV1));
+ message.append("\n");
+ message.append("Input inV2: ");
+ message.append(String.format("0x%x", args.inV2));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkMinUchar3Uchar3Uchar3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkMinUchar4Uchar4Uchar4() {
+ Allocation inV1 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_8, 4, 0x771f30ddb702af15l, false);
+ Allocation inV2 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_8, 4, 0x771f30ddb702af16l, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_8, 4), INPUTSIZE);
+ script.set_gAllocInV2(inV2);
+ script.forEach_testMinUchar4Uchar4Uchar4(inV1, out);
+ verifyResultsMinUchar4Uchar4Uchar4(inV1, inV2, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinUchar4Uchar4Uchar4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_8, 4), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV2(inV2);
+ scriptRelaxed.forEach_testMinUchar4Uchar4Uchar4(inV1, out);
+ verifyResultsMinUchar4Uchar4Uchar4(inV1, inV2, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinUchar4Uchar4Uchar4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsMinUchar4Uchar4Uchar4(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
+ byte[] arrayInV1 = new byte[INPUTSIZE * 4];
+ inV1.copyTo(arrayInV1);
+ byte[] arrayInV2 = new byte[INPUTSIZE * 4];
+ inV2.copyTo(arrayInV2);
+ byte[] arrayOut = new byte[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsUcharUcharUchar args = new ArgumentsUcharUcharUchar();
+ args.inV1 = arrayInV1[i * 4 + j];
+ args.inV2 = arrayInV2[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeMin(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV1: ");
+ message.append(String.format("0x%x", args.inV1));
+ message.append("\n");
+ message.append("Input inV2: ");
+ message.append(String.format("0x%x", args.inV2));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkMinUchar4Uchar4Uchar4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
public class ArgumentsShortShortShort {
public short inV1;
public short inV2;
@@ -532,6 +928,204 @@
}
}
+ private void checkMinShort2Short2Short2() {
+ Allocation inV1 = createRandomAllocation(mRS, Element.DataType.SIGNED_16, 2, 0xe4959a1ecbf1d380l, false);
+ Allocation inV2 = createRandomAllocation(mRS, Element.DataType.SIGNED_16, 2, 0xe4959a1ecbf1d381l, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_16, 2), INPUTSIZE);
+ script.set_gAllocInV2(inV2);
+ script.forEach_testMinShort2Short2Short2(inV1, out);
+ verifyResultsMinShort2Short2Short2(inV1, inV2, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinShort2Short2Short2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_16, 2), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV2(inV2);
+ scriptRelaxed.forEach_testMinShort2Short2Short2(inV1, out);
+ verifyResultsMinShort2Short2Short2(inV1, inV2, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinShort2Short2Short2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsMinShort2Short2Short2(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
+ short[] arrayInV1 = new short[INPUTSIZE * 2];
+ inV1.copyTo(arrayInV1);
+ short[] arrayInV2 = new short[INPUTSIZE * 2];
+ inV2.copyTo(arrayInV2);
+ short[] arrayOut = new short[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsShortShortShort args = new ArgumentsShortShortShort();
+ args.inV1 = arrayInV1[i * 2 + j];
+ args.inV2 = arrayInV2[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeMin(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV1: ");
+ message.append(String.format("%d", args.inV1));
+ message.append("\n");
+ message.append("Input inV2: ");
+ message.append(String.format("%d", args.inV2));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkMinShort2Short2Short2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkMinShort3Short3Short3() {
+ Allocation inV1 = createRandomAllocation(mRS, Element.DataType.SIGNED_16, 3, 0x1186e97f1c3c198bl, false);
+ Allocation inV2 = createRandomAllocation(mRS, Element.DataType.SIGNED_16, 3, 0x1186e97f1c3c198cl, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_16, 3), INPUTSIZE);
+ script.set_gAllocInV2(inV2);
+ script.forEach_testMinShort3Short3Short3(inV1, out);
+ verifyResultsMinShort3Short3Short3(inV1, inV2, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinShort3Short3Short3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_16, 3), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV2(inV2);
+ scriptRelaxed.forEach_testMinShort3Short3Short3(inV1, out);
+ verifyResultsMinShort3Short3Short3(inV1, inV2, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinShort3Short3Short3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsMinShort3Short3Short3(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
+ short[] arrayInV1 = new short[INPUTSIZE * 4];
+ inV1.copyTo(arrayInV1);
+ short[] arrayInV2 = new short[INPUTSIZE * 4];
+ inV2.copyTo(arrayInV2);
+ short[] arrayOut = new short[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsShortShortShort args = new ArgumentsShortShortShort();
+ args.inV1 = arrayInV1[i * 4 + j];
+ args.inV2 = arrayInV2[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeMin(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV1: ");
+ message.append(String.format("%d", args.inV1));
+ message.append("\n");
+ message.append("Input inV2: ");
+ message.append(String.format("%d", args.inV2));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkMinShort3Short3Short3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkMinShort4Short4Short4() {
+ Allocation inV1 = createRandomAllocation(mRS, Element.DataType.SIGNED_16, 4, 0x3e7838df6c865f96l, false);
+ Allocation inV2 = createRandomAllocation(mRS, Element.DataType.SIGNED_16, 4, 0x3e7838df6c865f97l, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_16, 4), INPUTSIZE);
+ script.set_gAllocInV2(inV2);
+ script.forEach_testMinShort4Short4Short4(inV1, out);
+ verifyResultsMinShort4Short4Short4(inV1, inV2, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinShort4Short4Short4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_16, 4), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV2(inV2);
+ scriptRelaxed.forEach_testMinShort4Short4Short4(inV1, out);
+ verifyResultsMinShort4Short4Short4(inV1, inV2, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinShort4Short4Short4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsMinShort4Short4Short4(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
+ short[] arrayInV1 = new short[INPUTSIZE * 4];
+ inV1.copyTo(arrayInV1);
+ short[] arrayInV2 = new short[INPUTSIZE * 4];
+ inV2.copyTo(arrayInV2);
+ short[] arrayOut = new short[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsShortShortShort args = new ArgumentsShortShortShort();
+ args.inV1 = arrayInV1[i * 4 + j];
+ args.inV2 = arrayInV2[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeMin(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV1: ");
+ message.append(String.format("%d", args.inV1));
+ message.append("\n");
+ message.append("Input inV2: ");
+ message.append(String.format("%d", args.inV2));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkMinShort4Short4Short4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
public class ArgumentsUshortUshortUshort {
public short inV1;
public short inV2;
@@ -604,6 +1198,204 @@
}
}
+ private void checkMinUshort2Ushort2Ushort2() {
+ Allocation inV1 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_16, 2, 0x98573ebbc511e319l, false);
+ Allocation inV2 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_16, 2, 0x98573ebbc511e31al, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_16, 2), INPUTSIZE);
+ script.set_gAllocInV2(inV2);
+ script.forEach_testMinUshort2Ushort2Ushort2(inV1, out);
+ verifyResultsMinUshort2Ushort2Ushort2(inV1, inV2, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinUshort2Ushort2Ushort2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_16, 2), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV2(inV2);
+ scriptRelaxed.forEach_testMinUshort2Ushort2Ushort2(inV1, out);
+ verifyResultsMinUshort2Ushort2Ushort2(inV1, inV2, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinUshort2Ushort2Ushort2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsMinUshort2Ushort2Ushort2(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
+ short[] arrayInV1 = new short[INPUTSIZE * 2];
+ inV1.copyTo(arrayInV1);
+ short[] arrayInV2 = new short[INPUTSIZE * 2];
+ inV2.copyTo(arrayInV2);
+ short[] arrayOut = new short[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsUshortUshortUshort args = new ArgumentsUshortUshortUshort();
+ args.inV1 = arrayInV1[i * 2 + j];
+ args.inV2 = arrayInV2[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeMin(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV1: ");
+ message.append(String.format("0x%x", args.inV1));
+ message.append("\n");
+ message.append("Input inV2: ");
+ message.append(String.format("0x%x", args.inV2));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkMinUshort2Ushort2Ushort2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkMinUshort3Ushort3Ushort3() {
+ Allocation inV1 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_16, 3, 0x1595f09b03867776l, false);
+ Allocation inV2 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_16, 3, 0x1595f09b03867777l, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_16, 3), INPUTSIZE);
+ script.set_gAllocInV2(inV2);
+ script.forEach_testMinUshort3Ushort3Ushort3(inV1, out);
+ verifyResultsMinUshort3Ushort3Ushort3(inV1, inV2, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinUshort3Ushort3Ushort3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_16, 3), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV2(inV2);
+ scriptRelaxed.forEach_testMinUshort3Ushort3Ushort3(inV1, out);
+ verifyResultsMinUshort3Ushort3Ushort3(inV1, inV2, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinUshort3Ushort3Ushort3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsMinUshort3Ushort3Ushort3(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
+ short[] arrayInV1 = new short[INPUTSIZE * 4];
+ inV1.copyTo(arrayInV1);
+ short[] arrayInV2 = new short[INPUTSIZE * 4];
+ inV2.copyTo(arrayInV2);
+ short[] arrayOut = new short[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsUshortUshortUshort args = new ArgumentsUshortUshortUshort();
+ args.inV1 = arrayInV1[i * 4 + j];
+ args.inV2 = arrayInV2[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeMin(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV1: ");
+ message.append(String.format("0x%x", args.inV1));
+ message.append("\n");
+ message.append("Input inV2: ");
+ message.append(String.format("0x%x", args.inV2));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkMinUshort3Ushort3Ushort3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkMinUshort4Ushort4Ushort4() {
+ Allocation inV1 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_16, 4, 0x92d4a27a41fb0bd3l, false);
+ Allocation inV2 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_16, 4, 0x92d4a27a41fb0bd4l, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_16, 4), INPUTSIZE);
+ script.set_gAllocInV2(inV2);
+ script.forEach_testMinUshort4Ushort4Ushort4(inV1, out);
+ verifyResultsMinUshort4Ushort4Ushort4(inV1, inV2, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinUshort4Ushort4Ushort4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_16, 4), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV2(inV2);
+ scriptRelaxed.forEach_testMinUshort4Ushort4Ushort4(inV1, out);
+ verifyResultsMinUshort4Ushort4Ushort4(inV1, inV2, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinUshort4Ushort4Ushort4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsMinUshort4Ushort4Ushort4(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
+ short[] arrayInV1 = new short[INPUTSIZE * 4];
+ inV1.copyTo(arrayInV1);
+ short[] arrayInV2 = new short[INPUTSIZE * 4];
+ inV2.copyTo(arrayInV2);
+ short[] arrayOut = new short[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsUshortUshortUshort args = new ArgumentsUshortUshortUshort();
+ args.inV1 = arrayInV1[i * 4 + j];
+ args.inV2 = arrayInV2[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeMin(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV1: ");
+ message.append(String.format("0x%x", args.inV1));
+ message.append("\n");
+ message.append("Input inV2: ");
+ message.append(String.format("0x%x", args.inV2));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkMinUshort4Ushort4Ushort4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
public class ArgumentsIntIntInt {
public int inV1;
public int inV2;
@@ -676,342 +1468,6 @@
}
}
- public class ArgumentsUintUintUint {
- public int inV1;
- public int inV2;
- public int out;
- }
-
- private void checkMinUintUintUint() {
- Allocation inV1 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_32, 1, 0xb3dbca2d537cf298l, false);
- Allocation inV2 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_32, 1, 0xb3dbca2d537cf299l, false);
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_32, 1), INPUTSIZE);
- script.set_gAllocInV2(inV2);
- script.forEach_testMinUintUintUint(inV1, out);
- verifyResultsMinUintUintUint(inV1, inV2, out, false);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinUintUintUint: " + e.toString());
- }
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_32, 1), INPUTSIZE);
- scriptRelaxed.set_gAllocInV2(inV2);
- scriptRelaxed.forEach_testMinUintUintUint(inV1, out);
- verifyResultsMinUintUintUint(inV1, inV2, out, true);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinUintUintUint: " + e.toString());
- }
- }
-
- private void verifyResultsMinUintUintUint(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
- int[] arrayInV1 = new int[INPUTSIZE * 1];
- inV1.copyTo(arrayInV1);
- int[] arrayInV2 = new int[INPUTSIZE * 1];
- inV2.copyTo(arrayInV2);
- int[] arrayOut = new int[INPUTSIZE * 1];
- out.copyTo(arrayOut);
- for (int i = 0; i < INPUTSIZE; i++) {
- for (int j = 0; j < 1 ; j++) {
- // Extract the inputs.
- ArgumentsUintUintUint args = new ArgumentsUintUintUint();
- args.inV1 = arrayInV1[i];
- args.inV2 = arrayInV2[i];
- // Figure out what the outputs should have been.
- Floaty.setRelaxed(relaxed);
- CoreMathVerifier.computeMin(args);
- // Figure out what the outputs should have been.
- boolean valid = true;
- if (args.out != arrayOut[i * 1 + j]) {
- valid = false;
- }
- if (!valid) {
- StringBuilder message = new StringBuilder();
- message.append("Input inV1: ");
- message.append(String.format("0x%x", args.inV1));
- message.append("\n");
- message.append("Input inV2: ");
- message.append(String.format("0x%x", args.inV2));
- message.append("\n");
- message.append("Expected output out: ");
- message.append(String.format("0x%x", args.out));
- message.append("\n");
- message.append("Actual output out: ");
- message.append(String.format("0x%x", arrayOut[i * 1 + j]));
- if (args.out != arrayOut[i * 1 + j]) {
- message.append(" FAIL");
- }
- message.append("\n");
- assertTrue("Incorrect output for checkMinUintUintUint" +
- (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
- }
- }
- }
- }
-
- private void checkMinChar2Char2Char2() {
- Allocation inV1 = createRandomAllocation(mRS, Element.DataType.SIGNED_8, 2, 0xec4705afc03447ael, false);
- Allocation inV2 = createRandomAllocation(mRS, Element.DataType.SIGNED_8, 2, 0xec4705afc03447afl, false);
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_8, 2), INPUTSIZE);
- script.set_gAllocInV2(inV2);
- script.forEach_testMinChar2Char2Char2(inV1, out);
- verifyResultsMinChar2Char2Char2(inV1, inV2, out, false);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinChar2Char2Char2: " + e.toString());
- }
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_8, 2), INPUTSIZE);
- scriptRelaxed.set_gAllocInV2(inV2);
- scriptRelaxed.forEach_testMinChar2Char2Char2(inV1, out);
- verifyResultsMinChar2Char2Char2(inV1, inV2, out, true);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinChar2Char2Char2: " + e.toString());
- }
- }
-
- private void verifyResultsMinChar2Char2Char2(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
- byte[] arrayInV1 = new byte[INPUTSIZE * 2];
- inV1.copyTo(arrayInV1);
- byte[] arrayInV2 = new byte[INPUTSIZE * 2];
- inV2.copyTo(arrayInV2);
- byte[] arrayOut = new byte[INPUTSIZE * 2];
- out.copyTo(arrayOut);
- for (int i = 0; i < INPUTSIZE; i++) {
- for (int j = 0; j < 2 ; j++) {
- // Extract the inputs.
- ArgumentsCharCharChar args = new ArgumentsCharCharChar();
- args.inV1 = arrayInV1[i * 2 + j];
- args.inV2 = arrayInV2[i * 2 + j];
- // Figure out what the outputs should have been.
- Floaty.setRelaxed(relaxed);
- CoreMathVerifier.computeMin(args);
- // Figure out what the outputs should have been.
- boolean valid = true;
- if (args.out != arrayOut[i * 2 + j]) {
- valid = false;
- }
- if (!valid) {
- StringBuilder message = new StringBuilder();
- message.append("Input inV1: ");
- message.append(String.format("%d", args.inV1));
- message.append("\n");
- message.append("Input inV2: ");
- message.append(String.format("%d", args.inV2));
- message.append("\n");
- message.append("Expected output out: ");
- message.append(String.format("%d", args.out));
- message.append("\n");
- message.append("Actual output out: ");
- message.append(String.format("%d", arrayOut[i * 2 + j]));
- if (args.out != arrayOut[i * 2 + j]) {
- message.append(" FAIL");
- }
- message.append("\n");
- assertTrue("Incorrect output for checkMinChar2Char2Char2" +
- (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
- }
- }
- }
- }
-
- private void checkMinUchar2Uchar2Uchar2() {
- Allocation inV1 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_8, 2, 0x1d3c921d166e22ffl, false);
- Allocation inV2 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_8, 2, 0x1d3c921d166e2300l, false);
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_8, 2), INPUTSIZE);
- script.set_gAllocInV2(inV2);
- script.forEach_testMinUchar2Uchar2Uchar2(inV1, out);
- verifyResultsMinUchar2Uchar2Uchar2(inV1, inV2, out, false);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinUchar2Uchar2Uchar2: " + e.toString());
- }
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_8, 2), INPUTSIZE);
- scriptRelaxed.set_gAllocInV2(inV2);
- scriptRelaxed.forEach_testMinUchar2Uchar2Uchar2(inV1, out);
- verifyResultsMinUchar2Uchar2Uchar2(inV1, inV2, out, true);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinUchar2Uchar2Uchar2: " + e.toString());
- }
- }
-
- private void verifyResultsMinUchar2Uchar2Uchar2(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
- byte[] arrayInV1 = new byte[INPUTSIZE * 2];
- inV1.copyTo(arrayInV1);
- byte[] arrayInV2 = new byte[INPUTSIZE * 2];
- inV2.copyTo(arrayInV2);
- byte[] arrayOut = new byte[INPUTSIZE * 2];
- out.copyTo(arrayOut);
- for (int i = 0; i < INPUTSIZE; i++) {
- for (int j = 0; j < 2 ; j++) {
- // Extract the inputs.
- ArgumentsUcharUcharUchar args = new ArgumentsUcharUcharUchar();
- args.inV1 = arrayInV1[i * 2 + j];
- args.inV2 = arrayInV2[i * 2 + j];
- // Figure out what the outputs should have been.
- Floaty.setRelaxed(relaxed);
- CoreMathVerifier.computeMin(args);
- // Figure out what the outputs should have been.
- boolean valid = true;
- if (args.out != arrayOut[i * 2 + j]) {
- valid = false;
- }
- if (!valid) {
- StringBuilder message = new StringBuilder();
- message.append("Input inV1: ");
- message.append(String.format("0x%x", args.inV1));
- message.append("\n");
- message.append("Input inV2: ");
- message.append(String.format("0x%x", args.inV2));
- message.append("\n");
- message.append("Expected output out: ");
- message.append(String.format("0x%x", args.out));
- message.append("\n");
- message.append("Actual output out: ");
- message.append(String.format("0x%x", arrayOut[i * 2 + j]));
- if (args.out != arrayOut[i * 2 + j]) {
- message.append(" FAIL");
- }
- message.append("\n");
- assertTrue("Incorrect output for checkMinUchar2Uchar2Uchar2" +
- (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
- }
- }
- }
- }
-
- private void checkMinShort2Short2Short2() {
- Allocation inV1 = createRandomAllocation(mRS, Element.DataType.SIGNED_16, 2, 0xe4959a1ecbf1d380l, false);
- Allocation inV2 = createRandomAllocation(mRS, Element.DataType.SIGNED_16, 2, 0xe4959a1ecbf1d381l, false);
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_16, 2), INPUTSIZE);
- script.set_gAllocInV2(inV2);
- script.forEach_testMinShort2Short2Short2(inV1, out);
- verifyResultsMinShort2Short2Short2(inV1, inV2, out, false);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinShort2Short2Short2: " + e.toString());
- }
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_16, 2), INPUTSIZE);
- scriptRelaxed.set_gAllocInV2(inV2);
- scriptRelaxed.forEach_testMinShort2Short2Short2(inV1, out);
- verifyResultsMinShort2Short2Short2(inV1, inV2, out, true);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinShort2Short2Short2: " + e.toString());
- }
- }
-
- private void verifyResultsMinShort2Short2Short2(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
- short[] arrayInV1 = new short[INPUTSIZE * 2];
- inV1.copyTo(arrayInV1);
- short[] arrayInV2 = new short[INPUTSIZE * 2];
- inV2.copyTo(arrayInV2);
- short[] arrayOut = new short[INPUTSIZE * 2];
- out.copyTo(arrayOut);
- for (int i = 0; i < INPUTSIZE; i++) {
- for (int j = 0; j < 2 ; j++) {
- // Extract the inputs.
- ArgumentsShortShortShort args = new ArgumentsShortShortShort();
- args.inV1 = arrayInV1[i * 2 + j];
- args.inV2 = arrayInV2[i * 2 + j];
- // Figure out what the outputs should have been.
- Floaty.setRelaxed(relaxed);
- CoreMathVerifier.computeMin(args);
- // Figure out what the outputs should have been.
- boolean valid = true;
- if (args.out != arrayOut[i * 2 + j]) {
- valid = false;
- }
- if (!valid) {
- StringBuilder message = new StringBuilder();
- message.append("Input inV1: ");
- message.append(String.format("%d", args.inV1));
- message.append("\n");
- message.append("Input inV2: ");
- message.append(String.format("%d", args.inV2));
- message.append("\n");
- message.append("Expected output out: ");
- message.append(String.format("%d", args.out));
- message.append("\n");
- message.append("Actual output out: ");
- message.append(String.format("%d", arrayOut[i * 2 + j]));
- if (args.out != arrayOut[i * 2 + j]) {
- message.append(" FAIL");
- }
- message.append("\n");
- assertTrue("Incorrect output for checkMinShort2Short2Short2" +
- (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
- }
- }
- }
- }
-
- private void checkMinUshort2Ushort2Ushort2() {
- Allocation inV1 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_16, 2, 0x98573ebbc511e319l, false);
- Allocation inV2 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_16, 2, 0x98573ebbc511e31al, false);
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_16, 2), INPUTSIZE);
- script.set_gAllocInV2(inV2);
- script.forEach_testMinUshort2Ushort2Ushort2(inV1, out);
- verifyResultsMinUshort2Ushort2Ushort2(inV1, inV2, out, false);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinUshort2Ushort2Ushort2: " + e.toString());
- }
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_16, 2), INPUTSIZE);
- scriptRelaxed.set_gAllocInV2(inV2);
- scriptRelaxed.forEach_testMinUshort2Ushort2Ushort2(inV1, out);
- verifyResultsMinUshort2Ushort2Ushort2(inV1, inV2, out, true);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinUshort2Ushort2Ushort2: " + e.toString());
- }
- }
-
- private void verifyResultsMinUshort2Ushort2Ushort2(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
- short[] arrayInV1 = new short[INPUTSIZE * 2];
- inV1.copyTo(arrayInV1);
- short[] arrayInV2 = new short[INPUTSIZE * 2];
- inV2.copyTo(arrayInV2);
- short[] arrayOut = new short[INPUTSIZE * 2];
- out.copyTo(arrayOut);
- for (int i = 0; i < INPUTSIZE; i++) {
- for (int j = 0; j < 2 ; j++) {
- // Extract the inputs.
- ArgumentsUshortUshortUshort args = new ArgumentsUshortUshortUshort();
- args.inV1 = arrayInV1[i * 2 + j];
- args.inV2 = arrayInV2[i * 2 + j];
- // Figure out what the outputs should have been.
- Floaty.setRelaxed(relaxed);
- CoreMathVerifier.computeMin(args);
- // Figure out what the outputs should have been.
- boolean valid = true;
- if (args.out != arrayOut[i * 2 + j]) {
- valid = false;
- }
- if (!valid) {
- StringBuilder message = new StringBuilder();
- message.append("Input inV1: ");
- message.append(String.format("0x%x", args.inV1));
- message.append("\n");
- message.append("Input inV2: ");
- message.append(String.format("0x%x", args.inV2));
- message.append("\n");
- message.append("Expected output out: ");
- message.append(String.format("0x%x", args.out));
- message.append("\n");
- message.append("Actual output out: ");
- message.append(String.format("0x%x", arrayOut[i * 2 + j]));
- if (args.out != arrayOut[i * 2 + j]) {
- message.append(" FAIL");
- }
- message.append("\n");
- assertTrue("Incorrect output for checkMinUshort2Ushort2Ushort2" +
- (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
- }
- }
- }
- }
-
private void checkMinInt2Int2Int2() {
Allocation inV1 = createRandomAllocation(mRS, Element.DataType.SIGNED_32, 2, 0xba635b605676e7a3l, false);
Allocation inV2 = createRandomAllocation(mRS, Element.DataType.SIGNED_32, 2, 0xba635b605676e7a4l, false);
@@ -1078,336 +1534,6 @@
}
}
- private void checkMinUint2Uint2Uint2() {
- Allocation inV1 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_32, 2, 0xb8cf8481d731a1eel, false);
- Allocation inV2 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_32, 2, 0xb8cf8481d731a1efl, false);
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_32, 2), INPUTSIZE);
- script.set_gAllocInV2(inV2);
- script.forEach_testMinUint2Uint2Uint2(inV1, out);
- verifyResultsMinUint2Uint2Uint2(inV1, inV2, out, false);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinUint2Uint2Uint2: " + e.toString());
- }
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_32, 2), INPUTSIZE);
- scriptRelaxed.set_gAllocInV2(inV2);
- scriptRelaxed.forEach_testMinUint2Uint2Uint2(inV1, out);
- verifyResultsMinUint2Uint2Uint2(inV1, inV2, out, true);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinUint2Uint2Uint2: " + e.toString());
- }
- }
-
- private void verifyResultsMinUint2Uint2Uint2(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
- int[] arrayInV1 = new int[INPUTSIZE * 2];
- inV1.copyTo(arrayInV1);
- int[] arrayInV2 = new int[INPUTSIZE * 2];
- inV2.copyTo(arrayInV2);
- int[] arrayOut = new int[INPUTSIZE * 2];
- out.copyTo(arrayOut);
- for (int i = 0; i < INPUTSIZE; i++) {
- for (int j = 0; j < 2 ; j++) {
- // Extract the inputs.
- ArgumentsUintUintUint args = new ArgumentsUintUintUint();
- args.inV1 = arrayInV1[i * 2 + j];
- args.inV2 = arrayInV2[i * 2 + j];
- // Figure out what the outputs should have been.
- Floaty.setRelaxed(relaxed);
- CoreMathVerifier.computeMin(args);
- // Figure out what the outputs should have been.
- boolean valid = true;
- if (args.out != arrayOut[i * 2 + j]) {
- valid = false;
- }
- if (!valid) {
- StringBuilder message = new StringBuilder();
- message.append("Input inV1: ");
- message.append(String.format("0x%x", args.inV1));
- message.append("\n");
- message.append("Input inV2: ");
- message.append(String.format("0x%x", args.inV2));
- message.append("\n");
- message.append("Expected output out: ");
- message.append(String.format("0x%x", args.out));
- message.append("\n");
- message.append("Actual output out: ");
- message.append(String.format("0x%x", arrayOut[i * 2 + j]));
- if (args.out != arrayOut[i * 2 + j]) {
- message.append(" FAIL");
- }
- message.append("\n");
- assertTrue("Incorrect output for checkMinUint2Uint2Uint2" +
- (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
- }
- }
- }
- }
-
- private void checkMinChar3Char3Char3() {
- Allocation inV1 = createRandomAllocation(mRS, Element.DataType.SIGNED_8, 3, 0x419881e2a4ec1a73l, false);
- Allocation inV2 = createRandomAllocation(mRS, Element.DataType.SIGNED_8, 3, 0x419881e2a4ec1a74l, false);
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_8, 3), INPUTSIZE);
- script.set_gAllocInV2(inV2);
- script.forEach_testMinChar3Char3Char3(inV1, out);
- verifyResultsMinChar3Char3Char3(inV1, inV2, out, false);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinChar3Char3Char3: " + e.toString());
- }
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_8, 3), INPUTSIZE);
- scriptRelaxed.set_gAllocInV2(inV2);
- scriptRelaxed.forEach_testMinChar3Char3Char3(inV1, out);
- verifyResultsMinChar3Char3Char3(inV1, inV2, out, true);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinChar3Char3Char3: " + e.toString());
- }
- }
-
- private void verifyResultsMinChar3Char3Char3(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
- byte[] arrayInV1 = new byte[INPUTSIZE * 4];
- inV1.copyTo(arrayInV1);
- byte[] arrayInV2 = new byte[INPUTSIZE * 4];
- inV2.copyTo(arrayInV2);
- byte[] arrayOut = new byte[INPUTSIZE * 4];
- out.copyTo(arrayOut);
- for (int i = 0; i < INPUTSIZE; i++) {
- for (int j = 0; j < 3 ; j++) {
- // Extract the inputs.
- ArgumentsCharCharChar args = new ArgumentsCharCharChar();
- args.inV1 = arrayInV1[i * 4 + j];
- args.inV2 = arrayInV2[i * 4 + j];
- // Figure out what the outputs should have been.
- Floaty.setRelaxed(relaxed);
- CoreMathVerifier.computeMin(args);
- // Figure out what the outputs should have been.
- boolean valid = true;
- if (args.out != arrayOut[i * 4 + j]) {
- valid = false;
- }
- if (!valid) {
- StringBuilder message = new StringBuilder();
- message.append("Input inV1: ");
- message.append(String.format("%d", args.inV1));
- message.append("\n");
- message.append("Input inV2: ");
- message.append(String.format("%d", args.inV2));
- message.append("\n");
- message.append("Expected output out: ");
- message.append(String.format("%d", args.out));
- message.append("\n");
- message.append("Actual output out: ");
- message.append(String.format("%d", arrayOut[i * 4 + j]));
- if (args.out != arrayOut[i * 4 + j]) {
- message.append(" FAIL");
- }
- message.append("\n");
- assertTrue("Incorrect output for checkMinChar3Char3Char3" +
- (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
- }
- }
- }
- }
-
- private void checkMinUchar3Uchar3Uchar3() {
- Allocation inV1 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_8, 3, 0x4a2de17d66b8690al, false);
- Allocation inV2 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_8, 3, 0x4a2de17d66b8690bl, false);
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_8, 3), INPUTSIZE);
- script.set_gAllocInV2(inV2);
- script.forEach_testMinUchar3Uchar3Uchar3(inV1, out);
- verifyResultsMinUchar3Uchar3Uchar3(inV1, inV2, out, false);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinUchar3Uchar3Uchar3: " + e.toString());
- }
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_8, 3), INPUTSIZE);
- scriptRelaxed.set_gAllocInV2(inV2);
- scriptRelaxed.forEach_testMinUchar3Uchar3Uchar3(inV1, out);
- verifyResultsMinUchar3Uchar3Uchar3(inV1, inV2, out, true);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinUchar3Uchar3Uchar3: " + e.toString());
- }
- }
-
- private void verifyResultsMinUchar3Uchar3Uchar3(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
- byte[] arrayInV1 = new byte[INPUTSIZE * 4];
- inV1.copyTo(arrayInV1);
- byte[] arrayInV2 = new byte[INPUTSIZE * 4];
- inV2.copyTo(arrayInV2);
- byte[] arrayOut = new byte[INPUTSIZE * 4];
- out.copyTo(arrayOut);
- for (int i = 0; i < INPUTSIZE; i++) {
- for (int j = 0; j < 3 ; j++) {
- // Extract the inputs.
- ArgumentsUcharUcharUchar args = new ArgumentsUcharUcharUchar();
- args.inV1 = arrayInV1[i * 4 + j];
- args.inV2 = arrayInV2[i * 4 + j];
- // Figure out what the outputs should have been.
- Floaty.setRelaxed(relaxed);
- CoreMathVerifier.computeMin(args);
- // Figure out what the outputs should have been.
- boolean valid = true;
- if (args.out != arrayOut[i * 4 + j]) {
- valid = false;
- }
- if (!valid) {
- StringBuilder message = new StringBuilder();
- message.append("Input inV1: ");
- message.append(String.format("0x%x", args.inV1));
- message.append("\n");
- message.append("Input inV2: ");
- message.append(String.format("0x%x", args.inV2));
- message.append("\n");
- message.append("Expected output out: ");
- message.append(String.format("0x%x", args.out));
- message.append("\n");
- message.append("Actual output out: ");
- message.append(String.format("0x%x", arrayOut[i * 4 + j]));
- if (args.out != arrayOut[i * 4 + j]) {
- message.append(" FAIL");
- }
- message.append("\n");
- assertTrue("Incorrect output for checkMinUchar3Uchar3Uchar3" +
- (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
- }
- }
- }
- }
-
- private void checkMinShort3Short3Short3() {
- Allocation inV1 = createRandomAllocation(mRS, Element.DataType.SIGNED_16, 3, 0x1186e97f1c3c198bl, false);
- Allocation inV2 = createRandomAllocation(mRS, Element.DataType.SIGNED_16, 3, 0x1186e97f1c3c198cl, false);
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_16, 3), INPUTSIZE);
- script.set_gAllocInV2(inV2);
- script.forEach_testMinShort3Short3Short3(inV1, out);
- verifyResultsMinShort3Short3Short3(inV1, inV2, out, false);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinShort3Short3Short3: " + e.toString());
- }
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_16, 3), INPUTSIZE);
- scriptRelaxed.set_gAllocInV2(inV2);
- scriptRelaxed.forEach_testMinShort3Short3Short3(inV1, out);
- verifyResultsMinShort3Short3Short3(inV1, inV2, out, true);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinShort3Short3Short3: " + e.toString());
- }
- }
-
- private void verifyResultsMinShort3Short3Short3(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
- short[] arrayInV1 = new short[INPUTSIZE * 4];
- inV1.copyTo(arrayInV1);
- short[] arrayInV2 = new short[INPUTSIZE * 4];
- inV2.copyTo(arrayInV2);
- short[] arrayOut = new short[INPUTSIZE * 4];
- out.copyTo(arrayOut);
- for (int i = 0; i < INPUTSIZE; i++) {
- for (int j = 0; j < 3 ; j++) {
- // Extract the inputs.
- ArgumentsShortShortShort args = new ArgumentsShortShortShort();
- args.inV1 = arrayInV1[i * 4 + j];
- args.inV2 = arrayInV2[i * 4 + j];
- // Figure out what the outputs should have been.
- Floaty.setRelaxed(relaxed);
- CoreMathVerifier.computeMin(args);
- // Figure out what the outputs should have been.
- boolean valid = true;
- if (args.out != arrayOut[i * 4 + j]) {
- valid = false;
- }
- if (!valid) {
- StringBuilder message = new StringBuilder();
- message.append("Input inV1: ");
- message.append(String.format("%d", args.inV1));
- message.append("\n");
- message.append("Input inV2: ");
- message.append(String.format("%d", args.inV2));
- message.append("\n");
- message.append("Expected output out: ");
- message.append(String.format("%d", args.out));
- message.append("\n");
- message.append("Actual output out: ");
- message.append(String.format("%d", arrayOut[i * 4 + j]));
- if (args.out != arrayOut[i * 4 + j]) {
- message.append(" FAIL");
- }
- message.append("\n");
- assertTrue("Incorrect output for checkMinShort3Short3Short3" +
- (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
- }
- }
- }
- }
-
- private void checkMinUshort3Ushort3Ushort3() {
- Allocation inV1 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_16, 3, 0x1595f09b03867776l, false);
- Allocation inV2 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_16, 3, 0x1595f09b03867777l, false);
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_16, 3), INPUTSIZE);
- script.set_gAllocInV2(inV2);
- script.forEach_testMinUshort3Ushort3Ushort3(inV1, out);
- verifyResultsMinUshort3Ushort3Ushort3(inV1, inV2, out, false);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinUshort3Ushort3Ushort3: " + e.toString());
- }
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_16, 3), INPUTSIZE);
- scriptRelaxed.set_gAllocInV2(inV2);
- scriptRelaxed.forEach_testMinUshort3Ushort3Ushort3(inV1, out);
- verifyResultsMinUshort3Ushort3Ushort3(inV1, inV2, out, true);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinUshort3Ushort3Ushort3: " + e.toString());
- }
- }
-
- private void verifyResultsMinUshort3Ushort3Ushort3(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
- short[] arrayInV1 = new short[INPUTSIZE * 4];
- inV1.copyTo(arrayInV1);
- short[] arrayInV2 = new short[INPUTSIZE * 4];
- inV2.copyTo(arrayInV2);
- short[] arrayOut = new short[INPUTSIZE * 4];
- out.copyTo(arrayOut);
- for (int i = 0; i < INPUTSIZE; i++) {
- for (int j = 0; j < 3 ; j++) {
- // Extract the inputs.
- ArgumentsUshortUshortUshort args = new ArgumentsUshortUshortUshort();
- args.inV1 = arrayInV1[i * 4 + j];
- args.inV2 = arrayInV2[i * 4 + j];
- // Figure out what the outputs should have been.
- Floaty.setRelaxed(relaxed);
- CoreMathVerifier.computeMin(args);
- // Figure out what the outputs should have been.
- boolean valid = true;
- if (args.out != arrayOut[i * 4 + j]) {
- valid = false;
- }
- if (!valid) {
- StringBuilder message = new StringBuilder();
- message.append("Input inV1: ");
- message.append(String.format("0x%x", args.inV1));
- message.append("\n");
- message.append("Input inV2: ");
- message.append(String.format("0x%x", args.inV2));
- message.append("\n");
- message.append("Expected output out: ");
- message.append(String.format("0x%x", args.out));
- message.append("\n");
- message.append("Actual output out: ");
- message.append(String.format("0x%x", arrayOut[i * 4 + j]));
- if (args.out != arrayOut[i * 4 + j]) {
- message.append(" FAIL");
- }
- message.append("\n");
- assertTrue("Incorrect output for checkMinUshort3Ushort3Ushort3" +
- (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
- }
- }
- }
- }
-
private void checkMinInt3Int3Int3() {
Allocation inV1 = createRandomAllocation(mRS, Element.DataType.SIGNED_32, 3, 0xe4f086806849fbc6l, false);
Allocation inV2 = createRandomAllocation(mRS, Element.DataType.SIGNED_32, 3, 0xe4f086806849fbc7l, false);
@@ -1474,336 +1600,6 @@
}
}
- private void checkMinUint3Uint3Uint3() {
- Allocation inV1 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_32, 3, 0xe2100b4bbe974b3l, false);
- Allocation inV2 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_32, 3, 0xe2100b4bbe974b4l, false);
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_32, 3), INPUTSIZE);
- script.set_gAllocInV2(inV2);
- script.forEach_testMinUint3Uint3Uint3(inV1, out);
- verifyResultsMinUint3Uint3Uint3(inV1, inV2, out, false);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinUint3Uint3Uint3: " + e.toString());
- }
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_32, 3), INPUTSIZE);
- scriptRelaxed.set_gAllocInV2(inV2);
- scriptRelaxed.forEach_testMinUint3Uint3Uint3(inV1, out);
- verifyResultsMinUint3Uint3Uint3(inV1, inV2, out, true);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinUint3Uint3Uint3: " + e.toString());
- }
- }
-
- private void verifyResultsMinUint3Uint3Uint3(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
- int[] arrayInV1 = new int[INPUTSIZE * 4];
- inV1.copyTo(arrayInV1);
- int[] arrayInV2 = new int[INPUTSIZE * 4];
- inV2.copyTo(arrayInV2);
- int[] arrayOut = new int[INPUTSIZE * 4];
- out.copyTo(arrayOut);
- for (int i = 0; i < INPUTSIZE; i++) {
- for (int j = 0; j < 3 ; j++) {
- // Extract the inputs.
- ArgumentsUintUintUint args = new ArgumentsUintUintUint();
- args.inV1 = arrayInV1[i * 4 + j];
- args.inV2 = arrayInV2[i * 4 + j];
- // Figure out what the outputs should have been.
- Floaty.setRelaxed(relaxed);
- CoreMathVerifier.computeMin(args);
- // Figure out what the outputs should have been.
- boolean valid = true;
- if (args.out != arrayOut[i * 4 + j]) {
- valid = false;
- }
- if (!valid) {
- StringBuilder message = new StringBuilder();
- message.append("Input inV1: ");
- message.append(String.format("0x%x", args.inV1));
- message.append("\n");
- message.append("Input inV2: ");
- message.append(String.format("0x%x", args.inV2));
- message.append("\n");
- message.append("Expected output out: ");
- message.append(String.format("0x%x", args.out));
- message.append("\n");
- message.append("Actual output out: ");
- message.append(String.format("0x%x", arrayOut[i * 4 + j]));
- if (args.out != arrayOut[i * 4 + j]) {
- message.append(" FAIL");
- }
- message.append("\n");
- assertTrue("Incorrect output for checkMinUint3Uint3Uint3" +
- (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
- }
- }
- }
- }
-
- private void checkMinChar4Char4Char4() {
- Allocation inV1 = createRandomAllocation(mRS, Element.DataType.SIGNED_8, 4, 0x96e9fe1589a3ed38l, false);
- Allocation inV2 = createRandomAllocation(mRS, Element.DataType.SIGNED_8, 4, 0x96e9fe1589a3ed39l, false);
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_8, 4), INPUTSIZE);
- script.set_gAllocInV2(inV2);
- script.forEach_testMinChar4Char4Char4(inV1, out);
- verifyResultsMinChar4Char4Char4(inV1, inV2, out, false);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinChar4Char4Char4: " + e.toString());
- }
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_8, 4), INPUTSIZE);
- scriptRelaxed.set_gAllocInV2(inV2);
- scriptRelaxed.forEach_testMinChar4Char4Char4(inV1, out);
- verifyResultsMinChar4Char4Char4(inV1, inV2, out, true);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinChar4Char4Char4: " + e.toString());
- }
- }
-
- private void verifyResultsMinChar4Char4Char4(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
- byte[] arrayInV1 = new byte[INPUTSIZE * 4];
- inV1.copyTo(arrayInV1);
- byte[] arrayInV2 = new byte[INPUTSIZE * 4];
- inV2.copyTo(arrayInV2);
- byte[] arrayOut = new byte[INPUTSIZE * 4];
- out.copyTo(arrayOut);
- for (int i = 0; i < INPUTSIZE; i++) {
- for (int j = 0; j < 4 ; j++) {
- // Extract the inputs.
- ArgumentsCharCharChar args = new ArgumentsCharCharChar();
- args.inV1 = arrayInV1[i * 4 + j];
- args.inV2 = arrayInV2[i * 4 + j];
- // Figure out what the outputs should have been.
- Floaty.setRelaxed(relaxed);
- CoreMathVerifier.computeMin(args);
- // Figure out what the outputs should have been.
- boolean valid = true;
- if (args.out != arrayOut[i * 4 + j]) {
- valid = false;
- }
- if (!valid) {
- StringBuilder message = new StringBuilder();
- message.append("Input inV1: ");
- message.append(String.format("%d", args.inV1));
- message.append("\n");
- message.append("Input inV2: ");
- message.append(String.format("%d", args.inV2));
- message.append("\n");
- message.append("Expected output out: ");
- message.append(String.format("%d", args.out));
- message.append("\n");
- message.append("Actual output out: ");
- message.append(String.format("%d", arrayOut[i * 4 + j]));
- if (args.out != arrayOut[i * 4 + j]) {
- message.append(" FAIL");
- }
- message.append("\n");
- assertTrue("Incorrect output for checkMinChar4Char4Char4" +
- (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
- }
- }
- }
- }
-
- private void checkMinUchar4Uchar4Uchar4() {
- Allocation inV1 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_8, 4, 0x771f30ddb702af15l, false);
- Allocation inV2 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_8, 4, 0x771f30ddb702af16l, false);
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_8, 4), INPUTSIZE);
- script.set_gAllocInV2(inV2);
- script.forEach_testMinUchar4Uchar4Uchar4(inV1, out);
- verifyResultsMinUchar4Uchar4Uchar4(inV1, inV2, out, false);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinUchar4Uchar4Uchar4: " + e.toString());
- }
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_8, 4), INPUTSIZE);
- scriptRelaxed.set_gAllocInV2(inV2);
- scriptRelaxed.forEach_testMinUchar4Uchar4Uchar4(inV1, out);
- verifyResultsMinUchar4Uchar4Uchar4(inV1, inV2, out, true);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinUchar4Uchar4Uchar4: " + e.toString());
- }
- }
-
- private void verifyResultsMinUchar4Uchar4Uchar4(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
- byte[] arrayInV1 = new byte[INPUTSIZE * 4];
- inV1.copyTo(arrayInV1);
- byte[] arrayInV2 = new byte[INPUTSIZE * 4];
- inV2.copyTo(arrayInV2);
- byte[] arrayOut = new byte[INPUTSIZE * 4];
- out.copyTo(arrayOut);
- for (int i = 0; i < INPUTSIZE; i++) {
- for (int j = 0; j < 4 ; j++) {
- // Extract the inputs.
- ArgumentsUcharUcharUchar args = new ArgumentsUcharUcharUchar();
- args.inV1 = arrayInV1[i * 4 + j];
- args.inV2 = arrayInV2[i * 4 + j];
- // Figure out what the outputs should have been.
- Floaty.setRelaxed(relaxed);
- CoreMathVerifier.computeMin(args);
- // Figure out what the outputs should have been.
- boolean valid = true;
- if (args.out != arrayOut[i * 4 + j]) {
- valid = false;
- }
- if (!valid) {
- StringBuilder message = new StringBuilder();
- message.append("Input inV1: ");
- message.append(String.format("0x%x", args.inV1));
- message.append("\n");
- message.append("Input inV2: ");
- message.append(String.format("0x%x", args.inV2));
- message.append("\n");
- message.append("Expected output out: ");
- message.append(String.format("0x%x", args.out));
- message.append("\n");
- message.append("Actual output out: ");
- message.append(String.format("0x%x", arrayOut[i * 4 + j]));
- if (args.out != arrayOut[i * 4 + j]) {
- message.append(" FAIL");
- }
- message.append("\n");
- assertTrue("Incorrect output for checkMinUchar4Uchar4Uchar4" +
- (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
- }
- }
- }
- }
-
- private void checkMinShort4Short4Short4() {
- Allocation inV1 = createRandomAllocation(mRS, Element.DataType.SIGNED_16, 4, 0x3e7838df6c865f96l, false);
- Allocation inV2 = createRandomAllocation(mRS, Element.DataType.SIGNED_16, 4, 0x3e7838df6c865f97l, false);
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_16, 4), INPUTSIZE);
- script.set_gAllocInV2(inV2);
- script.forEach_testMinShort4Short4Short4(inV1, out);
- verifyResultsMinShort4Short4Short4(inV1, inV2, out, false);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinShort4Short4Short4: " + e.toString());
- }
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_16, 4), INPUTSIZE);
- scriptRelaxed.set_gAllocInV2(inV2);
- scriptRelaxed.forEach_testMinShort4Short4Short4(inV1, out);
- verifyResultsMinShort4Short4Short4(inV1, inV2, out, true);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinShort4Short4Short4: " + e.toString());
- }
- }
-
- private void verifyResultsMinShort4Short4Short4(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
- short[] arrayInV1 = new short[INPUTSIZE * 4];
- inV1.copyTo(arrayInV1);
- short[] arrayInV2 = new short[INPUTSIZE * 4];
- inV2.copyTo(arrayInV2);
- short[] arrayOut = new short[INPUTSIZE * 4];
- out.copyTo(arrayOut);
- for (int i = 0; i < INPUTSIZE; i++) {
- for (int j = 0; j < 4 ; j++) {
- // Extract the inputs.
- ArgumentsShortShortShort args = new ArgumentsShortShortShort();
- args.inV1 = arrayInV1[i * 4 + j];
- args.inV2 = arrayInV2[i * 4 + j];
- // Figure out what the outputs should have been.
- Floaty.setRelaxed(relaxed);
- CoreMathVerifier.computeMin(args);
- // Figure out what the outputs should have been.
- boolean valid = true;
- if (args.out != arrayOut[i * 4 + j]) {
- valid = false;
- }
- if (!valid) {
- StringBuilder message = new StringBuilder();
- message.append("Input inV1: ");
- message.append(String.format("%d", args.inV1));
- message.append("\n");
- message.append("Input inV2: ");
- message.append(String.format("%d", args.inV2));
- message.append("\n");
- message.append("Expected output out: ");
- message.append(String.format("%d", args.out));
- message.append("\n");
- message.append("Actual output out: ");
- message.append(String.format("%d", arrayOut[i * 4 + j]));
- if (args.out != arrayOut[i * 4 + j]) {
- message.append(" FAIL");
- }
- message.append("\n");
- assertTrue("Incorrect output for checkMinShort4Short4Short4" +
- (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
- }
- }
- }
- }
-
- private void checkMinUshort4Ushort4Ushort4() {
- Allocation inV1 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_16, 4, 0x92d4a27a41fb0bd3l, false);
- Allocation inV2 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_16, 4, 0x92d4a27a41fb0bd4l, false);
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_16, 4), INPUTSIZE);
- script.set_gAllocInV2(inV2);
- script.forEach_testMinUshort4Ushort4Ushort4(inV1, out);
- verifyResultsMinUshort4Ushort4Ushort4(inV1, inV2, out, false);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinUshort4Ushort4Ushort4: " + e.toString());
- }
- try {
- Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_16, 4), INPUTSIZE);
- scriptRelaxed.set_gAllocInV2(inV2);
- scriptRelaxed.forEach_testMinUshort4Ushort4Ushort4(inV1, out);
- verifyResultsMinUshort4Ushort4Ushort4(inV1, inV2, out, true);
- } catch (Exception e) {
- throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinUshort4Ushort4Ushort4: " + e.toString());
- }
- }
-
- private void verifyResultsMinUshort4Ushort4Ushort4(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
- short[] arrayInV1 = new short[INPUTSIZE * 4];
- inV1.copyTo(arrayInV1);
- short[] arrayInV2 = new short[INPUTSIZE * 4];
- inV2.copyTo(arrayInV2);
- short[] arrayOut = new short[INPUTSIZE * 4];
- out.copyTo(arrayOut);
- for (int i = 0; i < INPUTSIZE; i++) {
- for (int j = 0; j < 4 ; j++) {
- // Extract the inputs.
- ArgumentsUshortUshortUshort args = new ArgumentsUshortUshortUshort();
- args.inV1 = arrayInV1[i * 4 + j];
- args.inV2 = arrayInV2[i * 4 + j];
- // Figure out what the outputs should have been.
- Floaty.setRelaxed(relaxed);
- CoreMathVerifier.computeMin(args);
- // Figure out what the outputs should have been.
- boolean valid = true;
- if (args.out != arrayOut[i * 4 + j]) {
- valid = false;
- }
- if (!valid) {
- StringBuilder message = new StringBuilder();
- message.append("Input inV1: ");
- message.append(String.format("0x%x", args.inV1));
- message.append("\n");
- message.append("Input inV2: ");
- message.append(String.format("0x%x", args.inV2));
- message.append("\n");
- message.append("Expected output out: ");
- message.append(String.format("0x%x", args.out));
- message.append("\n");
- message.append("Actual output out: ");
- message.append(String.format("0x%x", arrayOut[i * 4 + j]));
- if (args.out != arrayOut[i * 4 + j]) {
- message.append(" FAIL");
- }
- message.append("\n");
- assertTrue("Incorrect output for checkMinUshort4Ushort4Ushort4" +
- (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
- }
- }
- }
- }
-
private void checkMinInt4Int4Int4() {
Allocation inV1 = createRandomAllocation(mRS, Element.DataType.SIGNED_32, 4, 0xf7db1a07a1d0fe9l, false);
Allocation inV2 = createRandomAllocation(mRS, Element.DataType.SIGNED_32, 4, 0xf7db1a07a1d0feal, false);
@@ -1870,6 +1666,210 @@
}
}
+ public class ArgumentsUintUintUint {
+ public int inV1;
+ public int inV2;
+ public int out;
+ }
+
+ private void checkMinUintUintUint() {
+ Allocation inV1 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_32, 1, 0xb3dbca2d537cf298l, false);
+ Allocation inV2 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_32, 1, 0xb3dbca2d537cf299l, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_32, 1), INPUTSIZE);
+ script.set_gAllocInV2(inV2);
+ script.forEach_testMinUintUintUint(inV1, out);
+ verifyResultsMinUintUintUint(inV1, inV2, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinUintUintUint: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_32, 1), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV2(inV2);
+ scriptRelaxed.forEach_testMinUintUintUint(inV1, out);
+ verifyResultsMinUintUintUint(inV1, inV2, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinUintUintUint: " + e.toString());
+ }
+ }
+
+ private void verifyResultsMinUintUintUint(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
+ int[] arrayInV1 = new int[INPUTSIZE * 1];
+ inV1.copyTo(arrayInV1);
+ int[] arrayInV2 = new int[INPUTSIZE * 1];
+ inV2.copyTo(arrayInV2);
+ int[] arrayOut = new int[INPUTSIZE * 1];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 1 ; j++) {
+ // Extract the inputs.
+ ArgumentsUintUintUint args = new ArgumentsUintUintUint();
+ args.inV1 = arrayInV1[i];
+ args.inV2 = arrayInV2[i];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeMin(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 1 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV1: ");
+ message.append(String.format("0x%x", args.inV1));
+ message.append("\n");
+ message.append("Input inV2: ");
+ message.append(String.format("0x%x", args.inV2));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 1 + j]));
+ if (args.out != arrayOut[i * 1 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkMinUintUintUint" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkMinUint2Uint2Uint2() {
+ Allocation inV1 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_32, 2, 0xb8cf8481d731a1eel, false);
+ Allocation inV2 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_32, 2, 0xb8cf8481d731a1efl, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_32, 2), INPUTSIZE);
+ script.set_gAllocInV2(inV2);
+ script.forEach_testMinUint2Uint2Uint2(inV1, out);
+ verifyResultsMinUint2Uint2Uint2(inV1, inV2, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinUint2Uint2Uint2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_32, 2), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV2(inV2);
+ scriptRelaxed.forEach_testMinUint2Uint2Uint2(inV1, out);
+ verifyResultsMinUint2Uint2Uint2(inV1, inV2, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinUint2Uint2Uint2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsMinUint2Uint2Uint2(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
+ int[] arrayInV1 = new int[INPUTSIZE * 2];
+ inV1.copyTo(arrayInV1);
+ int[] arrayInV2 = new int[INPUTSIZE * 2];
+ inV2.copyTo(arrayInV2);
+ int[] arrayOut = new int[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsUintUintUint args = new ArgumentsUintUintUint();
+ args.inV1 = arrayInV1[i * 2 + j];
+ args.inV2 = arrayInV2[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeMin(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV1: ");
+ message.append(String.format("0x%x", args.inV1));
+ message.append("\n");
+ message.append("Input inV2: ");
+ message.append(String.format("0x%x", args.inV2));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkMinUint2Uint2Uint2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkMinUint3Uint3Uint3() {
+ Allocation inV1 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_32, 3, 0xe2100b4bbe974b3l, false);
+ Allocation inV2 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_32, 3, 0xe2100b4bbe974b4l, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_32, 3), INPUTSIZE);
+ script.set_gAllocInV2(inV2);
+ script.forEach_testMinUint3Uint3Uint3(inV1, out);
+ verifyResultsMinUint3Uint3Uint3(inV1, inV2, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinUint3Uint3Uint3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_32, 3), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV2(inV2);
+ scriptRelaxed.forEach_testMinUint3Uint3Uint3(inV1, out);
+ verifyResultsMinUint3Uint3Uint3(inV1, inV2, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinUint3Uint3Uint3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsMinUint3Uint3Uint3(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
+ int[] arrayInV1 = new int[INPUTSIZE * 4];
+ inV1.copyTo(arrayInV1);
+ int[] arrayInV2 = new int[INPUTSIZE * 4];
+ inV2.copyTo(arrayInV2);
+ int[] arrayOut = new int[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsUintUintUint args = new ArgumentsUintUintUint();
+ args.inV1 = arrayInV1[i * 4 + j];
+ args.inV2 = arrayInV2[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeMin(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV1: ");
+ message.append(String.format("0x%x", args.inV1));
+ message.append("\n");
+ message.append("Input inV2: ");
+ message.append(String.format("0x%x", args.inV2));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkMinUint3Uint3Uint3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
private void checkMinUint4Uint4Uint4() {
Allocation inV1 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_32, 4, 0x63727ce7a0a14778l, false);
Allocation inV2 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_32, 4, 0x63727ce7a0a14779l, false);
@@ -1936,34 +1936,582 @@
}
}
+ public class ArgumentsLongLongLong {
+ public long inV1;
+ public long inV2;
+ public long out;
+ }
+
+ private void checkMinLongLongLong() {
+ Allocation inV1 = createRandomAllocation(mRS, Element.DataType.SIGNED_64, 1, 0x20ce185251c10eb2l, false);
+ Allocation inV2 = createRandomAllocation(mRS, Element.DataType.SIGNED_64, 1, 0x20ce185251c10eb3l, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 1), INPUTSIZE);
+ script.set_gAllocInV2(inV2);
+ script.forEach_testMinLongLongLong(inV1, out);
+ verifyResultsMinLongLongLong(inV1, inV2, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinLongLongLong: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 1), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV2(inV2);
+ scriptRelaxed.forEach_testMinLongLongLong(inV1, out);
+ verifyResultsMinLongLongLong(inV1, inV2, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinLongLongLong: " + e.toString());
+ }
+ }
+
+ private void verifyResultsMinLongLongLong(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
+ long[] arrayInV1 = new long[INPUTSIZE * 1];
+ inV1.copyTo(arrayInV1);
+ long[] arrayInV2 = new long[INPUTSIZE * 1];
+ inV2.copyTo(arrayInV2);
+ long[] arrayOut = new long[INPUTSIZE * 1];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 1 ; j++) {
+ // Extract the inputs.
+ ArgumentsLongLongLong args = new ArgumentsLongLongLong();
+ args.inV1 = arrayInV1[i];
+ args.inV2 = arrayInV2[i];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeMin(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 1 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV1: ");
+ message.append(String.format("%d", args.inV1));
+ message.append("\n");
+ message.append("Input inV2: ");
+ message.append(String.format("%d", args.inV2));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 1 + j]));
+ if (args.out != arrayOut[i * 1 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkMinLongLongLong" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkMinLong2Long2Long2() {
+ Allocation inV1 = createRandomAllocation(mRS, Element.DataType.SIGNED_64, 2, 0x2285e00a094676a0l, false);
+ Allocation inV2 = createRandomAllocation(mRS, Element.DataType.SIGNED_64, 2, 0x2285e00a094676a1l, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 2), INPUTSIZE);
+ script.set_gAllocInV2(inV2);
+ script.forEach_testMinLong2Long2Long2(inV1, out);
+ verifyResultsMinLong2Long2Long2(inV1, inV2, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinLong2Long2Long2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 2), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV2(inV2);
+ scriptRelaxed.forEach_testMinLong2Long2Long2(inV1, out);
+ verifyResultsMinLong2Long2Long2(inV1, inV2, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinLong2Long2Long2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsMinLong2Long2Long2(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
+ long[] arrayInV1 = new long[INPUTSIZE * 2];
+ inV1.copyTo(arrayInV1);
+ long[] arrayInV2 = new long[INPUTSIZE * 2];
+ inV2.copyTo(arrayInV2);
+ long[] arrayOut = new long[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsLongLongLong args = new ArgumentsLongLongLong();
+ args.inV1 = arrayInV1[i * 2 + j];
+ args.inV2 = arrayInV2[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeMin(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV1: ");
+ message.append(String.format("%d", args.inV1));
+ message.append("\n");
+ message.append("Input inV2: ");
+ message.append(String.format("%d", args.inV2));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkMinLong2Long2Long2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkMinLong3Long3Long3() {
+ Allocation inV1 = createRandomAllocation(mRS, Element.DataType.SIGNED_64, 3, 0x77d75c3cedfe4965l, false);
+ Allocation inV2 = createRandomAllocation(mRS, Element.DataType.SIGNED_64, 3, 0x77d75c3cedfe4966l, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 3), INPUTSIZE);
+ script.set_gAllocInV2(inV2);
+ script.forEach_testMinLong3Long3Long3(inV1, out);
+ verifyResultsMinLong3Long3Long3(inV1, inV2, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinLong3Long3Long3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 3), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV2(inV2);
+ scriptRelaxed.forEach_testMinLong3Long3Long3(inV1, out);
+ verifyResultsMinLong3Long3Long3(inV1, inV2, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinLong3Long3Long3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsMinLong3Long3Long3(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
+ long[] arrayInV1 = new long[INPUTSIZE * 4];
+ inV1.copyTo(arrayInV1);
+ long[] arrayInV2 = new long[INPUTSIZE * 4];
+ inV2.copyTo(arrayInV2);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsLongLongLong args = new ArgumentsLongLongLong();
+ args.inV1 = arrayInV1[i * 4 + j];
+ args.inV2 = arrayInV2[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeMin(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV1: ");
+ message.append(String.format("%d", args.inV1));
+ message.append("\n");
+ message.append("Input inV2: ");
+ message.append(String.format("%d", args.inV2));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkMinLong3Long3Long3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkMinLong4Long4Long4() {
+ Allocation inV1 = createRandomAllocation(mRS, Element.DataType.SIGNED_64, 4, 0xcd28d86fd2b61c2al, false);
+ Allocation inV2 = createRandomAllocation(mRS, Element.DataType.SIGNED_64, 4, 0xcd28d86fd2b61c2bl, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 4), INPUTSIZE);
+ script.set_gAllocInV2(inV2);
+ script.forEach_testMinLong4Long4Long4(inV1, out);
+ verifyResultsMinLong4Long4Long4(inV1, inV2, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinLong4Long4Long4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.SIGNED_64, 4), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV2(inV2);
+ scriptRelaxed.forEach_testMinLong4Long4Long4(inV1, out);
+ verifyResultsMinLong4Long4Long4(inV1, inV2, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinLong4Long4Long4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsMinLong4Long4Long4(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
+ long[] arrayInV1 = new long[INPUTSIZE * 4];
+ inV1.copyTo(arrayInV1);
+ long[] arrayInV2 = new long[INPUTSIZE * 4];
+ inV2.copyTo(arrayInV2);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsLongLongLong args = new ArgumentsLongLongLong();
+ args.inV1 = arrayInV1[i * 4 + j];
+ args.inV2 = arrayInV2[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeMin(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV1: ");
+ message.append(String.format("%d", args.inV1));
+ message.append("\n");
+ message.append("Input inV2: ");
+ message.append(String.format("%d", args.inV2));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("%d", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%d", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkMinLong4Long4Long4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ public class ArgumentsUlongUlongUlong {
+ public long inV1;
+ public long inV2;
+ public long out;
+ }
+
+ private void checkMinUlongUlongUlong() {
+ Allocation inV1 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_64, 1, 0x9ea8f1e67008ea67l, false);
+ Allocation inV2 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_64, 1, 0x9ea8f1e67008ea68l, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 1), INPUTSIZE);
+ script.set_gAllocInV2(inV2);
+ script.forEach_testMinUlongUlongUlong(inV1, out);
+ verifyResultsMinUlongUlongUlong(inV1, inV2, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinUlongUlongUlong: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 1), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV2(inV2);
+ scriptRelaxed.forEach_testMinUlongUlongUlong(inV1, out);
+ verifyResultsMinUlongUlongUlong(inV1, inV2, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinUlongUlongUlong: " + e.toString());
+ }
+ }
+
+ private void verifyResultsMinUlongUlongUlong(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
+ long[] arrayInV1 = new long[INPUTSIZE * 1];
+ inV1.copyTo(arrayInV1);
+ long[] arrayInV2 = new long[INPUTSIZE * 1];
+ inV2.copyTo(arrayInV2);
+ long[] arrayOut = new long[INPUTSIZE * 1];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 1 ; j++) {
+ // Extract the inputs.
+ ArgumentsUlongUlongUlong args = new ArgumentsUlongUlongUlong();
+ args.inV1 = arrayInV1[i];
+ args.inV2 = arrayInV2[i];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeMin(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 1 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV1: ");
+ message.append(String.format("0x%x", args.inV1));
+ message.append("\n");
+ message.append("Input inV2: ");
+ message.append(String.format("0x%x", args.inV2));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 1 + j]));
+ if (args.out != arrayOut[i * 1 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkMinUlongUlongUlong" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkMinUlong2Ulong2Ulong2() {
+ Allocation inV1 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_64, 2, 0x26bb4add3110402dl, false);
+ Allocation inV2 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_64, 2, 0x26bb4add3110402el, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 2), INPUTSIZE);
+ script.set_gAllocInV2(inV2);
+ script.forEach_testMinUlong2Ulong2Ulong2(inV1, out);
+ verifyResultsMinUlong2Ulong2Ulong2(inV1, inV2, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinUlong2Ulong2Ulong2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 2), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV2(inV2);
+ scriptRelaxed.forEach_testMinUlong2Ulong2Ulong2(inV1, out);
+ verifyResultsMinUlong2Ulong2Ulong2(inV1, inV2, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinUlong2Ulong2Ulong2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsMinUlong2Ulong2Ulong2(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
+ long[] arrayInV1 = new long[INPUTSIZE * 2];
+ inV1.copyTo(arrayInV1);
+ long[] arrayInV2 = new long[INPUTSIZE * 2];
+ inV2.copyTo(arrayInV2);
+ long[] arrayOut = new long[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsUlongUlongUlong args = new ArgumentsUlongUlongUlong();
+ args.inV1 = arrayInV1[i * 2 + j];
+ args.inV2 = arrayInV2[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeMin(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 2 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV1: ");
+ message.append(String.format("0x%x", args.inV1));
+ message.append("\n");
+ message.append("Input inV2: ");
+ message.append(String.format("0x%x", args.inV2));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 2 + j]));
+ if (args.out != arrayOut[i * 2 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkMinUlong2Ulong2Ulong2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkMinUlong3Ulong3Ulong3() {
+ Allocation inV1 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_64, 3, 0x53ac9a3d815a8638l, false);
+ Allocation inV2 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_64, 3, 0x53ac9a3d815a8639l, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 3), INPUTSIZE);
+ script.set_gAllocInV2(inV2);
+ script.forEach_testMinUlong3Ulong3Ulong3(inV1, out);
+ verifyResultsMinUlong3Ulong3Ulong3(inV1, inV2, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinUlong3Ulong3Ulong3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 3), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV2(inV2);
+ scriptRelaxed.forEach_testMinUlong3Ulong3Ulong3(inV1, out);
+ verifyResultsMinUlong3Ulong3Ulong3(inV1, inV2, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinUlong3Ulong3Ulong3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsMinUlong3Ulong3Ulong3(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
+ long[] arrayInV1 = new long[INPUTSIZE * 4];
+ inV1.copyTo(arrayInV1);
+ long[] arrayInV2 = new long[INPUTSIZE * 4];
+ inV2.copyTo(arrayInV2);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsUlongUlongUlong args = new ArgumentsUlongUlongUlong();
+ args.inV1 = arrayInV1[i * 4 + j];
+ args.inV2 = arrayInV2[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeMin(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV1: ");
+ message.append(String.format("0x%x", args.inV1));
+ message.append("\n");
+ message.append("Input inV2: ");
+ message.append(String.format("0x%x", args.inV2));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkMinUlong3Ulong3Ulong3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkMinUlong4Ulong4Ulong4() {
+ Allocation inV1 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_64, 4, 0x809de99dd1a4cc43l, false);
+ Allocation inV2 = createRandomAllocation(mRS, Element.DataType.UNSIGNED_64, 4, 0x809de99dd1a4cc44l, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 4), INPUTSIZE);
+ script.set_gAllocInV2(inV2);
+ script.forEach_testMinUlong4Ulong4Ulong4(inV1, out);
+ verifyResultsMinUlong4Ulong4Ulong4(inV1, inV2, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinUlong4Ulong4Ulong4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.UNSIGNED_64, 4), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV2(inV2);
+ scriptRelaxed.forEach_testMinUlong4Ulong4Ulong4(inV1, out);
+ verifyResultsMinUlong4Ulong4Ulong4(inV1, inV2, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinUlong4Ulong4Ulong4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsMinUlong4Ulong4Ulong4(Allocation inV1, Allocation inV2, Allocation out, boolean relaxed) {
+ long[] arrayInV1 = new long[INPUTSIZE * 4];
+ inV1.copyTo(arrayInV1);
+ long[] arrayInV2 = new long[INPUTSIZE * 4];
+ inV2.copyTo(arrayInV2);
+ long[] arrayOut = new long[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsUlongUlongUlong args = new ArgumentsUlongUlongUlong();
+ args.inV1 = arrayInV1[i * 4 + j];
+ args.inV2 = arrayInV2[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeMin(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (args.out != arrayOut[i * 4 + j]) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inV1: ");
+ message.append(String.format("0x%x", args.inV1));
+ message.append("\n");
+ message.append("Input inV2: ");
+ message.append(String.format("0x%x", args.inV2));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(String.format("0x%x", args.out));
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("0x%x", arrayOut[i * 4 + j]));
+ if (args.out != arrayOut[i * 4 + j]) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkMinUlong4Ulong4Ulong4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
public void testMin() {
checkMinFloatFloatFloat();
checkMinFloat2Float2Float2();
checkMinFloat3Float3Float3();
checkMinFloat4Float4Float4();
checkMinCharCharChar();
- checkMinUcharUcharUchar();
- checkMinShortShortShort();
- checkMinUshortUshortUshort();
- checkMinIntIntInt();
- checkMinUintUintUint();
checkMinChar2Char2Char2();
- checkMinUchar2Uchar2Uchar2();
- checkMinShort2Short2Short2();
- checkMinUshort2Ushort2Ushort2();
- checkMinInt2Int2Int2();
- checkMinUint2Uint2Uint2();
checkMinChar3Char3Char3();
- checkMinUchar3Uchar3Uchar3();
- checkMinShort3Short3Short3();
- checkMinUshort3Ushort3Ushort3();
- checkMinInt3Int3Int3();
- checkMinUint3Uint3Uint3();
checkMinChar4Char4Char4();
+ checkMinUcharUcharUchar();
+ checkMinUchar2Uchar2Uchar2();
+ checkMinUchar3Uchar3Uchar3();
checkMinUchar4Uchar4Uchar4();
+ checkMinShortShortShort();
+ checkMinShort2Short2Short2();
+ checkMinShort3Short3Short3();
checkMinShort4Short4Short4();
+ checkMinUshortUshortUshort();
+ checkMinUshort2Ushort2Ushort2();
+ checkMinUshort3Ushort3Ushort3();
checkMinUshort4Ushort4Ushort4();
+ checkMinIntIntInt();
+ checkMinInt2Int2Int2();
+ checkMinInt3Int3Int3();
checkMinInt4Int4Int4();
+ checkMinUintUintUint();
+ checkMinUint2Uint2Uint2();
+ checkMinUint3Uint3Uint3();
checkMinUint4Uint4Uint4();
+ checkMinLongLongLong();
+ checkMinLong2Long2Long2();
+ checkMinLong3Long3Long3();
+ checkMinLong4Long4Long4();
+ checkMinUlongUlongUlong();
+ checkMinUlong2Ulong2Ulong2();
+ checkMinUlong3Ulong3Ulong3();
+ checkMinUlong4Ulong4Ulong4();
}
}
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/TestStep.java b/tests/tests/renderscript/src/android/renderscript/cts/TestStep.java
index 9f640de..f01170d 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/TestStep.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/TestStep.java
@@ -523,6 +523,213 @@
}
}
+ private void checkStepFloatFloat2Float2() {
+ Allocation inEdge = createRandomAllocation(mRS, Element.DataType.FLOAT_32, 1, 0x70a0554e664b1852l, false);
+ Allocation inV = createRandomAllocation(mRS, Element.DataType.FLOAT_32, 2, 0xdd7f0d444e2f7c5l, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_32, 2), INPUTSIZE);
+ script.set_gAllocInV(inV);
+ script.forEach_testStepFloatFloat2Float2(inEdge, out);
+ verifyResultsStepFloatFloat2Float2(inEdge, inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testStepFloatFloat2Float2: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_32, 2), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV(inV);
+ scriptRelaxed.forEach_testStepFloatFloat2Float2(inEdge, out);
+ verifyResultsStepFloatFloat2Float2(inEdge, inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testStepFloatFloat2Float2: " + e.toString());
+ }
+ }
+
+ private void verifyResultsStepFloatFloat2Float2(Allocation inEdge, Allocation inV, Allocation out, boolean relaxed) {
+ float[] arrayInEdge = new float[INPUTSIZE * 1];
+ inEdge.copyTo(arrayInEdge);
+ float[] arrayInV = new float[INPUTSIZE * 2];
+ inV.copyTo(arrayInV);
+ float[] arrayOut = new float[INPUTSIZE * 2];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 2 ; j++) {
+ // Extract the inputs.
+ ArgumentsFloatFloatFloat args = new ArgumentsFloatFloatFloat();
+ args.inEdge = arrayInEdge[i];
+ args.inV = arrayInV[i * 2 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeStep(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (!args.out.couldBe(arrayOut[i * 2 + j])) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inEdge: ");
+ message.append(String.format("%14.8g %8x %15a",
+ args.inEdge, Float.floatToRawIntBits(args.inEdge), args.inEdge));
+ message.append("\n");
+ message.append("Input inV: ");
+ message.append(String.format("%14.8g %8x %15a",
+ args.inV, Float.floatToRawIntBits(args.inV), args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(args.out.toString());
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%14.8g %8x %15a",
+ arrayOut[i * 2 + j], Float.floatToRawIntBits(arrayOut[i * 2 + j]), arrayOut[i * 2 + j]));
+ if (!args.out.couldBe(arrayOut[i * 2 + j])) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkStepFloatFloat2Float2" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkStepFloatFloat3Float3() {
+ Allocation inEdge = createRandomAllocation(mRS, Element.DataType.FLOAT_32, 1, 0x9b2d75ce91abcbccl, false);
+ Allocation inV = createRandomAllocation(mRS, Element.DataType.FLOAT_32, 3, 0xdd9b9ef3afe18a3l, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_32, 3), INPUTSIZE);
+ script.set_gAllocInV(inV);
+ script.forEach_testStepFloatFloat3Float3(inEdge, out);
+ verifyResultsStepFloatFloat3Float3(inEdge, inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testStepFloatFloat3Float3: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_32, 3), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV(inV);
+ scriptRelaxed.forEach_testStepFloatFloat3Float3(inEdge, out);
+ verifyResultsStepFloatFloat3Float3(inEdge, inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testStepFloatFloat3Float3: " + e.toString());
+ }
+ }
+
+ private void verifyResultsStepFloatFloat3Float3(Allocation inEdge, Allocation inV, Allocation out, boolean relaxed) {
+ float[] arrayInEdge = new float[INPUTSIZE * 1];
+ inEdge.copyTo(arrayInEdge);
+ float[] arrayInV = new float[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ float[] arrayOut = new float[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 3 ; j++) {
+ // Extract the inputs.
+ ArgumentsFloatFloatFloat args = new ArgumentsFloatFloatFloat();
+ args.inEdge = arrayInEdge[i];
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeStep(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inEdge: ");
+ message.append(String.format("%14.8g %8x %15a",
+ args.inEdge, Float.floatToRawIntBits(args.inEdge), args.inEdge));
+ message.append("\n");
+ message.append("Input inV: ");
+ message.append(String.format("%14.8g %8x %15a",
+ args.inV, Float.floatToRawIntBits(args.inV), args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(args.out.toString());
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%14.8g %8x %15a",
+ arrayOut[i * 4 + j], Float.floatToRawIntBits(arrayOut[i * 4 + j]), arrayOut[i * 4 + j]));
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkStepFloatFloat3Float3" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
+ private void checkStepFloatFloat4Float4() {
+ Allocation inEdge = createRandomAllocation(mRS, Element.DataType.FLOAT_32, 1, 0xc5ba964ebd0c7f46l, false);
+ Allocation inV = createRandomAllocation(mRS, Element.DataType.FLOAT_32, 4, 0xddb830a31193981l, false);
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_32, 4), INPUTSIZE);
+ script.set_gAllocInV(inV);
+ script.forEach_testStepFloatFloat4Float4(inEdge, out);
+ verifyResultsStepFloatFloat4Float4(inEdge, inV, out, false);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testStepFloatFloat4Float4: " + e.toString());
+ }
+ try {
+ Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_32, 4), INPUTSIZE);
+ scriptRelaxed.set_gAllocInV(inV);
+ scriptRelaxed.forEach_testStepFloatFloat4Float4(inEdge, out);
+ verifyResultsStepFloatFloat4Float4(inEdge, inV, out, true);
+ } catch (Exception e) {
+ throw new RSRuntimeException("RenderScript. Can't invoke forEach_testStepFloatFloat4Float4: " + e.toString());
+ }
+ }
+
+ private void verifyResultsStepFloatFloat4Float4(Allocation inEdge, Allocation inV, Allocation out, boolean relaxed) {
+ float[] arrayInEdge = new float[INPUTSIZE * 1];
+ inEdge.copyTo(arrayInEdge);
+ float[] arrayInV = new float[INPUTSIZE * 4];
+ inV.copyTo(arrayInV);
+ float[] arrayOut = new float[INPUTSIZE * 4];
+ out.copyTo(arrayOut);
+ for (int i = 0; i < INPUTSIZE; i++) {
+ for (int j = 0; j < 4 ; j++) {
+ // Extract the inputs.
+ ArgumentsFloatFloatFloat args = new ArgumentsFloatFloatFloat();
+ args.inEdge = arrayInEdge[i];
+ args.inV = arrayInV[i * 4 + j];
+ // Figure out what the outputs should have been.
+ Floaty.setRelaxed(relaxed);
+ CoreMathVerifier.computeStep(args);
+ // Figure out what the outputs should have been.
+ boolean valid = true;
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ valid = false;
+ }
+ if (!valid) {
+ StringBuilder message = new StringBuilder();
+ message.append("Input inEdge: ");
+ message.append(String.format("%14.8g %8x %15a",
+ args.inEdge, Float.floatToRawIntBits(args.inEdge), args.inEdge));
+ message.append("\n");
+ message.append("Input inV: ");
+ message.append(String.format("%14.8g %8x %15a",
+ args.inV, Float.floatToRawIntBits(args.inV), args.inV));
+ message.append("\n");
+ message.append("Expected output out: ");
+ message.append(args.out.toString());
+ message.append("\n");
+ message.append("Actual output out: ");
+ message.append(String.format("%14.8g %8x %15a",
+ arrayOut[i * 4 + j], Float.floatToRawIntBits(arrayOut[i * 4 + j]), arrayOut[i * 4 + j]));
+ if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+ message.append(" FAIL");
+ }
+ message.append("\n");
+ assertTrue("Incorrect output for checkStepFloatFloat4Float4" +
+ (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+ }
+ }
+ }
+ }
+
public void testStep() {
checkStepFloatFloatFloat();
checkStepFloat2Float2Float2();
@@ -531,5 +738,8 @@
checkStepFloat2FloatFloat2();
checkStepFloat3FloatFloat3();
checkStepFloat4FloatFloat4();
+ checkStepFloatFloat2Float2();
+ checkStepFloatFloat3Float3();
+ checkStepFloatFloat4Float4();
}
}
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/YuvTest.java b/tests/tests/renderscript/src/android/renderscript/cts/YuvTest.java
index c2c7275..21f4417 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/YuvTest.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/YuvTest.java
@@ -58,16 +58,10 @@
bv[i] = (byte)r.nextInt(256);
}
- Type.Builder tb = new Type.Builder(mRS, Element.U8(mRS));
- tb.setX(w);
- tb.setY(h);
- ay = Allocation.createTyped(mRS, tb.create());
-
- tb = new Type.Builder(mRS, Element.U8(mRS));
- tb.setX(w >> 1);
- tb.setY(h >> 1);
- au = Allocation.createTyped(mRS, tb.create());
- av = Allocation.createTyped(mRS, tb.create());
+ ay = Allocation.createTyped(mRS, Type.createXY(mRS, Element.U8(mRS), w, h));
+ final Type tuv = Type.createXY(mRS, Element.U8(mRS), w >> 1, h >> 1);
+ au = Allocation.createTyped(mRS, tuv);
+ av = Allocation.createTyped(mRS, tuv);
ay.copyFrom(by);
au.copyFrom(bu);
@@ -75,11 +69,7 @@
}
public Allocation makeOutput() {
- Type.Builder tb = new Type.Builder(mRS, Element.RGBA_8888(mRS));
- tb.setX(width);
- tb.setY(height);
- Type t = tb.create();
- return Allocation.createTyped(mRS, t);
+ return Allocation.createTyped(mRS, Type.createXY(mRS, Element.RGBA_8888(mRS), width, height));
}
// Test for the API 17 conversion path
diff --git a/tests/tests/rscpp/Android.mk b/tests/tests/rscpp/Android.mk
index 6f01cab..1f32dd5 100644
--- a/tests/tests/rscpp/Android.mk
+++ b/tests/tests/rscpp/Android.mk
@@ -25,9 +25,6 @@
# When built, explicitly put it in the data partition.
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-# All tests should include android.test.runner.
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
LOCAL_JNI_SHARED_LIBRARIES := librscpptest_jni
diff --git a/tests/tests/rscpp/AndroidManifest.xml b/tests/tests/rscpp/AndroidManifest.xml
index b3ab43a..c014382 100644
--- a/tests/tests/rscpp/AndroidManifest.xml
+++ b/tests/tests/rscpp/AndroidManifest.xml
@@ -23,9 +23,12 @@
</application>
<!-- This is a self-instrumenting test package. -->
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.stub"
- android:label="CTS tests of RenderScript C++ component"/>
+ android:label="CTS tests of RenderScript C++ component">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/tests/rsg/Android.mk b/tests/tests/rsg/Android.mk
index 9ff554c..c58a4b0 100644
--- a/tests/tests/rsg/Android.mk
+++ b/tests/tests/rsg/Android.mk
@@ -25,9 +25,6 @@
# When built, explicitly put it in the data partition.
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-# All tests should include android.test.runner.
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/rsg/AndroidManifest.xml b/tests/tests/rsg/AndroidManifest.xml
index 886a395..031cbc2 100644
--- a/tests/tests/rsg/AndroidManifest.xml
+++ b/tests/tests/rsg/AndroidManifest.xml
@@ -27,9 +27,12 @@
</application>
<!-- This is a self-instrumenting test package. -->
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.stub"
- android:label="CTS tests of Renderscript Graphics component"/>
+ android:label="CTS tests of Renderscript Graphics component">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/tests/sax/Android.mk b/tests/tests/sax/Android.mk
index 5270ae5..2ed7644 100644
--- a/tests/tests/sax/Android.mk
+++ b/tests/tests/sax/Android.mk
@@ -21,8 +21,6 @@
# and when built explicitly put it in the data partition
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
LOCAL_SRC_FILES := $(call all-java-files-under, src)
@@ -31,4 +29,6 @@
LOCAL_INSTRUMENTATION_FOR := CtsTestStubs
+LOCAL_SDK_VERSION := current
+
include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/sax/AndroidManifest.xml b/tests/tests/sax/AndroidManifest.xml
index 4fbf840..d1a6f91 100644
--- a/tests/tests/sax/AndroidManifest.xml
+++ b/tests/tests/sax/AndroidManifest.xml
@@ -22,8 +22,11 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.stub"
- android:label="CTS tests of android.sax"/>
+ android:label="CTS tests of android.sax">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/tests/security/Android.mk b/tests/tests/security/Android.mk
index f1a6bfb..d0fefa1 100644
--- a/tests/tests/security/Android.mk
+++ b/tests/tests/security/Android.mk
@@ -18,8 +18,6 @@
LOCAL_MODULE_TAGS := tests
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner guava
LOCAL_JNI_SHARED_LIBRARIES := libctssecurity_jni
@@ -32,8 +30,6 @@
LOCAL_SDK_VERSION := current
-LOCAL_ASSET_DIR := $(LOCAL_PATH)/assets
-
include $(BUILD_CTS_PACKAGE)
include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/security/AndroidManifest.xml b/tests/tests/security/AndroidManifest.xml
index 101c01c..da95e5c 100644
--- a/tests/tests/security/AndroidManifest.xml
+++ b/tests/tests/security/AndroidManifest.xml
@@ -31,9 +31,12 @@
android:exported="true"/>
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.stub"
- android:label="CTS tests of com.android.cts.stub"/>
+ android:label="CTS tests of com.android.cts.security">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/tests/security/jni/android_security_cts_NativeCodeTest.cpp b/tests/tests/security/jni/android_security_cts_NativeCodeTest.cpp
index 6bbc426..2f3fb79 100644
--- a/tests/tests/security/jni/android_security_cts_NativeCodeTest.cpp
+++ b/tests/tests/security/jni/android_security_cts_NativeCodeTest.cpp
@@ -15,27 +15,28 @@
*/
#include <jni.h>
-#include <netlink.h>
-#include <sock_diag.h>
+#include <linux/netlink.h>
+#include <linux/sock_diag.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/syscall.h>
+#include <unistd.h>
#include <sys/prctl.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <signal.h>
-#include <unistd.h>
-#include <errno.h>
-
-#define PASSED 0
-#define UNKNOWN_ERROR -1
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cutils/log.h>
#include <linux/perf_event.h>
+#include <errno.h>
+#include <inttypes.h>
+
+#define PASSED 0
+#define UNKNOWN_ERROR -1
/*
* Returns true iff this device is vulnerable to CVE-2013-2094.
@@ -85,142 +86,6 @@
return true;
}
-#define SEARCH_SIZE 0x4000
-
-static int secret;
-
-static bool isValidChildAddress(pid_t child, uintptr_t addr) {
- long word;
- long ret = syscall(__NR_ptrace, PTRACE_PEEKDATA, child, addr, &word);
- return (ret == 0);
-}
-
-/* A lazy, do nothing child. GET A JOB. */
-static void child() {
- int res;
- ALOGE("in child");
- secret = 0xbaadadd4;
- res = prctl(PR_SET_DUMPABLE, 1, 0, 0, 0);
- if (res != 0) {
- ALOGE("prctl failed");
- }
- res = ptrace(PTRACE_TRACEME, 0, 0, 0);
- if (res != 0) {
- ALOGE("child ptrace failed");
- }
- signal(SIGSTOP, SIG_IGN);
- kill(getpid(), SIGSTOP);
-}
-
-static jboolean parent(pid_t child) {
- int status;
- // Wait for the child to suspend itself so we can trace it.
- waitpid(child, &status, 0);
- jboolean result = true;
-
- uintptr_t addr;
- for (addr = 0x00000000; addr < 0xFFFF1000; addr+=SEARCH_SIZE) {
- if (isValidChildAddress(child, addr)) {
- // Don't scribble on our memory.
- // (which has the same mapping as our child)
- // We don't want to corrupt ourself.
- continue;
- }
-
- errno = 0;
- syscall(__NR_ptrace, PTRACE_PEEKDATA, child, &secret, addr);
- if (errno == 0) {
- result = false;
- // We found an address which isn't in our our, or our child's,
- // address space, but yet which is still writable. Scribble
- // all over it.
- ALOGE("parent: found writable at %x", addr);
- uintptr_t addr2;
- for (addr2 = addr; addr2 < addr + SEARCH_SIZE; addr2++) {
- syscall(__NR_ptrace, PTRACE_PEEKDATA, child, &secret, addr2);
- }
- }
- }
-
- ptrace(PTRACE_DETACH, child, 0, 0);
- return result;
-}
-
-/*
- * Prior to https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/arch/arm/include/asm/uaccess.h?id=8404663f81d212918ff85f493649a7991209fa04
- * there was a flaw in the kernel's handling of get_user and put_user
- * requests. Normally, get_user and put_user are supposed to guarantee
- * that reads/writes outside the process's address space are not
- * allowed.
- *
- * In this test, we use prctl(PTRACE_PEEKDATA) to force a write to
- * an address outside of our address space. Without the patch applied,
- * this write succeeds, because prctl(PTRACE_PEEKDATA) uses the
- * vulnerable put_user call.
- */
-static jboolean android_security_cts_NativeCodeTest_doVrootTest(JNIEnv*, jobject)
-{
- ALOGE("Starting doVrootTest");
- pid_t pid = fork();
- if (pid == -1) {
- return false;
- }
-
- if (pid == 0) {
- child();
- exit(0);
- }
-
- return parent(pid);
-}
-
-static void* mmap_syscall(void* addr, size_t len, int prot, int flags, int fd, off_t offset)
-{
- return (void*) syscall(__NR_mmap2, addr, len, prot, flags, fd, offset);
-}
-
-#define KBASE_REG_COOKIE_TB 2
-#define KBASE_REG_COOKIE_MTP 3
-
-/*
- * Returns true if the device is immune to CVE-2014-1710,
- * false if the device is vulnerable.
- */
-static jboolean android_security_cts_NativeCodeTest_doCVE20141710Test(JNIEnv*, jobject)
-{
- jboolean result = false;
- int fd = open("/dev/mali0", O_RDWR);
- if (fd < 0) {
- return true; /* not vulnerable */
- }
-
- void* a = mmap_syscall(NULL, 0x1000, PROT_READ, MAP_SHARED, fd, KBASE_REG_COOKIE_MTP);
- void* b = mmap_syscall(NULL, 0x1000, PROT_READ, MAP_SHARED, fd, KBASE_REG_COOKIE_TB);
-
- if (a == MAP_FAILED) {
- result = true; /* assume not vulnerable */
- goto done;
- }
-
- if (b == MAP_FAILED) {
- result = true; /* assume not vulnerable */
- goto done;
- }
-
- /* mprotect should return an error if not vulnerable */
- result = (mprotect(b, 0x1000, PROT_READ | PROT_WRITE) == -1);
-
- done:
- if (a != MAP_FAILED) {
- munmap(a, 0x1000);
- }
- if (b != MAP_FAILED) {
- munmap(b, 0x1000);
- }
- close(fd);
- return result;
-}
-
/*
* Will hang if vulnerable, return 0 if successful, -1 on unforseen
* error.
@@ -288,6 +153,147 @@
return UNKNOWN_ERROR;
}
+#define SEARCH_SIZE 0x4000
+
+static int secret;
+
+static bool isValidChildAddress(pid_t child, uintptr_t addr) {
+ long word;
+ long ret = syscall(__NR_ptrace, PTRACE_PEEKDATA, child, addr, &word);
+ return (ret == 0);
+}
+
+/* A lazy, do nothing child. GET A JOB. */
+static void child() {
+ int res;
+ ALOGE("in child");
+ secret = 0xbaadadd4;
+ res = prctl(PR_SET_DUMPABLE, 1, 0, 0, 0);
+ if (res != 0) {
+ ALOGE("prctl failed");
+ }
+ res = ptrace(PTRACE_TRACEME, 0, 0, 0);
+ if (res != 0) {
+ ALOGE("child ptrace failed");
+ }
+ signal(SIGSTOP, SIG_IGN);
+ kill(getpid(), SIGSTOP);
+}
+
+static jboolean parent(pid_t child) {
+ int status;
+ // Wait for the child to suspend itself so we can trace it.
+ waitpid(child, &status, 0);
+ jboolean result = true;
+
+ uintptr_t addr;
+ for (addr = 0x00000000; addr < 0xFFFF1000; addr+=SEARCH_SIZE) {
+ if (isValidChildAddress(child, addr)) {
+ // Don't scribble on our memory.
+ // (which has the same mapping as our child)
+ // We don't want to corrupt ourself.
+ continue;
+ }
+
+ errno = 0;
+ syscall(__NR_ptrace, PTRACE_PEEKDATA, child, &secret, addr);
+ if (errno == 0) {
+ result = false;
+ // We found an address which isn't in our our, or our child's,
+ // address space, but yet which is still writable. Scribble
+ // all over it.
+ ALOGE("parent: found writable at %" PRIxPTR, addr);
+ uintptr_t addr2;
+ for (addr2 = addr; addr2 < addr + SEARCH_SIZE; addr2++) {
+ syscall(__NR_ptrace, PTRACE_PEEKDATA, child, &secret, addr2);
+ }
+ }
+ }
+
+ ptrace(PTRACE_DETACH, child, 0, 0);
+ return result;
+}
+
+/*
+ * Prior to https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/arch/arm/include/asm/uaccess.h?id=8404663f81d212918ff85f493649a7991209fa04
+ * there was a flaw in the kernel's handling of get_user and put_user
+ * requests. Normally, get_user and put_user are supposed to guarantee
+ * that reads/writes outside the process's address space are not
+ * allowed.
+ *
+ * In this test, we use prctl(PTRACE_PEEKDATA) to force a write to
+ * an address outside of our address space. Without the patch applied,
+ * this write succeeds, because prctl(PTRACE_PEEKDATA) uses the
+ * vulnerable put_user call.
+ */
+static jboolean android_security_cts_NativeCodeTest_doVrootTest(JNIEnv*, jobject)
+{
+ ALOGE("Starting doVrootTest");
+ pid_t pid = fork();
+ if (pid == -1) {
+ return false;
+ }
+
+ if (pid == 0) {
+ child();
+ exit(0);
+ }
+
+ return parent(pid);
+}
+
+static void* mmap_syscall(void* addr, size_t len, int prot, int flags, int fd, off_t offset)
+{
+#ifdef __LP64__
+ return mmap(addr, len, prot, flags, fd, offset);
+#else
+ return (void*) syscall(__NR_mmap2, addr, len, prot, flags, fd, offset);
+#endif
+}
+
+#define KBASE_REG_COOKIE_TB 2
+#define KBASE_REG_COOKIE_MTP 3
+
+/*
+ * Returns true if the device is immune to CVE-2014-1710,
+ * false if the device is vulnerable.
+ */
+static jboolean android_security_cts_NativeCodeTest_doCVE20141710Test(JNIEnv*, jobject)
+{
+ jboolean result = false;
+ int fd = open("/dev/mali0", O_RDWR);
+ if (fd < 0) {
+ return true; /* not vulnerable */
+ }
+
+ void* a = mmap_syscall(NULL, 0x1000, PROT_READ, MAP_SHARED, fd, KBASE_REG_COOKIE_MTP);
+ void* b = mmap_syscall(NULL, 0x1000, PROT_READ, MAP_SHARED, fd, KBASE_REG_COOKIE_TB);
+
+ if (a == MAP_FAILED) {
+ result = true; /* assume not vulnerable */
+ goto done;
+ }
+
+ if (b == MAP_FAILED) {
+ result = true; /* assume not vulnerable */
+ goto done;
+ }
+
+ /* mprotect should return an error if not vulnerable */
+ result = (mprotect(b, 0x1000, PROT_READ | PROT_WRITE) == -1);
+
+ done:
+ if (a != MAP_FAILED) {
+ munmap(a, 0x1000);
+ }
+ if (b != MAP_FAILED) {
+ munmap(b, 0x1000);
+ }
+ close(fd);
+ return result;
+}
+
+
static JNINativeMethod gMethods[] = {
{ "doPerfEventTest", "()Z",
(void *) android_security_cts_NativeCodeTest_doPerfEventTest },
diff --git a/tests/tests/security/jni/netlink.h b/tests/tests/security/jni/netlink.h
deleted file mode 100644
index b5567b0..0000000
--- a/tests/tests/security/jni/netlink.h
+++ /dev/null
@@ -1,170 +0,0 @@
-/****************************************************************************
- ****************************************************************************
- ***
- *** This header was automatically generated from a Linux kernel header
- *** of the same name, to make information necessary for userspace to
- *** call into the kernel available to libc. It contains only constants,
- *** structures, and macros generated from the original header, and thus,
- *** contains no copyrightable information.
- ***
- *** To edit the content of this header, modify the corresponding
- *** source file (e.g. under external/kernel-headers/original/) then
- *** run bionic/libc/kernel/tools/update_all.py
- ***
- *** Any manual change here will be lost the next time this script will
- *** be run. You've been warned!
- ***
- ****************************************************************************
- ****************************************************************************/
-#ifndef _UAPI__LINUX_NETLINK_H
-#define _UAPI__LINUX_NETLINK_H
-#include <linux/kernel.h>
-#include <linux/socket.h>
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-#include <linux/types.h>
-#define NETLINK_ROUTE 0
-#define NETLINK_UNUSED 1
-#define NETLINK_USERSOCK 2
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-#define NETLINK_FIREWALL 3
-#define NETLINK_SOCK_DIAG 4
-#define NETLINK_NFLOG 5
-#define NETLINK_XFRM 6
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-#define NETLINK_SELINUX 7
-#define NETLINK_ISCSI 8
-#define NETLINK_AUDIT 9
-#define NETLINK_FIB_LOOKUP 10
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-#define NETLINK_CONNECTOR 11
-#define NETLINK_NETFILTER 12
-#define NETLINK_IP6_FW 13
-#define NETLINK_DNRTMSG 14
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-#define NETLINK_KOBJECT_UEVENT 15
-#define NETLINK_GENERIC 16
-#define NETLINK_SCSITRANSPORT 18
-#define NETLINK_ECRYPTFS 19
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-#define NETLINK_RDMA 20
-#define NETLINK_CRYPTO 21
-#define NETLINK_INET_DIAG NETLINK_SOCK_DIAG
-#define MAX_LINKS 32
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-struct sockaddr_nl {
- __kernel_sa_family_t nl_family;
- unsigned short nl_pad;
- __u32 nl_pid;
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
- __u32 nl_groups;
-};
-struct nlmsghdr {
- __u32 nlmsg_len;
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
- __u16 nlmsg_type;
- __u16 nlmsg_flags;
- __u32 nlmsg_seq;
- __u32 nlmsg_pid;
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-};
-#define NLM_F_REQUEST 1
-#define NLM_F_MULTI 2
-#define NLM_F_ACK 4
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-#define NLM_F_ECHO 8
-#define NLM_F_DUMP_INTR 16
-#define NLM_F_ROOT 0x100
-#define NLM_F_MATCH 0x200
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-#define NLM_F_ATOMIC 0x400
-#define NLM_F_DUMP (NLM_F_ROOT|NLM_F_MATCH)
-#define NLM_F_REPLACE 0x100
-#define NLM_F_EXCL 0x200
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-#define NLM_F_CREATE 0x400
-#define NLM_F_APPEND 0x800
-#define NLMSG_ALIGNTO 4U
-#define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-#define NLMSG_HDRLEN ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr)))
-#define NLMSG_LENGTH(len) ((len) + NLMSG_HDRLEN)
-#define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))
-#define NLMSG_DATA(nlh) ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-#define NLMSG_NEXT(nlh,len) ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), (struct nlmsghdr*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len)))
-#define NLMSG_OK(nlh,len) ((len) >= (int)sizeof(struct nlmsghdr) && (nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && (nlh)->nlmsg_len <= (len))
-#define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len)))
-#define NLMSG_NOOP 0x1
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-#define NLMSG_ERROR 0x2
-#define NLMSG_DONE 0x3
-#define NLMSG_OVERRUN 0x4
-#define NLMSG_MIN_TYPE 0x10
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-struct nlmsgerr {
- int error;
- struct nlmsghdr msg;
-};
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-#define NETLINK_ADD_MEMBERSHIP 1
-#define NETLINK_DROP_MEMBERSHIP 2
-#define NETLINK_PKTINFO 3
-#define NETLINK_BROADCAST_ERROR 4
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-#define NETLINK_NO_ENOBUFS 5
-#define NETLINK_RX_RING 6
-#define NETLINK_TX_RING 7
-struct nl_pktinfo {
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
- __u32 group;
-};
-struct nl_mmap_req {
- unsigned int nm_block_size;
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
- unsigned int nm_block_nr;
- unsigned int nm_frame_size;
- unsigned int nm_frame_nr;
-};
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-struct nl_mmap_hdr {
- unsigned int nm_status;
- unsigned int nm_len;
- __u32 nm_group;
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
- __u32 nm_pid;
- __u32 nm_uid;
- __u32 nm_gid;
-};
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-enum nl_mmap_status {
- NL_MMAP_STATUS_UNUSED,
- NL_MMAP_STATUS_RESERVED,
- NL_MMAP_STATUS_VALID,
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
- NL_MMAP_STATUS_COPY,
- NL_MMAP_STATUS_SKIP,
-};
-#define NL_MMAP_MSG_ALIGNMENT NLMSG_ALIGNTO
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-#define NL_MMAP_MSG_ALIGN(sz) __ALIGN_KERNEL(sz, NL_MMAP_MSG_ALIGNMENT)
-#define NL_MMAP_HDRLEN NL_MMAP_MSG_ALIGN(sizeof(struct nl_mmap_hdr))
-#define NET_MAJOR 36
-enum {
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
- NETLINK_UNCONNECTED = 0,
- NETLINK_CONNECTED,
-};
-struct nlattr {
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
- __u16 nla_len;
- __u16 nla_type;
-};
-#define NLA_F_NESTED (1 << 15)
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-#define NLA_F_NET_BYTEORDER (1 << 14)
-#define NLA_TYPE_MASK ~(NLA_F_NESTED | NLA_F_NET_BYTEORDER)
-#define NLA_ALIGNTO 4
-#define NLA_ALIGN(len) (((len) + NLA_ALIGNTO - 1) & ~(NLA_ALIGNTO - 1))
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-#define NLA_HDRLEN ((int) NLA_ALIGN(sizeof(struct nlattr)))
-#endif
diff --git a/tests/tests/security/jni/sock_diag.h b/tests/tests/security/jni/sock_diag.h
deleted file mode 100644
index 0dc2902..0000000
--- a/tests/tests/security/jni/sock_diag.h
+++ /dev/null
@@ -1,43 +0,0 @@
-/****************************************************************************
- ****************************************************************************
- ***
- *** This header was automatically generated from a Linux kernel header
- *** of the same name, to make information necessary for userspace to
- *** call into the kernel available to libc. It contains only constants,
- *** structures, and macros generated from the original header, and thus,
- *** contains no copyrightable information.
- ***
- *** To edit the content of this header, modify the corresponding
- *** source file (e.g. under external/kernel-headers/original/) then
- *** run bionic/libc/kernel/tools/update_all.py
- ***
- *** Any manual change here will be lost the next time this script will
- *** be run. You've been warned!
- ***
- ****************************************************************************
- ****************************************************************************/
-#ifndef _UAPI__SOCK_DIAG_H__
-#define _UAPI__SOCK_DIAG_H__
-#include <linux/types.h>
-#define SOCK_DIAG_BY_FAMILY 20
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-struct sock_diag_req {
- __u8 sdiag_family;
- __u8 sdiag_protocol;
-};
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-enum {
- SK_MEMINFO_RMEM_ALLOC,
- SK_MEMINFO_RCVBUF,
- SK_MEMINFO_WMEM_ALLOC,
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
- SK_MEMINFO_SNDBUF,
- SK_MEMINFO_FWD_ALLOC,
- SK_MEMINFO_WMEM_QUEUED,
- SK_MEMINFO_OPTMEM,
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
- SK_MEMINFO_BACKLOG,
- SK_MEMINFO_VARS,
-};
-#endif
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
diff --git a/tests/tests/security/src/android/security/cts/NativeCodeTest.java b/tests/tests/security/src/android/security/cts/NativeCodeTest.java
index 6634310..5e3ffa4 100644
--- a/tests/tests/security/src/android/security/cts/NativeCodeTest.java
+++ b/tests/tests/security/src/android/security/cts/NativeCodeTest.java
@@ -68,7 +68,13 @@
private static native boolean doPerfEventTest2();
/**
- * ANDROID-11234878
+ * Hangs if device is vulnerable to CVE-2013-1763, returns -1 if
+ * unexpected error occurs, 0 otherwise.
+ */
+ private static native int doSockDiagTest();
+
+ /**
+ * ANDROID-11234878 / CVE-2013-6282
*
* Returns true if the device is patched against the vroot
* vulnerability. Returns false if there was some problem running
@@ -90,10 +96,4 @@
* false if the device is vulnerable.
*/
private static native boolean doCVE20141710Test();
-
- /**
- * Hangs if device is vulnerable to CVE-2013-1763, returns -1 if
- * unexpected error occurs, 0 otherwise.
- */
- private static native int doSockDiagTest();
}
diff --git a/tests/tests/speech/Android.mk b/tests/tests/speech/Android.mk
index 60acf90..75f7e4c 100755
--- a/tests/tests/speech/Android.mk
+++ b/tests/tests/speech/Android.mk
@@ -21,8 +21,6 @@
# and when built explicitly put it in the data partition
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/speech/AndroidManifest.xml b/tests/tests/speech/AndroidManifest.xml
index 93576b1..788f7cc 100755
--- a/tests/tests/speech/AndroidManifest.xml
+++ b/tests/tests/speech/AndroidManifest.xml
@@ -25,9 +25,12 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.stub"
- android:label="CTS tests of android.speech"/>
+ android:label="CTS tests of android.speech">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/tests/telephony/Android.mk b/tests/tests/telephony/Android.mk
index e7a3336..a64fa11 100644
--- a/tests/tests/telephony/Android.mk
+++ b/tests/tests/telephony/Android.mk
@@ -22,7 +22,7 @@
# and when built explicitly put it in the data partition
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common mms-common
+LOCAL_JAVA_LIBRARIES := telephony-common mms-common
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
@@ -32,7 +32,8 @@
LOCAL_INSTRUMENTATION_FOR := CtsTestStubs
-# uncomment when dalvik.annotation.Test* are removed or part of SDK
-# #LOCAL_SDK_VERSION := current
+# uncomment when b/13250611 is fixed
+#LOCAL_SDK_VERSION := current
+LOCAL_JAVA_LIBRARIES += android.test.runner
include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/telephony/AndroidManifest.xml b/tests/tests/telephony/AndroidManifest.xml
index 1dfd68d..36d1e5e 100644
--- a/tests/tests/telephony/AndroidManifest.xml
+++ b/tests/tests/telephony/AndroidManifest.xml
@@ -22,9 +22,12 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.stub"
- android:label="CTS tests of android.telephony"/>
+ android:label="CTS tests of android.telephony">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/tests/telephony/src/android/telephony/cts/PhoneNumberUtilsTest.java b/tests/tests/telephony/src/android/telephony/cts/PhoneNumberUtilsTest.java
index 3fc5b28..d96743c 100644
--- a/tests/tests/telephony/src/android/telephony/cts/PhoneNumberUtilsTest.java
+++ b/tests/tests/telephony/src/android/telephony/cts/PhoneNumberUtilsTest.java
@@ -301,4 +301,29 @@
assertTrue(PhoneNumberUtils.isWellFormedSmsAddress("+17005554141"));
assertFalse(PhoneNumberUtils.isWellFormedSmsAddress("android"));
}
+
+ public void testIsUriNumber() {
+ assertTrue(PhoneNumberUtils.isUriNumber("foo@google.com"));
+ assertTrue(PhoneNumberUtils.isUriNumber("xyz@zzz.org"));
+ assertFalse(PhoneNumberUtils.isUriNumber("+15103331245"));
+ assertFalse(PhoneNumberUtils.isUriNumber("+659231235"));
+ }
+
+ public void testGetUsernameFromUriNumber() {
+ assertEquals("john", PhoneNumberUtils.getUsernameFromUriNumber("john@myorg.com"));
+ assertEquals("tim_123", PhoneNumberUtils.getUsernameFromUriNumber("tim_123@zzz.org"));
+ assertEquals("5103331245", PhoneNumberUtils.getUsernameFromUriNumber("5103331245"));
+ }
+
+ public void testConvertAndStrip() {
+ // Untouched number.
+ assertEquals("123456789", PhoneNumberUtils.convertAndStrip("123456789"));
+ // Dashes should be stripped, legal separators (i.e. wild character remain untouched)
+ assertEquals("+15103331245*123", PhoneNumberUtils.convertAndStrip("+1-510-333-1245*123"));
+ // Arabic digits should be converted
+ assertEquals("5567861616", PhoneNumberUtils.convertAndStrip("٥٥٦٧٨٦١٦١٦"));
+ // Arabic digits converted and spaces stripped
+ assertEquals("5567861616", PhoneNumberUtils.convertAndStrip("٥ ٥٦ ٧ ٨ ٦ ١ ٦ ١ ٦"));
+
+ }
}
diff --git a/tests/tests/text/AndroidManifest.xml b/tests/tests/text/AndroidManifest.xml
index 16ba2d9..63f6d59 100644
--- a/tests/tests/text/AndroidManifest.xml
+++ b/tests/tests/text/AndroidManifest.xml
@@ -23,9 +23,12 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.stub"
- android:label="CTS tests of android.text"/>
+ android:label="CTS tests of android.text">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/tests/textureview/Android.mk b/tests/tests/textureview/Android.mk
index 30cc4ff..f85a738 100644
--- a/tests/tests/textureview/Android.mk
+++ b/tests/tests/textureview/Android.mk
@@ -21,8 +21,6 @@
# and when built explicitly put it in the data partition
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/textureview/AndroidManifest.xml b/tests/tests/textureview/AndroidManifest.xml
index 63cd233..9ec3f17 100644
--- a/tests/tests/textureview/AndroidManifest.xml
+++ b/tests/tests/textureview/AndroidManifest.xml
@@ -25,7 +25,10 @@
<instrumentation
android:targetPackage="com.android.cts.textureview"
- android:name="android.test.InstrumentationCtsTestRunner" />
+ android:name="android.support.test.runner.AndroidJUnitRunner" >
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
<application
android:label="@string/app_name"
diff --git a/tests/tests/theme/Android.mk b/tests/tests/theme/Android.mk
index 5846426..134af7c 100644
--- a/tests/tests/theme/Android.mk
+++ b/tests/tests/theme/Android.mk
@@ -24,9 +24,6 @@
# When built, explicitly put it in the data partition.
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-# All tests should include android.test.runner.
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/theme/AndroidManifest.xml b/tests/tests/theme/AndroidManifest.xml
index 0edc836..8232d2b 100644
--- a/tests/tests/theme/AndroidManifest.xml
+++ b/tests/tests/theme/AndroidManifest.xml
@@ -19,12 +19,15 @@
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<application>
- <uses-library android:name="android.test.runner" />
- <activity android:name="android.theme.cts.DeviceDefaultActivity" />
+ <uses-library android:name="android.test.runner" />
+ <activity android:name="android.theme.cts.DeviceDefaultActivity" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.theme"
- android:label="CTS tests for themes"/>
+ android:label="CTS tests for themes">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/tests/tv/Android.mk b/tests/tests/tv/Android.mk
new file mode 100644
index 0000000..b45129d
--- /dev/null
+++ b/tests/tests/tv/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_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsTvTestCases
+
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/tv/AndroidManifest.xml b/tests/tests/tv/AndroidManifest.xml
new file mode 100644
index 0000000..553f93b
--- /dev/null
+++ b/tests/tests/tv/AndroidManifest.xml
@@ -0,0 +1,47 @@
+<?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="android.tv.cts">
+
+ <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+ <application>
+ <uses-library android:name="android.test.runner"/>
+ <service android:name="android.tv.cts.TvInputManagerTest$MockTvInputInternalService"
+ android:permission="android.permission.BIND_TV_INPUT">
+ <intent-filter>
+ <action android:name="android.tv.TvInputService" />
+ </intent-filter>
+ </service>
+ <service android:name="android.tv.cts.TvInputManagerTest$MockTvInputRemoteService"
+ android:permission="android.permission.BIND_TV_INPUT"
+ android:process=":remoteTvInputForTest"
+ android:isolatedProcess="true">
+ <intent-filter>
+ <action android:name="android.tv.TvInputService" />
+ </intent-filter>
+ </service>
+ </application>
+
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.tv.cts"
+ android:label="Tests for the TV APIs.">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
+</manifest>
diff --git a/tests/tests/tv/src/android/tv/cts/TvInputManagerTest.java b/tests/tests/tv/src/android/tv/cts/TvInputManagerTest.java
new file mode 100644
index 0000000..2b19963
--- /dev/null
+++ b/tests/tests/tv/src/android/tv/cts/TvInputManagerTest.java
@@ -0,0 +1,334 @@
+/*
+ * 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 android.tv.cts;
+
+import android.content.ComponentName;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.provider.TvContract;
+import android.test.AndroidTestCase;
+import android.text.TextUtils;
+import android.tv.TvInputInfo;
+import android.tv.TvInputManager;
+import android.tv.TvInputManager.Session;
+import android.tv.TvInputManager.SessionCallback;
+import android.tv.TvInputManager.TvInputListener;
+import android.tv.TvInputService;
+import android.tv.TvInputService.TvInputSessionImpl;
+import android.util.Log;
+import android.view.Surface;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test for {@link android.tv.TvInputManager}.
+ */
+public class TvInputManagerTest extends AndroidTestCase {
+ private static final String TAG = "TvInputManagerTest";
+ private static final long OPERATION_TIMEOUT_MS = 1500;
+
+ private TvInputManager mManager;
+ private Session mSession;
+ private SessionCallback mSessionCallback;
+ private boolean mAvailability;
+ private TvInputListener mTvInputListener;
+ private HandlerThread mCallbackThread;
+ private Handler mCallbackHandler;
+
+ private CountDownLatch mAvailabilityChangeLatch;
+ private CountDownLatch mSessionCreationLatch;
+ private CountDownLatch mSessionReleaseLatch;
+
+ public TvInputManagerTest() {
+ mSessionCallback = new MockSessionCallback();
+ }
+
+ @Override
+ public void setContext(Context context) {
+ super.setContext(context);
+ if (TextUtils.isEmpty(MockTvInputInternalService.sInputId)) {
+ ComponentName componentName = new ComponentName(
+ context.getPackageName(), MockTvInputInternalService.class.getName());
+ // TODO: Do not directly generate an input id.
+ MockTvInputInternalService.sInputId = componentName.flattenToShortString();
+ }
+ if (TextUtils.isEmpty(MockTvInputRemoteService.sInputId)) {
+ ComponentName componentName = new ComponentName(
+ context.getPackageName(), MockTvInputRemoteService.class.getName());
+ // TODO: Do not directly generate an input id.
+ MockTvInputRemoteService.sInputId = componentName.flattenToShortString();
+ }
+ mManager = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE);
+ }
+
+ @Override
+ protected void setUp() {
+ mAvailability = false;
+ mSession = null;
+ MockTvInputInternalService.sInstance = null;
+ MockTvInputInternalService.sSession = null;
+ MockTvInputInternalService.sFailOnCreateSession = false;
+ mCallbackThread = new HandlerThread("CallbackThread");
+ mCallbackThread.start();
+ mCallbackHandler = new Handler(mCallbackThread.getLooper());
+ }
+
+ @Override
+ protected void tearDown() throws InterruptedException {
+ if (mTvInputListener != null) {
+ mManager.unregisterListener(MockTvInputInternalService.sInputId, mTvInputListener);
+ mManager.unregisterListener(MockTvInputRemoteService.sInputId, mTvInputListener);
+ mTvInputListener = null;
+ }
+ mCallbackThread.quit();
+ mCallbackThread.join();
+ }
+
+ public void testGetTvInputList() throws Exception {
+ // Check if the returned list includes the mock tv input services.
+ int mockServiceInstalled = 0;
+ for (TvInputInfo info : mManager.getTvInputList()) {
+ if (MockTvInputInternalService.sInputId.equals(info.getId())) {
+ ++mockServiceInstalled;
+ }
+ if (MockTvInputRemoteService.sInputId.equals(info.getId())) {
+ ++mockServiceInstalled;
+ }
+ }
+
+ // Verify the result.
+ assertEquals("Mock services must be listed", 2, mockServiceInstalled);
+ }
+
+ public void testCreateSession() throws Exception {
+ mSessionCreationLatch = new CountDownLatch(1);
+ // Make the mock service return a session on request.
+ mManager.createSession(MockTvInputInternalService.sInputId, mSessionCallback,
+ mCallbackHandler);
+
+ // Verify the result.
+ assertTrue(mSessionCreationLatch.await(OPERATION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ assertNotNull(mSession);
+
+ mSession.release();
+ }
+
+ public void testCreateSessionFailure() throws Exception {
+ mSessionCreationLatch = new CountDownLatch(1);
+ // Make the mock service return {@code null} on request.
+ MockTvInputInternalService.sFailOnCreateSession = true;
+ mManager.createSession(MockTvInputInternalService.sInputId, mSessionCallback,
+ mCallbackHandler);
+
+ // Verify the result.
+ assertTrue(mSessionCreationLatch.await(OPERATION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ assertNull(mSession);
+ }
+
+ public void testAvailabilityChanged() throws Exception {
+ // Register a listener for availability change.
+ MockTvInputInternalService.sInstanceLatch = new CountDownLatch(1);
+ mTvInputListener = new MockTvInputListener();
+ mManager.registerListener(MockTvInputInternalService.sInputId, mTvInputListener,
+ mCallbackHandler);
+
+ // Make sure that the mock service is created.
+ if (MockTvInputInternalService.sInstance == null) {
+ assertTrue(MockTvInputInternalService.sInstanceLatch.await(
+ OPERATION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ }
+
+ // Change the availability of the mock service.
+ mAvailability = mManager.getAvailability(MockTvInputInternalService.sInputId);
+ boolean newAvailiability = !mAvailability;
+ mAvailabilityChangeLatch = new CountDownLatch(1);
+ MockTvInputInternalService.sInstance.setAvailable(newAvailiability);
+
+ // Verify the result.
+ assertTrue(mAvailabilityChangeLatch.await(OPERATION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ assertEquals(newAvailiability, mAvailability);
+ }
+
+ public void testCrashOnCreateSession() throws Exception {
+ mSessionCreationLatch = new CountDownLatch(
+ MockTvInputRemoteService.MAX_SESSION_CREATION_BEFORE_CRASH + 1);
+ mSessionReleaseLatch = new CountDownLatch(
+ MockTvInputRemoteService.MAX_SESSION_CREATION_BEFORE_CRASH);
+ // availability should be changed three times:
+ // 1) false -> true, when connected, 2) true -> false after crash, and
+ // 3) false -> true, after reconnected.
+ mAvailabilityChangeLatch = new CountDownLatch(3);
+ mTvInputListener = new MockTvInputListener();
+ mManager.registerListener(MockTvInputRemoteService.sInputId, mTvInputListener,
+ mCallbackHandler);
+
+ for (int i = 0; i < MockTvInputRemoteService.MAX_SESSION_CREATION_BEFORE_CRASH + 1; ++i) {
+ mManager.createSession(MockTvInputRemoteService.sInputId, mSessionCallback,
+ mCallbackHandler);
+ }
+
+ // Verify the result.
+ assertTrue(mSessionReleaseLatch.await(OPERATION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ assertTrue(mSessionCreationLatch.await(OPERATION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ assertTrue(mAvailabilityChangeLatch.await(OPERATION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ }
+
+ public void testCrashOnTune() throws Exception {
+ mSessionCreationLatch = new CountDownLatch(1);
+ mSessionReleaseLatch = new CountDownLatch(1);
+ // availability should be changed three times:
+ // 1) false -> true, when connected, 2) true -> false after crash, and
+ // 3) false -> true, after reconnected.
+ mAvailabilityChangeLatch = new CountDownLatch(3);
+
+ mTvInputListener = new MockTvInputListener();
+ mManager.registerListener(MockTvInputRemoteService.sInputId, mTvInputListener,
+ mCallbackHandler);
+ mManager.createSession(MockTvInputRemoteService.sInputId, mSessionCallback,
+ mCallbackHandler);
+
+ assertTrue(mSessionCreationLatch.await(OPERATION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+ Uri channelUri = ContentUris.withAppendedId(TvContract.Channels.CONTENT_URI, 0);
+ mSession.tune(channelUri);
+ assertTrue(mSessionReleaseLatch.await(OPERATION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ assertNull(mSession);
+ assertTrue(mAvailabilityChangeLatch.await(OPERATION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ }
+
+ private class MockTvInputListener extends TvInputListener {
+ @Override
+ public void onAvailabilityChanged(String inputId, boolean isAvailable) {
+ mAvailability = isAvailable;
+ if (mAvailabilityChangeLatch != null) {
+ mAvailabilityChangeLatch.countDown();
+ }
+ }
+ }
+
+ private class MockSessionCallback extends SessionCallback {
+ @Override
+ public void onSessionCreated(Session session) {
+ mSession = session;
+ if (mSessionCreationLatch != null) {
+ mSessionCreationLatch.countDown();
+ }
+ }
+
+ @Override
+ public void onSessionReleased(Session session) {
+ mSession = null;
+ if (mSessionReleaseLatch != null) {
+ mSessionReleaseLatch.countDown();
+ }
+ }
+ }
+
+ public static class MockTvInputInternalService extends TvInputService {
+ static String sInputId;
+ static CountDownLatch sInstanceLatch;
+ static MockTvInputInternalService sInstance;
+ static TvInputSessionImpl sSession;
+
+ static boolean sFailOnCreateSession;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ sInstance = this;
+ sSession = new MockTvInputInternalSessionImpl();
+ if (sInstanceLatch != null) {
+ sInstanceLatch.countDown();
+ }
+ }
+
+ @Override
+ public TvInputSessionImpl onCreateSession() {
+ return sFailOnCreateSession ? null : sSession;
+ }
+
+ class MockTvInputInternalSessionImpl extends TvInputSessionImpl {
+ public MockTvInputInternalSessionImpl() { }
+
+ @Override
+ public void onRelease() { }
+
+ @Override
+ public boolean onSetSurface(Surface surface) {
+ return false;
+ }
+
+ @Override
+ public void onSetVolume(float volume) { }
+
+ @Override
+ public boolean onTune(Uri channelUri) {
+ return false;
+ }
+ }
+ }
+
+ public static class MockTvInputRemoteService extends TvInputService {
+ public static final int MAX_SESSION_CREATION_BEFORE_CRASH = 2;
+ static String sInputId;
+
+ private int mSessionCreationBeforeCrash;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mSessionCreationBeforeCrash = MAX_SESSION_CREATION_BEFORE_CRASH;
+ setAvailable(true);
+ }
+
+ @Override
+ public TvInputSessionImpl onCreateSession() {
+ if (mSessionCreationBeforeCrash > 0) {
+ --mSessionCreationBeforeCrash;
+ return new MockTvInputRemoteSessionImpl();
+ }
+ android.os.Process.killProcess(android.os.Process.myPid());
+ return null;
+ }
+
+ class MockTvInputRemoteSessionImpl extends TvInputSessionImpl {
+ public MockTvInputRemoteSessionImpl() { }
+
+ @Override
+ public void onRelease() { }
+
+ @Override
+ public boolean onSetSurface(Surface surface) {
+ return false;
+ }
+
+ @Override
+ public void onSetVolume(float volume) { }
+
+ @Override
+ public boolean onTune(Uri channelUri) {
+ android.os.Process.killProcess(android.os.Process.myPid());
+ return false;
+ }
+ }
+ }
+}
diff --git a/tests/tests/uiautomation/Android.mk b/tests/tests/uiautomation/Android.mk
new file mode 100644
index 0000000..bb0fc19
--- /dev/null
+++ b/tests/tests/uiautomation/Android.mk
@@ -0,0 +1,30 @@
+# 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_MODULE_TAGS := optional
+
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner ub-uiautomator
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsUiAutomationTestCases
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
+
diff --git a/tests/tests/uiautomation/AndroidManifest.xml b/tests/tests/uiautomation/AndroidManifest.xml
new file mode 100644
index 0000000..06b31c8
--- /dev/null
+++ b/tests/tests/uiautomation/AndroidManifest.xml
@@ -0,0 +1,44 @@
+<?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="android.app.cts.uiautomation">
+
+ <application android:theme="@android:style/Theme.Holo.NoActionBar" >
+
+ <uses-library android:name="android.test.runner"/>
+
+ <activity
+ android:name="android.app.uiautomation.cts.UiAutomationTestFirstActivity"
+ android:exported="true">
+ </activity>
+
+ <activity
+ android:name="android.app.uiautomation.cts.UiAutomationTestSecondActivity"
+ android:exported="true">
+ </activity>
+
+ </application>
+
+ <instrumentation android:name="android.support.test.uiautomator.UiAutomatorInstrumentationTestRunner"
+ android:targetPackage="android.app.cts.uiautomation">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
+
+</manifest>
diff --git a/tests/tests/uiautomation/res/layout/ui_automation_test.xml b/tests/tests/uiautomation/res/layout/ui_automation_test.xml
new file mode 100644
index 0000000..fb9621d
--- /dev/null
+++ b/tests/tests/uiautomation/res/layout/ui_automation_test.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+-->
+<ListView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/list_view"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+</ListView>
diff --git a/tests/tests/uiautomation/res/values/strings.xml b/tests/tests/uiautomation/res/values/strings.xml
new file mode 100644
index 0000000..7e4e4e4
--- /dev/null
+++ b/tests/tests/uiautomation/res/values/strings.xml
@@ -0,0 +1,55 @@
+<?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.
+-->
+
+<resources>
+
+ <string name="uiautomation_test_activity">Cheeses</string>
+
+ <string-array name="some_cheeses">
+ <item>Abbaye de Belloc</item>
+ <item>Abbaye de Belval</item>
+ <item>Abbaye de Citeaux</item>
+ <item>Abbaye du Mont des Cats</item>
+ <item>Abbot’s Gold</item>
+ <item>Acapella</item>
+ <item>Acorn</item>
+ <item>Adelost</item>
+ <item>Affidelice au Chablis</item>
+ <item>Afuega\'l Pitu</item>
+ <item>Aged Gouda</item>
+ <item>Airag</item>
+ <item>Airedale</item>
+ <item>Aisy Cendre</item>
+ <item>Allgauer Emmentaler</item>
+ <item>Babybel</item>
+ <item>Baby Swiss</item>
+ <item>Baguette Laonnaise</item>
+ <item>Bakers</item>
+ <item>Balaton</item>
+ <item>Bandal</item>
+ <item>Banon</item>
+ <item>Barry\'s Bay Cheddar</item>
+ <item>Basing</item>
+ <item>Basket Cheese</item>
+ <item>Bath Cheese</item>
+ <item>Bavarian Bergkase</item>
+ <item>Baylough</item>
+ <item>Beauvoorde</item>
+ <item>Beemster 2% Milk</item>
+ </string-array>
+
+</resources>
diff --git a/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTest.java b/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTest.java
new file mode 100644
index 0000000..6d80819
--- /dev/null
+++ b/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTest.java
@@ -0,0 +1,291 @@
+/*
+ * 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 android.app.uiautomation.cts;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.app.Activity;
+import android.app.UiAutomation;
+import android.content.Intent;
+import android.view.FrameStats;
+import android.view.WindowAnimationFrameStats;
+import android.view.WindowContentFrameStats;
+import android.view.accessibility.AccessibilityWindowInfo;
+import android.support.test.uiautomator.UiScrollable;
+import android.support.test.uiautomator.UiSelector;
+import android.support.test.uiautomator.UiAutomatorTestCase;
+
+import java.util.List;
+
+/**
+ * Tests for the UiAutomation APIs.
+ */
+public class UiAutomationTest extends UiAutomatorTestCase {
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ AccessibilityServiceInfo info = getInstrumentation().getUiAutomation().getServiceInfo();
+ info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
+ getInstrumentation().getUiAutomation().setServiceInfo(info);
+ }
+
+ public void testWindowContentFrameStats() throws Exception {
+ Activity activity = null;
+ try {
+ UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+
+ // Start an activity.
+ Intent intent = new Intent(getInstrumentation().getContext(),
+ UiAutomationTestFirstActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ activity = getInstrumentation().startActivitySync(intent);
+
+ // Wait for things to settle.
+ getUiDevice().waitForIdle();
+
+ // Find the application window.
+ final int windowId = findAppWindowId(uiAutomation.getWindows());
+ assertTrue(windowId >= 0);
+
+ // Clear stats to be with a clean slate.
+ assertTrue(uiAutomation.clearWindowContentFrameStats(windowId));
+
+ // Find the list to scroll around.
+ UiScrollable listView = new UiScrollable(new UiSelector().resourceId(
+ "android.app.cts.uiautomation:id/list_view"));
+
+ // Scoll a bit.
+ listView.scrollToEnd(Integer.MAX_VALUE);
+ listView.scrollToBeginning(Integer.MAX_VALUE);
+
+ // Get the frame stats.
+ WindowContentFrameStats stats = uiAutomation.getWindowContentFrameStats(windowId);
+
+ // Check the frame stats...
+
+ // We should have somethong.
+ assertNotNull(stats);
+
+ // The refresh presiod is always positive.
+ assertTrue(stats.getRefreshPeriodNano() > 0);
+
+ // There is some frame data.
+ final int frameCount = stats.getFrameCount();
+ assertTrue(frameCount > 0);
+
+ // The frames are ordered in ascending order.
+ assertWindowContentTimestampsInAscendingOrder(stats);
+
+ // The start and end times are based on first and last frame.
+ assertEquals(stats.getStartTimeNano(), stats.getFramePresentedTimeNano(0));
+ assertEquals(stats.getEndTimeNano(), stats.getFramePresentedTimeNano(frameCount - 1));
+ } finally {
+ // Clean up.
+ if (activity != null) {
+ activity.finish();
+ }
+ }
+ }
+
+ public void testWindowContentFrameStatsNoAnimation() throws Exception {
+ Activity activity = null;
+ try {
+ UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+
+ // Start an activity.
+ Intent intent = new Intent(getInstrumentation().getContext(),
+ UiAutomationTestFirstActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ activity = getInstrumentation().startActivitySync(intent);
+
+ // Wait for things to settle.
+ getUiDevice().waitForIdle();
+
+ // Find the application window.
+ final int windowId = findAppWindowId(uiAutomation.getWindows());
+ assertTrue(windowId >= 0);
+
+ // Clear stats to be with a clean slate.
+ assertTrue(uiAutomation.clearWindowContentFrameStats(windowId));
+
+ // Get the frame stats.
+ WindowContentFrameStats stats = uiAutomation.getWindowContentFrameStats(windowId);
+
+ // Check the frame stats...
+
+ // We should have somethong.
+ assertNotNull(stats);
+
+ // The refresh presiod is always positive.
+ assertTrue(stats.getRefreshPeriodNano() > 0);
+
+ // There is no data.
+ assertTrue(stats.getFrameCount() == 0);
+
+ // The start and end times are undefibed as we have no data.
+ assertEquals(stats.getStartTimeNano(), FrameStats.UNDEFINED_TIME_NANO);
+ assertEquals(stats.getEndTimeNano(), FrameStats.UNDEFINED_TIME_NANO);
+ } finally {
+ // Clean up.
+ if (activity != null) {
+ activity.finish();
+ }
+ }
+ }
+
+ public void testWindowAnimationFrameStats() throws Exception {
+ Activity firstActivity = null;
+ Activity secondActivity = null;
+ try {
+ UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+
+ // Start the frist activity.
+ Intent firstIntent = new Intent(getInstrumentation().getContext(),
+ UiAutomationTestFirstActivity.class);
+ firstIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ firstActivity = getInstrumentation().startActivitySync(firstIntent);
+
+ // Wait for things to settle.
+ getUiDevice().waitForIdle();
+
+ // Clear the window animation stats to be with a clean slate.
+ uiAutomation.clearWindowAnimationFrameStats();
+
+ // Start the second activity
+ Intent secondIntent = new Intent(getInstrumentation().getContext(),
+ UiAutomationTestSecondActivity.class);
+ secondIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ secondActivity = getInstrumentation().startActivitySync(secondIntent);
+
+ // Wait for things to settle.
+ getUiDevice().waitForIdle();
+
+ // Get the frame stats.
+ WindowAnimationFrameStats stats = uiAutomation.getWindowAnimationFrameStats();
+
+ // Check the frame stats...
+
+ // We should have somethong.
+ assertNotNull(stats);
+
+ // The refresh presiod is always positive.
+ assertTrue(stats.getRefreshPeriodNano() > 0);
+
+ // There is some frame data.
+ final int frameCount = stats.getFrameCount();
+ assertTrue(frameCount > 0);
+
+ // The frames are ordered in ascending order.
+ assertWindowAnimationTimestampsInAscendingOrder(stats);
+
+ // The start and end times are based on first and last frame.
+ assertEquals(stats.getStartTimeNano(), stats.getFramePresentedTimeNano(0));
+ assertEquals(stats.getEndTimeNano(), stats.getFramePresentedTimeNano(frameCount - 1));
+ } finally {
+ // Clean up.
+ if (firstActivity != null) {
+ firstActivity.finish();
+ }
+ if (secondActivity != null) {
+ secondActivity.finish();
+ }
+ }
+ }
+
+ public void testWindowAnimationFrameStatsNoAnimation() throws Exception {
+ UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+
+ // Wait for things to settle.
+ getUiDevice().waitForIdle();
+
+ // Clear the window animation stats to be with a clean slate.
+ uiAutomation.clearWindowAnimationFrameStats();
+
+ // Get the frame stats.
+ WindowAnimationFrameStats stats = uiAutomation.getWindowAnimationFrameStats();
+
+ // Check the frame stats...
+
+ // We should have somethong.
+ assertNotNull(stats);
+
+ // The refresh presiod is always positive.
+ assertTrue(stats.getRefreshPeriodNano() > 0);
+
+ // There is no data.
+ assertTrue(stats.getFrameCount() == 0);
+
+ // The start and end times are undefibed as we have no data.
+ assertEquals(stats.getStartTimeNano(), FrameStats.UNDEFINED_TIME_NANO);
+ assertEquals(stats.getEndTimeNano(), FrameStats.UNDEFINED_TIME_NANO);
+ }
+
+ private void assertWindowContentTimestampsInAscendingOrder(WindowContentFrameStats stats) {
+ long lastExpectedTimeNano = 0;
+ long lastPresentedTimeNano = 0;
+ long lastPreparedTimeNano = 0;
+
+ final int frameCount = stats.getFrameCount();
+ for (int i = 0; i < frameCount; i++) {
+ final long expectedTimeNano = stats.getFramePostedTimeNano(i);
+ assertTrue(expectedTimeNano > lastExpectedTimeNano);
+ lastExpectedTimeNano = expectedTimeNano;
+
+ final long presentedTimeNano = stats.getFramePresentedTimeNano(i);
+ if (lastPresentedTimeNano == FrameStats.UNDEFINED_TIME_NANO) {
+ assertTrue(presentedTimeNano == FrameStats.UNDEFINED_TIME_NANO);
+ } else if (presentedTimeNano != FrameStats.UNDEFINED_TIME_NANO) {
+ assertTrue(presentedTimeNano > lastPresentedTimeNano);
+ }
+ lastPresentedTimeNano = presentedTimeNano;
+
+ final long preparedTimeNano = stats.getFrameReadyTimeNano(i);
+ if (lastPreparedTimeNano == FrameStats.UNDEFINED_TIME_NANO) {
+ assertTrue(preparedTimeNano == FrameStats.UNDEFINED_TIME_NANO);
+ } else if (preparedTimeNano != FrameStats.UNDEFINED_TIME_NANO) {
+ assertTrue(preparedTimeNano > lastPreparedTimeNano);
+ }
+ lastPreparedTimeNano = preparedTimeNano;
+ }
+ }
+
+ private void assertWindowAnimationTimestampsInAscendingOrder(WindowAnimationFrameStats stats) {
+ long lastPresentedTimeNano = 0;
+
+ final int frameCount = stats.getFrameCount();
+ for (int i = 0; i < frameCount; i++) {
+ final long presentedTimeNano = stats.getFramePresentedTimeNano(i);
+ if (lastPresentedTimeNano == FrameStats.UNDEFINED_TIME_NANO) {
+ assertTrue(presentedTimeNano == FrameStats.UNDEFINED_TIME_NANO);
+ } else if (presentedTimeNano != FrameStats.UNDEFINED_TIME_NANO) {
+ assertTrue(presentedTimeNano > lastPresentedTimeNano);
+ }
+ lastPresentedTimeNano = presentedTimeNano;
+ }
+ }
+
+ private int findAppWindowId(List<AccessibilityWindowInfo> windows) {
+ final int windowCount = windows.size();
+ for (int i = 0; i < windowCount; i++) {
+ AccessibilityWindowInfo window = windows.get(i);
+ if (window.getType() == AccessibilityWindowInfo.TYPE_APPLICATION) {
+ return window.getId();
+ }
+ }
+ return -1;
+ }
+}
diff --git a/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTestFirstActivity.java b/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTestFirstActivity.java
new file mode 100644
index 0000000..49791ab
--- /dev/null
+++ b/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTestFirstActivity.java
@@ -0,0 +1,42 @@
+/*
+* 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 android.app.uiautomation.cts;
+
+import android.app.cts.uiautomation.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+
+/**
+* Activity for testing the UiAutomatoin APIs.
+*/
+public class UiAutomationTestFirstActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.ui_automation_test);
+
+ String[] cheeses = getResources().getStringArray(R.array.some_cheeses);
+ ArrayAdapter<String> cheeseAdapter = new ArrayAdapter<String>(this,
+ android.R.layout.simple_list_item_1, cheeses);
+
+ ListView listView = (ListView) findViewById(R.id.list_view);
+ listView.setAdapter(cheeseAdapter);
+ }
+}
diff --git a/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTestSecondActivity.java b/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTestSecondActivity.java
new file mode 100644
index 0000000..7def379
--- /dev/null
+++ b/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTestSecondActivity.java
@@ -0,0 +1,41 @@
+/*
+* 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 android.app.uiautomation.cts;
+
+import android.app.Activity;
+import android.app.cts.uiautomation.R;
+import android.os.Bundle;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+
+/**
+* Activity for testing the UiAutomatoin APIs.
+*/
+public class UiAutomationTestSecondActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.ui_automation_test);
+
+ String[] cheeses = getResources().getStringArray(R.array.some_cheeses);
+ ArrayAdapter<String> cheeseAdapter = new ArrayAdapter<String>(this,
+ android.R.layout.simple_list_item_1, cheeses);
+
+ ListView listView = (ListView) findViewById(R.id.list_view);
+ listView.setAdapter(cheeseAdapter);
+ }
+}
diff --git a/tests/tests/uidisolation/Android.mk b/tests/tests/uidisolation/Android.mk
index ba82eb5..8529407 100644
--- a/tests/tests/uidisolation/Android.mk
+++ b/tests/tests/uidisolation/Android.mk
@@ -21,12 +21,12 @@
# and when built explicitly put it in the data partition
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner ctstestserver
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := CtsUidIsolationTestCases
+LOCAL_SDK_VERSION := current
+
include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/uidisolation/AndroidManifest.xml b/tests/tests/uidisolation/AndroidManifest.xml
index e456a50..a8c6848 100644
--- a/tests/tests/uidisolation/AndroidManifest.xml
+++ b/tests/tests/uidisolation/AndroidManifest.xml
@@ -31,8 +31,11 @@
<uses-permission android:name="android.permission.INTERNET"/>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.uidisolation"
- android:label="CTS tests of android.uidisolation"/>
+ android:label="CTS tests of android.uidisolation">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/tests/util/Android.mk b/tests/tests/util/Android.mk
index f1c75dc..6ede3fb 100644
--- a/tests/tests/util/Android.mk
+++ b/tests/tests/util/Android.mk
@@ -21,8 +21,6 @@
# and when built explicitly put it in the data partition
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/util/AndroidManifest.xml b/tests/tests/util/AndroidManifest.xml
index 3969ac8..ab417bd 100644
--- a/tests/tests/util/AndroidManifest.xml
+++ b/tests/tests/util/AndroidManifest.xml
@@ -23,9 +23,12 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.stub"
- android:label="CTS tests of android.util"/>
+ android:label="CTS tests of android.util">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/tests/view/AndroidManifest.xml b/tests/tests/view/AndroidManifest.xml
index 233dc44..4cdeab2 100644
--- a/tests/tests/view/AndroidManifest.xml
+++ b/tests/tests/view/AndroidManifest.xml
@@ -23,9 +23,12 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.stub"
- android:label="CTS tests of android.view"/>
+ android:label="CTS tests of android.view">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/tests/view/src/android/view/cts/LayoutInflaterTest.java b/tests/tests/view/src/android/view/cts/LayoutInflaterTest.java
index b33a312..07c6e8c 100644
--- a/tests/tests/view/src/android/view/cts/LayoutInflaterTest.java
+++ b/tests/tests/view/src/android/view/cts/LayoutInflaterTest.java
@@ -19,7 +19,6 @@
import com.android.cts.stub.R;
import com.android.internal.util.XmlUtils;
-
import org.xmlpull.v1.XmlPullParser;
import android.app.cts.MockActivity;
@@ -28,9 +27,12 @@
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
+import android.content.res.Resources.Theme;
import android.content.res.XmlResourceParser;
import android.test.AndroidTestCase;
+import android.text.TextUtils;
import android.util.AttributeSet;
+import android.util.TypedValue;
import android.util.Xml;
import android.view.Gravity;
import android.view.InflateException;
@@ -42,20 +44,21 @@
import android.widget.LinearLayout;
public class LayoutInflaterTest extends AndroidTestCase {
-
private LayoutInflater mLayoutInflater;
- private Context mContext;
- private final Factory mFactory = new Factory() {
- public View onCreateView(String name, Context context,
- AttributeSet attrs) {
+ @SuppressWarnings("hiding")
+ private Context mContext;
+
+ private final Factory mFactory = new Factory() {
+ @Override
+ public View onCreateView(String name, Context context, AttributeSet attrs) {
return null;
}
};
private boolean isOnLoadClass;
private final Filter mFilter = new Filter() {
-
- @SuppressWarnings("unchecked")
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ @Override
public boolean onLoadClass(Class clazz) {
isOnLoadClass = true;
return true;
@@ -148,7 +151,8 @@
mLayoutInflater = LayoutInflater.from(mContext);
isOnLoadClass = false;
mLayoutInflater.setFilter(new Filter() {
- @SuppressWarnings("unchecked")
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ @Override
public boolean onLoadClass(Class clazz) {
isOnLoadClass = true;
return false;
@@ -307,60 +311,99 @@
}
public void testInflate4() {
- XmlResourceParser parser = getContext().getResources().getLayout(
- R.layout.inflater_layout);
- View view = mLayoutInflater.inflate(parser, null, false);
- assertNotNull(view);
- view = null;
- try {
- view = mLayoutInflater.inflate(null, null, false);
- fail("should throw exception");
- } catch (NullPointerException e) {
- }
- LinearLayout mLayout;
- mLayout = new LinearLayout(mContext);
- mLayout.setOrientation(LinearLayout.VERTICAL);
- mLayout.setHorizontalGravity(Gravity.LEFT);
- mLayout.setLayoutParams(new ViewGroup.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT));
- assertEquals(0, mLayout.getChildCount());
+ XmlResourceParser parser = getContext().getResources().getLayout(
+ R.layout.inflater_layout);
+ View view = mLayoutInflater.inflate(parser, null, false);
+ assertNotNull(view);
+ view = null;
+ try {
+ view = mLayoutInflater.inflate(null, null, false);
+ fail("should throw exception");
+ } catch (NullPointerException e) {
+ }
+ LinearLayout mLayout;
+ mLayout = new LinearLayout(mContext);
+ mLayout.setOrientation(LinearLayout.VERTICAL);
+ mLayout.setHorizontalGravity(Gravity.LEFT);
+ mLayout.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+ assertEquals(0, mLayout.getChildCount());
- try {
- view = mLayoutInflater.inflate(parser, mLayout, false);
- fail("should throw exception");
- } catch (NullPointerException e) {
- }
- parser = getContext().getResources().getLayout(
- R.layout.inflater_layout);
- view = mLayoutInflater.inflate(parser, mLayout, false);
- assertNull(view.getParent());
- assertNotNull(view);
- assertEquals(0, mLayout.getChildCount());
- parser = getContext().getResources().getLayout(
- R.layout.inflater_layout);
- assertEquals(0, mLayout.getChildCount());
- view = mLayoutInflater.inflate(parser, mLayout, true);
- assertNotNull(view);
- assertNull(view.getParent());
- assertEquals(1, mLayout.getChildCount());
+ try {
+ view = mLayoutInflater.inflate(parser, mLayout, false);
+ fail("should throw exception");
+ } catch (NullPointerException e) {
+ }
+ parser = getContext().getResources().getLayout(
+ R.layout.inflater_layout);
+ view = mLayoutInflater.inflate(parser, mLayout, false);
+ assertNull(view.getParent());
+ assertNotNull(view);
+ assertEquals(0, mLayout.getChildCount());
+ parser = getContext().getResources().getLayout(
+ R.layout.inflater_layout);
+ assertEquals(0, mLayout.getChildCount());
+ view = mLayoutInflater.inflate(parser, mLayout, true);
+ assertNotNull(view);
+ assertNull(view.getParent());
+ assertEquals(1, mLayout.getChildCount());
- parser = null;
- parser = getParser();
- try {
- view = mLayoutInflater.inflate(parser, mLayout, false);
- fail("should throw exception");
- } catch (InflateException e) {
- }
+ parser = null;
+ parser = getParser();
+ try {
+ view = mLayoutInflater.inflate(parser, mLayout, false);
+ fail("should throw exception");
+ } catch (InflateException e) {
+ }
- parser = null;
- view = null;
- parser = getParser();
+ parser = null;
+ view = null;
+ parser = getParser();
- view = mLayoutInflater.inflate(parser, mLayout, true);
- assertNotNull(view);
- assertEquals(2, mLayout.getChildCount());
- }
+ view = mLayoutInflater.inflate(parser, mLayout, true);
+ assertNotNull(view);
+ assertEquals(2, mLayout.getChildCount());
+ }
+
+ public void testOverrideTheme() {
+ View container = mLayoutInflater.inflate(R.layout.inflater_override_theme_layout, null);
+ verifyThemeType(container, "view_outer", R.id.view_outer, 1);
+ verifyThemeType(container, "view_inner", R.id.view_inner, 2);
+ verifyThemeType(container, "view_attr", R.id.view_attr, 3);
+ }
+
+ private void verifyThemeType(View container, String tag, int id, int type) {
+ TypedValue outValue = new TypedValue();
+ View view = container.findViewById(id);
+ assertNotNull("Found " + tag, view);
+ Theme theme = view.getContext().getTheme();
+ boolean resolved = theme.resolveAttribute(R.attr.themeType, outValue, true);
+ assertTrue("Resolved themeType for " + tag, resolved);
+ assertEquals(tag + " has themeType " + type, type, outValue.data);
+ }
+
+ public void testInflateTags() {
+ final View view = mLayoutInflater.inflate(
+ com.android.cts.stub.R.layout.inflater_layout_tags, null);
+ assertNotNull(view);
+
+ checkViewTag(view, R.id.viewlayout_root, R.id.tag_viewlayout_root, R.string.tag1);
+ checkViewTag(view, R.id.mock_view, R.id.tag_mock_view, R.string.tag2);
+ }
+
+ private void checkViewTag(View parent, int viewId, int tagId, int valueResId) {
+ final View target = parent.findViewById(viewId);
+ assertNotNull("Found target view for " + viewId, target);
+
+ final Object tag = target.getTag(tagId);
+ assertNotNull("Tag is set", tag);
+ assertTrue("Tag is a character sequence", tag instanceof CharSequence);
+
+ final Context targetContext = target.getContext();
+ final CharSequence expectedValue = targetContext.getString(valueResId);
+ assertEquals(tagId + " has tag " + expectedValue, expectedValue, tag);
+ }
static class MockLayoutInflater extends LayoutInflater {
diff --git a/tests/tests/view/src/android/view/cts/ViewTest.java b/tests/tests/view/src/android/view/cts/ViewTest.java
index 95a365f..5c9516e 100644
--- a/tests/tests/view/src/android/view/cts/ViewTest.java
+++ b/tests/tests/view/src/android/view/cts/ViewTest.java
@@ -3574,6 +3574,34 @@
View source, int changeType) {
}
+
+ @Override
+ public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
+ return false;
+ }
+
+ @Override
+ public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {
+ }
+
+ @Override
+ public void onStopNestedScroll(View target) {
+ }
+
+ @Override
+ public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
+ int dxUnconsumed, int dyUnconsumed) {
+ }
+
+ @Override
+ public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
+ }
+
+ @Override
+ public boolean onNestedFling(View target, float velocityX, float velocityY,
+ boolean consumed) {
+ return false;
+ }
}
private final class OnCreateContextMenuListenerImpl implements OnCreateContextMenuListener {
diff --git a/tests/tests/view/src/android/view/cts/WindowTest.java b/tests/tests/view/src/android/view/cts/WindowTest.java
index afe5f0d..154c5cd 100644
--- a/tests/tests/view/src/android/view/cts/WindowTest.java
+++ b/tests/tests/view/src/android/view/cts/WindowTest.java
@@ -997,6 +997,24 @@
@Override
public void takeInputQueue(InputQueue.Callback callback) {
}
+
+ @Override
+ public void setStatusBarColor(int color) {
+ }
+
+ @Override
+ public int getStatusBarColor() {
+ return 0;
+ }
+
+ @Override
+ public void setNavigationBarColor(int color) {
+ }
+
+ @Override
+ public int getNavigationBarColor() {
+ return 0;
+ }
}
private class MockWindowCallback implements Window.Callback {
diff --git a/tests/tests/view/src/android/view/inputmethod/cts/InputMethodInfoTest.java b/tests/tests/view/src/android/view/inputmethod/cts/InputMethodInfoTest.java
index 9f92d23..b4d65a5 100644
--- a/tests/tests/view/src/android/view/inputmethod/cts/InputMethodInfoTest.java
+++ b/tests/tests/view/src/android/view/inputmethod/cts/InputMethodInfoTest.java
@@ -164,6 +164,7 @@
mInputMethodInfo.writeToParcel(p, 0);
p.setDataPosition(0);
final InputMethodInfo imi = InputMethodInfo.CREATOR.createFromParcel(p);
+ p.recycle();
assertEquals(mInputMethodInfo.getPackageName(), imi.getPackageName());
assertEquals(mInputMethodInfo.getServiceName(), imi.getServiceName());
@@ -178,6 +179,7 @@
mInputMethodSubtype.writeToParcel(p, 0);
p.setDataPosition(0);
final InputMethodSubtype subtype = InputMethodSubtype.CREATOR.createFromParcel(p);
+ p.recycle();
assertEquals(mInputMethodSubtype.containsExtraValueKey(mSubtypeExtraValue_key),
subtype.containsExtraValueKey(mSubtypeExtraValue_key));
diff --git a/tests/tests/webkit/AndroidManifest.xml b/tests/tests/webkit/AndroidManifest.xml
index f4424a8..776f695 100644
--- a/tests/tests/webkit/AndroidManifest.xml
+++ b/tests/tests/webkit/AndroidManifest.xml
@@ -28,9 +28,12 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.stub"
- android:label="CTS tests of android.webkit"/>
+ android:label="CTS tests of android.webkit">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/tests/webkit/src/android/webkit/cts/TestHtmlConstants.java b/tests/tests/webkit/src/android/webkit/cts/TestHtmlConstants.java
index 11cc1a5..cc39f48 100644
--- a/tests/tests/webkit/src/android/webkit/cts/TestHtmlConstants.java
+++ b/tests/tests/webkit/src/android/webkit/cts/TestHtmlConstants.java
@@ -65,6 +65,7 @@
public static final String DATABASE_ACCESS_URL = "webkit/test_databaseaccess.html";
public static final String STOP_LOADING_URL = "webkit/test_stop_loading.html";
public static final String BLANK_TAG_URL = "webkit/blank_tag.html";
+ public static final String PAGE_WITH_LINK_URL = "webkit/page_with_link.html";
// Must match the title of the page at
// android/frameworks/base/core/res/res/raw/loaderror.html
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebChromeClientTest.java b/tests/tests/webkit/src/android/webkit/cts/WebChromeClientTest.java
index 3142298..fca14e2 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebChromeClientTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebChromeClientTest.java
@@ -126,6 +126,7 @@
Thread.sleep(100); // Wait for open to be received on the icon db thread.
assertFalse(webChromeClient.hadOnReceivedIcon());
+ assertNull(mOnUiThread.getFavicon());
String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
mOnUiThread.loadUrlAndWaitForCompletion(url);
@@ -136,6 +137,7 @@
return webChromeClient.hadOnReceivedIcon();
}
}.run();
+ assertNotNull(mOnUiThread.getFavicon());
}
public void runWindowTest(boolean expectWindowClose) throws Exception {
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java
index 7872dbe..5907d2f 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java
@@ -92,11 +92,12 @@
"<a href=\"" + TEST_URL + "\" id=\"link\">new page</a>" +
"</body></html>";
mOnUiThread.loadDataAndWaitForCompletion(data, "text/html", null);
- clickOnLinkUsingJs("link");
+ clickOnLinkUsingJs("link", mOnUiThread);
assertEquals(TEST_URL, webViewClient.getLastShouldOverrideUrl());
}
// Verify shouldoverrideurlloading called on webview called via onCreateWindow
+ // TODO(sgurun) upstream this test to Aw.
public void testShouldOverrideUrlLoadingOnCreateWindow() throws Exception {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
@@ -110,12 +111,14 @@
mOnUiThread.getSettings().setJavaScriptEnabled(true);
mOnUiThread.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
mOnUiThread.getSettings().setSupportMultipleWindows(true);
+
+ final WebView childWebView = mOnUiThread.createWebView();
+
mOnUiThread.setWebChromeClient(new WebChromeClient() {
@Override
public boolean onCreateWindow(
WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) {
WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj;
- WebView childWebView = new WebView(view.getContext());
childWebView.setWebViewClient(childWebViewClient);
childWebView.getSettings().setJavaScriptEnabled(true);
transport.setWebView(childWebView);
@@ -134,13 +137,30 @@
return childWebViewClient.hasOnPageFinishedCalled();
}
}.run();
- assertEquals(mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL),
+ assertEquals(mWebServer.getAssetUrl(TestHtmlConstants.PAGE_WITH_LINK_URL),
childWebViewClient.getLastShouldOverrideUrl());
+
+ // Now test a navigation within the page
+ //TODO(hush) Enable this portion when b/12804986 is fixed.
+ /*
+ WebViewOnUiThread childWebViewOnUiThread = new WebViewOnUiThread(this, childWebView);
+ final int childCallCount = childWebViewClient.getShouldOverrideUrlLoadingCallCount();
+ final int mainCallCount = mainWebViewClient.getShouldOverrideUrlLoadingCallCount();
+ clickOnLinkUsingJs("link", childWebViewOnUiThread);
+ new PollingCheck(TEST_TIMEOUT) {
+ @Override
+ protected boolean check() {
+ return childWebViewClient.getShouldOverrideUrlLoadingCallCount() > childCallCount;
+ }
+ }.run();
+ assertEquals(mainCallCount, mainWebViewClient.getShouldOverrideUrlLoadingCallCount());
+ assertEquals(TEST_URL, childWebViewClient.getLastShouldOverrideUrl());
+ */
}
- private void clickOnLinkUsingJs(final String linkId) {
+ private void clickOnLinkUsingJs(final String linkId, WebViewOnUiThread webViewOnUiThread) {
EvaluateJsResultPollingCheck jsResult = new EvaluateJsResultPollingCheck("null");
- mOnUiThread.evaluateJavascript(
+ webViewOnUiThread.evaluateJavascript(
"document.getElementById('" + linkId + "').click();" +
"console.log('element with id [" + linkId + "] clicked');", jsResult);
jsResult.run();
@@ -336,6 +356,7 @@
private boolean mOnReceivedHttpAuthRequestCalled;
private boolean mOnUnhandledKeyEventCalled;
private boolean mOnScaleChangedCalled;
+ private int mShouldOverrideUrlLoadingCallCount;
private String mLastShouldOverrideUrl;
public MockWebViewClient() {
@@ -378,6 +399,10 @@
return mOnScaleChangedCalled;
}
+ public int getShouldOverrideUrlLoadingCallCount() {
+ return mShouldOverrideUrlLoadingCallCount;
+ }
+
public String getLastShouldOverrideUrl() {
return mLastShouldOverrideUrl;
}
@@ -444,6 +469,7 @@
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
mLastShouldOverrideUrl = url;
+ mShouldOverrideUrlLoadingCallCount++;
return false;
}
}
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
index d339a62..982acfc 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
@@ -80,14 +80,21 @@
import java.io.IOException;
import java.util.Collections;
import java.util.Date;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.HashMap;
+import java.util.Map;
import org.apache.http.Header;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpRequest;
+import org.apache.http.util.EncodingUtils;
+import org.apache.http.util.EntityUtils;
public class WebViewTest extends ActivityInstrumentationTestCase2<WebViewStubActivity> {
private static final String LOGTAG = "WebViewTest";
@@ -147,7 +154,7 @@
mOnUiThread.cleanUp();
}
if (mWebServer != null) {
- mWebServer.shutdown();
+ stopWebServer();
}
if (mIconDb != null) {
mIconDb.removeAllIcons();
@@ -399,6 +406,36 @@
}
@UiThreadTest
+ public void testPostUrlWithNonNetworkUrl() throws Exception {
+ final String nonNetworkUrl = "file:///android_asset/" + TestHtmlConstants.HELLO_WORLD_URL;
+
+ mOnUiThread.postUrlAndWaitForCompletion(nonNetworkUrl, new byte[1]);
+
+ // Test if the nonNetworkUrl is loaded
+ assertEquals(TestHtmlConstants.HELLO_WORLD_TITLE, mWebView.getTitle());
+ }
+
+ @UiThreadTest
+ public void testPostUrlWithNetworkUrl() throws Exception {
+ startWebServer(false);
+ final String networkUrl = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
+ final String postDataString = "username=my_username&password=my_password";
+ final byte[] postData = EncodingUtils.getBytes(postDataString, "BASE64");
+
+ mOnUiThread.postUrlAndWaitForCompletion(networkUrl, postData);
+
+ HttpRequest request = mWebServer.getLastRequest(TestHtmlConstants.HELLO_WORLD_URL);
+ // The last request should be POST
+ assertEquals(request.getRequestLine().getMethod(), "POST");
+
+ // The last request should have a request body
+ assertTrue(request instanceof HttpEntityEnclosingRequest);
+ HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity();
+ String entityString = EntityUtils.toString(entity);
+ assertEquals(entityString, postDataString);
+ }
+
+ @UiThreadTest
public void testLoadUrlDoesNotStripParamsWhenLoadingContentUrls() throws Exception {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
@@ -460,6 +497,28 @@
assertEquals(requester, header.getValue());
}
+ public void testCanInjectHeaders() throws Exception {
+ final String X_FOO = "X-foo";
+ final String X_FOO_VALUE = "test";
+
+ final String X_REFERER = "Referer";
+ final String X_REFERER_VALUE = "http://www.example.com/";
+ startWebServer(false);
+ String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
+ HashMap<String, String> map = new HashMap<String, String>();
+ map.put(X_FOO, X_FOO_VALUE);
+ map.put(X_REFERER, X_REFERER_VALUE);
+ mOnUiThread.loadUrlAndWaitForCompletion(url, map);
+
+ HttpRequest request = mWebServer.getLastRequest(TestHtmlConstants.HELLO_WORLD_URL);
+ for (Map.Entry<String,String> value : map.entrySet()) {
+ String header = value.getKey();
+ Header[] matchingHeaders = request.getHeaders(header);
+ assertEquals("header " + header + " not found", 1, matchingHeaders.length);
+ assertEquals(value.getValue(), matchingHeaders[0].getValue());
+ }
+ }
+
@SuppressWarnings("deprecation")
@UiThreadTest
public void testGetVisibleTitleHeight() throws Exception {
@@ -634,6 +693,30 @@
String url = mWebServer.getAssetUrl(TestHtmlConstants.ADD_JAVA_SCRIPT_INTERFACE_URL);
mOnUiThread.loadUrlAndWaitForCompletion(url);
assertEquals("Original title", obj.waitForResult());
+
+ // Verify that only methods annotated with @JavascriptInterface are exposed
+ // on the JavaScript interface object.
+ mOnUiThread.evaluateJavascript("typeof dummy.provideResult",
+ new ValueCallback<String>() {
+ @Override
+ public void onReceiveValue(String result) {
+ assertEquals("\"function\"", result);
+ }
+ });
+ mOnUiThread.evaluateJavascript("typeof dummy.wasProvideResultCalled",
+ new ValueCallback<String>() {
+ @Override
+ public void onReceiveValue(String result) {
+ assertEquals("\"undefined\"", result);
+ }
+ });
+ mOnUiThread.evaluateJavascript("typeof dummy.getClass",
+ new ValueCallback<String>() {
+ @Override
+ public void onReceiveValue(String result) {
+ assertEquals("\"undefined\"", result);
+ }
+ });
}
@UiThreadTest
@@ -748,6 +831,42 @@
assertEquals("removedObject", resultObject.getResult());
}
+ public void testAddJavascriptInterfaceExceptions() throws Exception {
+ WebSettings settings = mOnUiThread.getSettings();
+ settings.setJavaScriptEnabled(true);
+ settings.setJavaScriptCanOpenWindowsAutomatically(true);
+
+ final AtomicBoolean mJsInterfaceWasCalled = new AtomicBoolean(false) {
+ @JavascriptInterface
+ public synchronized void call() {
+ set(true);
+ // The main purpose of this test is to ensure an exception here does not
+ // crash the implementation.
+ throw new RuntimeException("Javascript Interface exception");
+ }
+ };
+
+ mOnUiThread.addJavascriptInterface(mJsInterfaceWasCalled, "dummy");
+
+ mOnUiThread.loadUrlAndWaitForCompletion("about:blank");
+
+ assertFalse(mJsInterfaceWasCalled.get());
+
+ final CountDownLatch resultLatch = new CountDownLatch(1);
+ mOnUiThread.evaluateJavascript(
+ "try {dummy.call(); 'fail'; } catch (exception) { 'pass'; } ",
+ new ValueCallback<String>() {
+ @Override
+ public void onReceiveValue(String result) {
+ assertEquals("\"pass\"", result);
+ resultLatch.countDown();
+ }
+ });
+
+ assertTrue(resultLatch.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS));
+ assertTrue(mJsInterfaceWasCalled.get());
+ }
+
public void testJavascriptInterfaceCustomPropertiesClearedOnReload() throws Exception {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
@@ -1003,7 +1122,6 @@
// Verify that the resource request makes it to the server.
assertTrue(mWebServer.wasResourceRequested(imgUrl));
assertEquals(historyUrl, mWebView.getUrl());
- assertEquals(historyUrl, mWebView.getOriginalUrl());
// Check that reported URL is "about:blank" when supplied history URL
// is null.
@@ -1013,7 +1131,6 @@
"text/html", "UTF-8", null);
assertTrue(mWebServer.wasResourceRequested(imgUrl));
assertEquals("about:blank", mWebView.getUrl());
- assertEquals("about:blank", mWebView.getOriginalUrl());
// Test that JavaScript can access content from the same origin as the base URL.
mWebView.getSettings().setJavaScriptEnabled(true);
@@ -1038,19 +1155,18 @@
mOnUiThread.loadDataWithBaseURLAndWaitForCompletion("http://www.foo.com",
HTML_HEADER + "<title>Hello World%21</title><body>bar</body></html>",
"text/html", "UTF-8", null);
- assertEquals("Hello World%21", mWebView.getTitle());
+ assertEquals("Hello World%21", mOnUiThread.getTitle());
// Check that when a data: base URL is used, we treat the String to load as a data: URL
// and run load steps such as decoding URL entities (i.e., contrary to the test case
// above.)
mOnUiThread.loadDataWithBaseURLAndWaitForCompletion("data:foo",
HTML_HEADER + "<title>Hello World%21</title></html>", "text/html", "UTF-8", null);
- assertEquals("Hello World!", mWebView.getTitle());
+ assertEquals("Hello World!", mOnUiThread.getTitle());
// Check the method is null input safe.
mOnUiThread.loadDataWithBaseURLAndWaitForCompletion(null, null, null, null, null);
- assertEquals("about:blank", mWebView.getUrl());
- assertEquals("about:blank", mWebView.getOriginalUrl());
+ assertEquals("about:blank", mOnUiThread.getUrl());
}
private static class WaitForFindResultsListener extends FutureTask<Integer>
@@ -1636,19 +1752,6 @@
}
@UiThreadTest
- public void testGetFavicon() throws Exception {
- if (!NullWebViewUtils.isWebViewAvailable()) {
- return;
- }
- startWebServer(false);
- String url = mWebServer.getAssetUrl(TestHtmlConstants.TEST_FAVICON_URL);
- mOnUiThread.loadUrlAndWaitForCompletion(url);
- mWebView.getFavicon();
- // ToBeFixed: Favicon is not loaded automatically.
- // assertNotNull(mWebView.getFavicon());
- }
-
- @UiThreadTest
public void testClearHistory() throws Exception {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
@@ -1716,7 +1819,7 @@
assertNotNull(restoreList);
assertEquals(3, restoreList.getSize());
assertEquals(2, saveList.getCurrentIndex());
- /* ToBeFixed: The WebHistoryItems do not get inflated. Uncomment remaining tests when fixed.
+
// wait for the list items to get inflated
new PollingCheck(TEST_TIMEOUT) {
@Override
@@ -1737,7 +1840,6 @@
assertEquals(url1, copyListAfterRestore.getItemAtIndex(0).getUrl());
assertEquals(url2, copyListAfterRestore.getItemAtIndex(1).getUrl());
assertEquals(url3, copyListAfterRestore.getItemAtIndex(2).getUrl());
- */
}
public void testSetWebViewClient() throws Throwable {
@@ -1978,6 +2080,35 @@
assertEquals("Second page", mOnUiThread.getTitle());
}
+ public void testSecureServerRequestingClientCertDoesNotCancelRequest() throws Throwable {
+ mWebServer = new CtsTestServer(getActivity(), CtsTestServer.SslMode.WANTS_CLIENT_AUTH);
+ final String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
+ final SslErrorWebViewClient webViewClient = new SslErrorWebViewClient();
+ mOnUiThread.setWebViewClient(webViewClient);
+ mOnUiThread.clearSslPreferences();
+ mOnUiThread.loadUrlAndWaitForCompletion(url);
+ // Page loaded OK...
+ assertTrue(webViewClient.wasOnReceivedSslErrorCalled());
+ assertEquals(TestHtmlConstants.HELLO_WORLD_TITLE, mOnUiThread.getTitle());
+ assertEquals(0, webViewClient.onReceivedErrorCode());
+ }
+
+ public void testSecureServerRequiringClientCertDoesCancelRequest() throws Throwable {
+ mWebServer = new CtsTestServer(getActivity(), CtsTestServer.SslMode.NEEDS_CLIENT_AUTH);
+ final String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
+ final SslErrorWebViewClient webViewClient = new SslErrorWebViewClient();
+ mOnUiThread.setWebViewClient(webViewClient);
+ mOnUiThread.clearSslPreferences();
+ mOnUiThread.loadUrlAndWaitForCompletion(url);
+ // Page NOT loaded OK...
+ // In this case, we must NOT have received the onReceivedSslError callback as that is for
+ // recoverable (e.g. server auth) errors, whereas failing mandatory client auth is non-
+ // recoverable and should drop straight through to a load error.
+ assertFalse(webViewClient.wasOnReceivedSslErrorCalled());
+ assertFalse(TestHtmlConstants.HELLO_WORLD_TITLE.equals(mOnUiThread.getTitle()));
+ assertEquals(WebViewClient.ERROR_FAILED_SSL_HANDSHAKE, webViewClient.onReceivedErrorCode());
+ }
+
public void testRequestChildRectangleOnScreen() throws Throwable {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
@@ -2008,12 +2139,13 @@
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
+
+ final CountDownLatch resultLatch = new CountDownLatch(1);
final class MyDownloadListener implements DownloadListener {
public String url;
public String mimeType;
public long contentLength;
public String contentDisposition;
- public boolean called;
@Override
public void onDownloadStart(String url, String userAgent, String contentDisposition,
@@ -2022,7 +2154,7 @@
this.mimeType = mimetype;
this.contentLength = contentLength;
this.contentDisposition = contentDisposition;
- this.called = true;
+ resultLatch.countDown();
}
}
@@ -2039,27 +2171,17 @@
// the WebView will load the new URL.
mOnUiThread.setDownloadListener(listener);
mOnUiThread.getSettings().setJavaScriptEnabled(true);
-
- // See b/13675265 for discussion on why the setTimeout is necessary.
- // Works around a Blink bug.
mOnUiThread.loadDataAndWaitForCompletion(
- "<html><body onload=\"setTimeout(" +
- "function() { window.location = \'" + url + "\'; }, 100);\">" +
- "</body></html>", "text/html", null);
+ "<html><body onload=\"window.location = \'" + url + "\'\"></body></html>",
+ "text/html", null);
// Wait for layout to complete before setting focus.
getInstrumentation().waitForIdleSync();
- new PollingCheck(TEST_TIMEOUT) {
- @Override
- protected boolean check() {
- return listener.called;
- }
- }.run();
+ assertTrue(resultLatch.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS));
assertEquals(url, listener.url);
assertTrue(listener.contentDisposition.contains("test.bin"));
- // ToBeFixed: uncomment the following tests after fixing the framework
- // assertEquals(mimeType, listener.mimeType);
- // assertEquals(length, listener.contentLength);
+ assertEquals(length, listener.contentLength);
+ assertEquals(mimeType, listener.mimeType);
}
@UiThreadTest
@@ -2349,11 +2471,6 @@
});
}
- @UiThreadTest
- public void testInternals() {
- // Do not test these APIs. They are implementation details.
- }
-
private static class HrefCheckHandler extends Handler {
private boolean mHadRecieved;
@@ -2448,51 +2565,6 @@
return true;
}
- // Find b1 inside b2
- private boolean checkBitmapInsideAnother(Bitmap b1, Bitmap b2) {
- int w = b1.getWidth();
- int h = b1.getHeight();
-
- for (int i = 0; i < (b2.getWidth()-w+1); i++) {
- for (int j = 0; j < (b2.getHeight()-h+1); j++) {
- if (checkBitmapInsideAnother(b1, b2, i, j))
- return true;
- }
- }
- return false;
- }
-
- private boolean comparePixel(int p1, int p2, int maxError) {
- int err;
- err = Math.abs(((p1&0xff000000)>>>24) - ((p2&0xff000000)>>>24));
- if (err > maxError)
- return false;
-
- err = Math.abs(((p1&0x00ff0000)>>>16) - ((p2&0x00ff0000)>>>16));
- if (err > maxError)
- return false;
-
- err = Math.abs(((p1&0x0000ff00)>>>8) - ((p2&0x0000ff00)>>>8));
- if (err > maxError)
- return false;
-
- err = Math.abs(((p1&0x000000ff)>>>0) - ((p2&0x000000ff)>>>0));
- if (err > maxError)
- return false;
-
- return true;
- }
-
- private boolean checkBitmapInsideAnother(Bitmap b1, Bitmap b2, int x, int y) {
- for (int i = 0; i < b1.getWidth(); i++)
- for (int j = 0; j < b1.getHeight(); j++) {
- if (!comparePixel(b1.getPixel(i, j), b2.getPixel(x + i, y + j), 10)) {
- return false;
- }
- }
- return true;
- }
-
/**
* Waits at least MIN_SCROLL_WAIT_MS for scrolling to start. Once started,
* scrolling is checked every SCROLL_WAIT_INTERVAL_MS for changes. Once
@@ -2522,6 +2594,7 @@
final class SslErrorWebViewClient extends WaitForLoadedClient {
private boolean mWasOnReceivedSslErrorCalled;
private String mErrorUrl;
+ private int mErrorCode;
public SslErrorWebViewClient() {
super(mOnUiThread);
@@ -2532,6 +2605,11 @@
mErrorUrl = error.getUrl();
handler.proceed();
}
+ @Override
+ public void onReceivedError(WebView view, int errorCode, String description,
+ String failingUrl) {
+ mErrorCode = errorCode;
+ }
public void resetWasOnReceivedSslErrorCalled() {
mWasOnReceivedSslErrorCalled = false;
}
@@ -2541,6 +2619,9 @@
public String errorUrl() {
return mErrorUrl;
}
+ public int onReceivedErrorCode() {
+ return mErrorCode;
+ }
}
private void pollingCheckForCanZoomIn() {
diff --git a/tests/tests/widget/AndroidManifest.xml b/tests/tests/widget/AndroidManifest.xml
index e69a7d5..3c7fe5f 100644
--- a/tests/tests/widget/AndroidManifest.xml
+++ b/tests/tests/widget/AndroidManifest.xml
@@ -23,9 +23,12 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.stub"
- android:label="CTS tests of android.widget"/>
+ android:label="CTS tests of android.widget">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tools/cts-holo-generation/Android.mk b/tools/cts-holo-generation/Android.mk
index 5f12ef0..2affc5e 100644
--- a/tools/cts-holo-generation/Android.mk
+++ b/tools/cts-holo-generation/Android.mk
@@ -23,7 +23,7 @@
LOCAL_DEX_PREOPT := false
LOCAL_PROGUARD_ENABLED := disabled
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tools/cts-holo-generation/AndroidManifest.xml b/tools/cts-holo-generation/AndroidManifest.xml
index d7de891..ad4ae22 100644
--- a/tools/cts-holo-generation/AndroidManifest.xml
+++ b/tools/cts-holo-generation/AndroidManifest.xml
@@ -7,7 +7,7 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<instrumentation
- android:name="android.test.InstrumentationTestRunner"
+ android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.holo_capture" />
<application>
diff --git a/tools/cts-java-scanner-doclet/src/com/android/cts/javascannerdoclet/CtsJavaScannerDoclet.java b/tools/cts-java-scanner-doclet/src/com/android/cts/javascannerdoclet/CtsJavaScannerDoclet.java
index 40a3d05..f54ba00 100644
--- a/tools/cts-java-scanner-doclet/src/com/android/cts/javascannerdoclet/CtsJavaScannerDoclet.java
+++ b/tools/cts-java-scanner-doclet/src/com/android/cts/javascannerdoclet/CtsJavaScannerDoclet.java
@@ -61,6 +61,8 @@
*/
public class CtsJavaScannerDoclet extends Doclet {
+ private static final String JUNIT4_TEST_ANNOTATION = "org.junit.Test";
+
static final String JUNIT_TEST_CASE_CLASS_NAME = "junit.framework.testcase";
public static boolean start(RootDoc root) {
@@ -72,31 +74,61 @@
PrintWriter writer = new PrintWriter(System.out);
for (ClassDoc clazz : classes) {
- if (clazz.isAbstract() || !isValidJUnitTestCase(clazz)) {
+ if (clazz.isAbstract()) {
continue;
}
+
+ final boolean isJUnit3 = isJUnit3TestCase(clazz);
+ if (!isJUnit3 && !isJUnit4TestClass(clazz)) {
+ continue;
+ }
+
writer.append("suite:").println(clazz.containingPackage().name());
writer.append("case:").println(clazz.name());
for (; clazz != null; clazz = clazz.superclass()) {
for (MethodDoc method : clazz.methods()) {
- if (!method.name().startsWith("test")) {
- continue;
- }
int timeout = -1;
- AnnotationDesc[] annotations = method.annotations();
- for (AnnotationDesc annot : annotations) {
- AnnotationTypeDoc atype = annot.annotationType();
- if (atype.toString().equals("com.android.cts.util.TimeoutReq")) {
- ElementValuePair[] cpairs = annot.elementValues();
- for (ElementValuePair pair: cpairs) {
- AnnotationTypeElementDoc elem = pair.element();
- AnnotationValue value = pair.value();
- if (elem.name().equals("minutes")) {
- timeout = ((Integer)value.value());
+ if (isJUnit3) {
+ if (!method.name().startsWith("test")) {
+ continue;
+ }
+
+ AnnotationDesc[] annotations = method.annotations();
+ for (AnnotationDesc annot : annotations) {
+ String atype = annot.annotationType().toString();
+ if (atype.equals("android.cts.util.TimeoutReq")) {
+ ElementValuePair[] cpairs = annot.elementValues();
+ for (ElementValuePair pair : cpairs) {
+ AnnotationTypeElementDoc elem = pair.element();
+ AnnotationValue value = pair.value();
+ if (elem.name().equals("minutes")) {
+ timeout = ((Integer) value.value());
+ }
}
}
}
+ } else {
+ /* JUnit4 */
+ boolean isTest = false;
+
+ for (AnnotationDesc annot : method.annotations()) {
+ if (annot.annotationType().toString().equals(JUNIT4_TEST_ANNOTATION)) {
+ isTest = true;
+
+ for (ElementValuePair pair : annot.elementValues()) {
+ if (pair.element().name().equals("timeout")) {
+ /* JUnit4 timeouts are in milliseconds. */
+ timeout = (int) (((Long) pair.value().value()) / 60000L);
+ }
+ }
+ }
+ }
+
+ if (!isTest) {
+ continue;
+ }
}
+
writer.append("test:");
if (timeout >= 0) {
writer.append(method.name()).println(":" + timeout);
@@ -111,7 +143,7 @@
return true;
}
- private static boolean isValidJUnitTestCase(ClassDoc clazz) {
+ private static boolean isJUnit3TestCase(ClassDoc clazz) {
while((clazz = clazz.superclass()) != null) {
if (JUNIT_TEST_CASE_CLASS_NAME.equals(clazz.qualifiedName().toLowerCase())) {
return true;
@@ -119,4 +151,15 @@
}
return false;
}
+
+ private static boolean isJUnit4TestClass(ClassDoc clazz) {
+ for (MethodDoc method : clazz.methods()) {
+ for (AnnotationDesc annot : method.annotations()) {
+ if (annot.annotationType().toString().equals(JUNIT4_TEST_ANNOTATION)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
}
diff --git a/tools/cts-java-scanner/src/com/android/cts/javascanner/DocletRunner.java b/tools/cts-java-scanner/src/com/android/cts/javascanner/DocletRunner.java
index e56a7cf..06951b9 100644
--- a/tools/cts-java-scanner/src/com/android/cts/javascanner/DocletRunner.java
+++ b/tools/cts-java-scanner/src/com/android/cts/javascanner/DocletRunner.java
@@ -77,8 +77,6 @@
sourcePath.add("./cts/tests/src");
sourcePath.add("./cts/libs/commonutil/src");
sourcePath.add("./cts/libs/deviceutil/src");
- sourcePath.add("./frameworks/testing/uiautomator/library/testrunner-src");
- sourcePath.add("./frameworks/testing/uiautomator_test_libraries/src");
sourcePath.add(sourceDir.toString());
return join(sourcePath, ":");
}
@@ -86,6 +84,7 @@
private String getClassPath() {
List<String> classPath = new ArrayList<String>();
classPath.add("./prebuilts/misc/common/tradefed/tradefed-prebuilt.jar");
+ classPath.add("./prebuilts/misc/common/ub-uiautomator/ub-uiautomator.jar");
return join(classPath, ":");
}
diff --git a/tools/cts-reference-app-lib/Android.mk b/tools/cts-reference-app-lib/Android.mk
index 8341970..fae85b4 100644
--- a/tools/cts-reference-app-lib/Android.mk
+++ b/tools/cts-reference-app-lib/Android.mk
@@ -24,7 +24,7 @@
# and when built explicitly put it in the data partition
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_SDK_VERSION := current
LOCAL_MODULE := android.cts.refapp
diff --git a/tools/device-setup/TestDeviceSetup/Android.mk b/tools/device-setup/TestDeviceSetup/Android.mk
index ba1998c..44e66bb 100644
--- a/tools/device-setup/TestDeviceSetup/Android.mk
+++ b/tools/device-setup/TestDeviceSetup/Android.mk
@@ -25,8 +25,6 @@
LOCAL_DEX_PREOPT := false
LOCAL_PROGUARD_ENABLED := disabled
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
LOCAL_SDK_VERSION := current
LOCAL_PACKAGE_NAME := TestDeviceSetup
diff --git a/tools/tradefed-host/.classpath b/tools/tradefed-host/.classpath
index 0b1866d..b82e340 100644
--- a/tools/tradefed-host/.classpath
+++ b/tools/tradefed-host/.classpath
@@ -2,10 +2,11 @@
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="res"/>
+ <classpathentry kind="src" path="commonutil-src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry exported="true" kind="var" path="CTS_SRC_ROOT/out/host/common/obj/JAVA_LIBRARIES/ctsdeviceinfolib_intermediates/javalib.jar"/>
<classpathentry exported="true" kind="var" path="CTS_SRC_ROOT/out/host/common/obj/JAVA_LIBRARIES/hosttestlib_intermediates/javalib.jar"/>
<classpathentry kind="var" path="CTS_SRC_ROOT/out/host/common/obj/JAVA_LIBRARIES/ddmlib-prebuilt_intermediates/ddmlib-prebuilt.jar" sourcepath="/SDK_SRC_ROOT"/>
- <classpathentry kind="var" path="CTS_SRC_ROOT/out/host/common/obj/JAVA_LIBRARIES/tradefed-prebuilt_intermediates/tradefed-prebuilt.jar" sourcepath="/TRADEFED_ROOT/tools/tradefederation/src"/>
+ <classpathentry kind="var" path="CTS_SRC_ROOT/out/host/common/obj/JAVA_LIBRARIES/tradefed-prebuilt_intermediates/tradefed-prebuilt.jar" sourcepath="/TRADEFED_ROOT/tools/tradefederation/src"/>
<classpathentry kind="output" path="bin"/>
</classpath>
diff --git a/tools/tradefed-host/.project b/tools/tradefed-host/.project
index 990c63e..ffd16af 100644
--- a/tools/tradefed-host/.project
+++ b/tools/tradefed-host/.project
@@ -14,4 +14,11 @@
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
+ <linkedResources>
+ <link>
+ <name>commonutil-src</name>
+ <type>2</type>
+ <locationURI>CTS_SRC_ROOT/cts/libs/commonutil/src</locationURI>
+ </link>
+ </linkedResources>
</projectDescription>
diff --git a/tools/tradefed-host/etc/cts-tradefed b/tools/tradefed-host/etc/cts-tradefed
index bc5c07a..485740e 100755
--- a/tools/tradefed-host/etc/cts-tradefed
+++ b/tools/tradefed-host/etc/cts-tradefed
@@ -51,17 +51,21 @@
# check if in Android build env
-if [ ! -z ${ANDROID_BUILD_TOP} ]; then
- HOST=`uname`
- if [ "$HOST" == "Linux" ]; then
- OS="linux-x86"
- elif [ "$HOST" == "Darwin" ]; then
- OS="darwin-x86"
+if [ ! -z "${ANDROID_BUILD_TOP}" ]; then
+ if [ ! -z "${ANDROID_HOST_OUT}" ]; then
+ CTS_ROOT=${ANDROID_HOST_OUT}/cts
else
- echo "Unrecognized OS"
- exit
- fi;
- CTS_ROOT=${ANDROID_BUILD_TOP}/out/host/${OS}/cts
+ HOST=`uname`
+ if [ "$HOST" == "Linux" ]; then
+ OS="linux-x86"
+ elif [ "$HOST" == "Darwin" ]; then
+ OS="darwin-x86"
+ else
+ echo "Unrecognized OS"
+ exit
+ fi
+ CTS_ROOT=${ANDROID_BUILD_TOP}/${OUT_DIR:-out}/host/${OS}/cts
+ fi
if [ ! -d ${CTS_ROOT} ]; then
echo "Could not find $CTS_ROOT in Android build environment. Try 'make cts'"
exit
diff --git a/tools/tradefed-host/res/config/cts.xml b/tools/tradefed-host/res/config/cts.xml
index 158f49d..416b400 100644
--- a/tools/tradefed-host/res/config/cts.xml
+++ b/tools/tradefed-host/res/config/cts.xml
@@ -22,6 +22,7 @@
<test class="com.android.cts.tradefed.testtype.CtsTest" />
<logger class="com.android.tradefed.log.FileLogger" />
<result_reporter class="com.android.cts.tradefed.result.CtsXmlResultReporter" />
+ <result_reporter class="com.android.cts.tradefed.result.CtsTestLogReporter" />
<result_reporter class="com.android.cts.tradefed.result.IssueReporter" />
</configuration>
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsTestLogReporter.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsTestLogReporter.java
new file mode 100644
index 0000000..bbdcb05
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsTestLogReporter.java
@@ -0,0 +1,137 @@
+/*
+ * 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.tradefed.result;
+
+import com.android.cts.tradefed.device.DeviceInfoCollector;
+import com.android.ddmlib.Log;
+import com.android.ddmlib.Log.LogLevel;
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.config.OptionCopier;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.IShardableListener;
+import com.android.tradefed.result.StubTestInvocationListener;
+
+import java.util.Map;
+
+/**
+ * Dumps tests in progress to stdout
+ */
+public class CtsTestLogReporter extends StubTestInvocationListener implements IShardableListener {
+
+ @Option(name = "quiet-output", description = "Mute display of test results.")
+ private boolean mQuietOutput = false;
+
+ protected IBuildInfo mBuildInfo;
+ private String mDeviceSerial;
+ private TestResults mResults = new TestResults();
+ private TestPackageResult mCurrentPkgResult = null;
+ private boolean mIsDeviceInfoRun = false;
+
+ @Override
+ public void invocationStarted(IBuildInfo buildInfo) {
+ mDeviceSerial = buildInfo.getDeviceSerial() == null ? "unknown_device" : buildInfo.getDeviceSerial();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void testRunStarted(String name, int numTests) {
+ if (mCurrentPkgResult != null && !name.equals(mCurrentPkgResult.getAppPackageName())) {
+ // display results from previous run
+ logCompleteRun(mCurrentPkgResult);
+ }
+ mIsDeviceInfoRun = name.equals(DeviceInfoCollector.APP_PACKAGE_NAME);
+ if (mIsDeviceInfoRun) {
+ logResult("Collecting device info");
+ } else {
+ if (mCurrentPkgResult == null || !name.equals(mCurrentPkgResult.getAppPackageName())) {
+ logResult("-----------------------------------------");
+ logResult("Test package %s started", name);
+ logResult("-----------------------------------------");
+ }
+ mCurrentPkgResult = mResults.getOrCreatePackage(name);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void testStarted(TestIdentifier test) {
+ mCurrentPkgResult.insertTest(test);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void testFailed(TestFailure status, TestIdentifier test, String trace) {
+ mCurrentPkgResult.reportTestFailure(test, CtsTestStatus.FAIL, trace);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void testEnded(TestIdentifier test, Map<String, String> testMetrics) {
+ mCurrentPkgResult.reportTestEnded(test);
+ Test result = mCurrentPkgResult.findTest(test);
+ String stack = result.getStackTrace() == null ? "" : "\n" + result.getStackTrace();
+ logResult("%s#%s %s %s", test.getClassName(), test.getTestName(), result.getResult(),
+ stack);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void invocationEnded(long elapsedTime) {
+ // display the results of the last completed run
+ if (mCurrentPkgResult != null) {
+ logCompleteRun(mCurrentPkgResult);
+ }
+ }
+
+ private void logResult(String format, Object... args) {
+ if (mQuietOutput) {
+ CLog.i(format, args);
+ } else {
+ Log.logAndDisplay(LogLevel.INFO, mDeviceSerial, String.format(format, args));
+ }
+ }
+
+ private void logCompleteRun(TestPackageResult pkgResult) {
+ if (pkgResult.getAppPackageName().equals(DeviceInfoCollector.APP_PACKAGE_NAME)) {
+ logResult("Device info collection complete");
+ return;
+ }
+ logResult("%s package complete: Passed %d, Failed %d, Not Executed %d",
+ pkgResult.getAppPackageName(), pkgResult.countTests(CtsTestStatus.PASS),
+ pkgResult.countTests(CtsTestStatus.FAIL),
+ pkgResult.countTests(CtsTestStatus.NOT_EXECUTED));
+ }
+
+ @Override
+ public IShardableListener clone() {
+ CtsTestLogReporter clone = new CtsTestLogReporter();
+ OptionCopier.copyOptionsNoThrow(this, clone);
+ return clone;
+ }
+}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsXmlResultReporter.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsXmlResultReporter.java
index 70310ca..ae22613 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsXmlResultReporter.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsXmlResultReporter.java
@@ -69,6 +69,9 @@
static final String PLAN_ATTR = "testPlan";
static final String STARTTIME_ATTR = "starttime";
+ @Option(name = "quiet-output", description = "Mute display of test results.")
+ private boolean mQuietOutput = false;
+
private static final String REPORT_DIR_NAME = "output-file-path";
@Option(name=REPORT_DIR_NAME, description="root file system path to directory to store xml " +
"test results and associated logs. If not specified, results will be stored at " +
@@ -83,9 +86,6 @@
@Option(name = CtsTest.CONTINUE_OPTION, description = "the test result session to continue.")
private Integer mContinueSessionId = null;
- @Option(name = "quiet-output", description = "Mute display of test results.")
- private boolean mQuietOutput = false;
-
@Option(name = "result-server", description = "Server to publish test results.")
private String mResultServer;
@@ -220,27 +220,11 @@
return new LogFileSaver(mLogDir);
}
- /**
- * {@inheritDoc}
- */
+
@Override
public void testRunStarted(String name, int numTests) {
- if (mCurrentPkgResult != null && !name.equals(mCurrentPkgResult.getAppPackageName())) {
- // display results from previous run
- logCompleteRun(mCurrentPkgResult);
- }
+ mCurrentPkgResult = mResults.getOrCreatePackage(name);
mIsDeviceInfoRun = name.equals(DeviceInfoCollector.APP_PACKAGE_NAME);
- if (mIsDeviceInfoRun) {
- logResult("Collecting device info");
- } else {
- if (mCurrentPkgResult == null || !name.equals(mCurrentPkgResult.getAppPackageName())) {
- logResult("-----------------------------------------");
- logResult("Test package %s started", name);
- logResult("-----------------------------------------");
- }
- mCurrentPkgResult = mResults.getOrCreatePackage(name);
- }
-
}
/**
@@ -266,10 +250,6 @@
public void testEnded(TestIdentifier test, Map<String, String> testMetrics) {
collectCtsResults(test, testMetrics);
mCurrentPkgResult.reportTestEnded(test);
- Test result = mCurrentPkgResult.findTest(test);
- String stack = result.getStackTrace() == null ? "" : "\n" + result.getStackTrace();
- logResult("%s#%s %s %s", test.getClassName(), test.getTestName(), result.getResult(),
- stack);
}
/**
@@ -314,10 +294,6 @@
*/
@Override
public void invocationEnded(long elapsedTime) {
- // display the results of the last completed run
- if (mCurrentPkgResult != null) {
- logCompleteRun(mCurrentPkgResult);
- }
if (mReportDir == null || mStartTime == null) {
// invocationStarted must have failed, abort
CLog.w("Unable to create XML report");
@@ -344,17 +320,6 @@
}
}
- private void logCompleteRun(TestPackageResult pkgResult) {
- if (pkgResult.getAppPackageName().equals(DeviceInfoCollector.APP_PACKAGE_NAME)) {
- logResult("Device info collection complete");
- return;
- }
- logResult("%s package complete: Passed %d, Failed %d, Not Executed %d",
- pkgResult.getAppPackageName(), pkgResult.countTests(CtsTestStatus.PASS),
- pkgResult.countTests(CtsTestStatus.FAIL),
- pkgResult.countTests(CtsTestStatus.NOT_EXECUTED));
- }
-
/**
* Creates a report file and populates it with the report data from the completed tests.
*/
@@ -382,7 +347,7 @@
} catch (IOException e) {
Log.e(LOG_TAG, "Failed to generate report data");
} finally {
- StreamUtil.closeStream(stream);
+ StreamUtil.close(stream);
}
}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/CtsTest.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/CtsTest.java
index 48f4773..2f35266 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/CtsTest.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/CtsTest.java
@@ -27,6 +27,7 @@
import com.android.tradefed.config.ConfigurationException;
import com.android.tradefed.config.Option;
import com.android.tradefed.config.Option.Importance;
+import com.android.tradefed.config.OptionCopier;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.device.TestDeviceOptions;
@@ -167,8 +168,8 @@
/** data structure for a {@link IRemoteTest} and its known tests */
class TestPackage {
private final IRemoteTest mTestForPackage;
- private final Collection<TestIdentifier> mKnownTests;
private final ITestPackageDef mPackageDef;
+ private final Collection<TestIdentifier> mKnownTests;
TestPackage(ITestPackageDef packageDef, IRemoteTest testForPackage,
Collection<TestIdentifier> knownTests) {
@@ -467,6 +468,12 @@
uninstallPrequisiteApks(uninstallPackages);
+ } catch (RuntimeException e) {
+ CLog.e(e);
+ throw e;
+ } catch (Error e) {
+ CLog.e(e);
+ throw e;
} finally {
filter.reportUnexecutedTests();
}
@@ -736,6 +743,8 @@
// don't create more shards than the number of tests we have!
for (int i = 0; i < mShards && i < allTests.size(); i++) {
CtsTest shard = new CtsTest();
+ OptionCopier.copyOptionsNoThrow(this, shard);
+ shard.mShards = 0;
shard.mRemainingTestPkgs = new LinkedList<TestPackage>();
shardQueue.add(shard);
}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/ITestPackageDef.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/ITestPackageDef.java
index 68d6743..0c3d9bc 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/ITestPackageDef.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/ITestPackageDef.java
@@ -39,10 +39,6 @@
* Creates a runnable {@link IRemoteTest} from info stored in this definition.
*
* @param testCaseDir {@link File} representing directory of test case data
- * @param className the test class to restrict this run to or <code>null</code> to run all tests
- * in package
- * @param methodName the optional test method to restrict this run to, or <code>null</code> to
- * run all tests in class/package
* @return a {@link IRemoteTest} with all necessary data populated to run the test or
* <code>null</code> if test could not be created
*/
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/InstrumentationApkTest.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/InstrumentationApkTest.java
index 809696a..01c3370 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/InstrumentationApkTest.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/InstrumentationApkTest.java
@@ -23,12 +23,12 @@
import com.android.tradefed.testtype.IBuildReceiver;
import com.android.tradefed.testtype.InstrumentationTest;
+import junit.framework.Assert;
+
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Collection;
-import junit.framework.Assert;
-
/**
* A {@link InstrumentationTest] that will install CTS apks before test execution,
* and uninstall on execution completion.
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/PrintTestRemoteTestRunner.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/PrintTestRemoteTestRunner.java
new file mode 100644
index 0000000..3d92eb3
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/PrintTestRemoteTestRunner.java
@@ -0,0 +1,277 @@
+/*
+ * 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.tradefed.testtype;
+
+import com.android.ddmlib.AdbCommandRejectedException;
+import com.android.ddmlib.IShellEnabledDevice;
+import com.android.ddmlib.Log;
+import com.android.ddmlib.ShellCommandUnresponsiveException;
+import com.android.ddmlib.TimeoutException;
+import com.android.ddmlib.testrunner.IRemoteAndroidTestRunner;
+import com.android.ddmlib.testrunner.ITestRunListener;
+import com.android.ddmlib.testrunner.InstrumentationResultParser;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.TimeUnit;
+
+public class PrintTestRemoteTestRunner implements IRemoteAndroidTestRunner {
+
+ private final String mPackageName;
+ private final String mRunnerName;
+ private IShellEnabledDevice mRemoteDevice;
+ // default to no timeout
+ private long mMaxTimeToOutputResponse = 0;
+ private TimeUnit mMaxTimeUnits = TimeUnit.MILLISECONDS;
+ private String mRunName = null;
+
+ /** map of name-value instrumentation argument pairs */
+ private Map<String, String> mArgMap;
+ private InstrumentationResultParser mParser;
+
+ private static final String LOG_TAG = "RemoteAndroidTest";
+ private static final String DEFAULT_RUNNER_NAME = "android.test.InstrumentationTestRunner";
+
+ private static final char CLASS_SEPARATOR = ',';
+ private static final char METHOD_SEPARATOR = '#';
+ private static final char RUNNER_SEPARATOR = '/';
+
+ // defined instrumentation argument names
+ private static final String CLASS_ARG_NAME = "class";
+ private static final String LOG_ARG_NAME = "log";
+ private static final String DEBUG_ARG_NAME = "debug";
+ private static final String COVERAGE_ARG_NAME = "coverage";
+ private static final String PACKAGE_ARG_NAME = "package";
+ private static final String SIZE_ARG_NAME = "size";
+
+ // This command starts a shell Java program (installed by this class)
+ // in the folder owned by the shell user. This app creates a proxy
+ // which does privileged operations such as wiping a package's user
+ // data and then starts the tests passing the proxy. This enables
+ // the tests to clear the print spooler data.
+ private static final String INSTRUMENTATION_COMMAND =
+ "chmod 755 /data/local/tmp/print-instrument && "
+ + "/data/local/tmp/print-instrument instrument -w -r %1$s %2$s";
+
+ /**
+ * Creates a remote Android test runner.
+ *
+ * @param packageName the Android application package that contains the
+ * tests to run
+ * @param runnerName the instrumentation test runner to execute. If null,
+ * will use default runner
+ * @param remoteDevice the Android device to execute tests on
+ */
+ public PrintTestRemoteTestRunner(String packageName, String runnerName,
+ IShellEnabledDevice remoteDevice) {
+
+ mPackageName = packageName;
+ mRunnerName = runnerName;
+ mRemoteDevice = remoteDevice;
+ mArgMap = new Hashtable<String, String>();
+ }
+
+ /**
+ * Alternate constructor. Uses default instrumentation runner.
+ *
+ * @param packageName the Android application package that contains the
+ * tests to run
+ * @param remoteDevice the Android device to execute tests on
+ */
+ public PrintTestRemoteTestRunner(String packageName, IShellEnabledDevice remoteDevice) {
+ this(packageName, null, remoteDevice);
+ }
+
+ @Override
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ @Override
+ public String getRunnerName() {
+ if (mRunnerName == null) {
+ return DEFAULT_RUNNER_NAME;
+ }
+ return mRunnerName;
+ }
+
+ /**
+ * Returns the complete instrumentation component path.
+ */
+ private String getRunnerPath() {
+ return getPackageName() + RUNNER_SEPARATOR + getRunnerName();
+ }
+
+ @Override
+ public void setClassName(String className) {
+ addInstrumentationArg(CLASS_ARG_NAME, className);
+ }
+
+ @Override
+ public void setClassNames(String[] classNames) {
+ StringBuilder classArgBuilder = new StringBuilder();
+
+ for (int i = 0; i < classNames.length; i++) {
+ if (i != 0) {
+ classArgBuilder.append(CLASS_SEPARATOR);
+ }
+ classArgBuilder.append(classNames[i]);
+ }
+ setClassName(classArgBuilder.toString());
+ }
+
+ @Override
+ public void setMethodName(String className, String testName) {
+ setClassName(className + METHOD_SEPARATOR + testName);
+ }
+
+ @Override
+ public void setTestPackageName(String packageName) {
+ addInstrumentationArg(PACKAGE_ARG_NAME, packageName);
+ }
+
+ @Override
+ public void addInstrumentationArg(String name, String value) {
+ if (name == null || value == null) {
+ throw new IllegalArgumentException("name or value arguments cannot be null");
+ }
+ mArgMap.put(name, value);
+ }
+
+ @Override
+ public void removeInstrumentationArg(String name) {
+ if (name == null) {
+ throw new IllegalArgumentException("name argument cannot be null");
+ }
+ mArgMap.remove(name);
+ }
+
+ @Override
+ public void addBooleanArg(String name, boolean value) {
+ addInstrumentationArg(name, Boolean.toString(value));
+ }
+
+ @Override
+ public void setLogOnly(boolean logOnly) {
+ addBooleanArg(LOG_ARG_NAME, logOnly);
+ }
+
+ @Override
+ public void setDebug(boolean debug) {
+ addBooleanArg(DEBUG_ARG_NAME, debug);
+ }
+
+ @Override
+ public void setCoverage(boolean coverage) {
+ addBooleanArg(COVERAGE_ARG_NAME, coverage);
+ }
+
+ @Override
+ public void setTestSize(TestSize size) {
+ addInstrumentationArg(SIZE_ARG_NAME, ""/*size.getRunnerValue()*/);
+ }
+
+ @Override
+ public void setMaxtimeToOutputResponse(int maxTimeToOutputResponse) {
+ setMaxTimeToOutputResponse(maxTimeToOutputResponse, TimeUnit.MILLISECONDS);
+ }
+
+ @Override
+ public void setMaxTimeToOutputResponse(long maxTimeToOutputResponse, TimeUnit maxTimeUnits) {
+ mMaxTimeToOutputResponse = maxTimeToOutputResponse;
+ mMaxTimeUnits = maxTimeUnits;
+ }
+
+ @Override
+ public void setRunName(String runName) {
+ mRunName = runName;
+ }
+
+ @Override
+ public void run(ITestRunListener... listeners) throws TimeoutException,
+ AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException {
+ run(Arrays.asList(listeners));
+ }
+
+ @Override
+ public void run(Collection<ITestRunListener> listeners) throws TimeoutException,
+ AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException {
+ final String runCaseCommandStr = String.format(INSTRUMENTATION_COMMAND,
+ getArgsCommand(), getRunnerPath());
+ Log.i(LOG_TAG,
+ String.format("Running %1$s on %2$s", runCaseCommandStr, mRemoteDevice.getName()));
+ String runName = mRunName == null ? mPackageName : mRunName;
+ mParser = new InstrumentationResultParser(runName, listeners);
+
+ try {
+ mRemoteDevice.executeShellCommand(runCaseCommandStr, mParser, mMaxTimeToOutputResponse,
+ mMaxTimeUnits);
+ } catch (IOException e) {
+ Log.w(LOG_TAG, String.format("IOException %1$s when running tests %2$s on %3$s",
+ e.toString(), getPackageName(), mRemoteDevice.getName()));
+ // rely on parser to communicate results to listeners
+ mParser.handleTestRunFailed(e.toString());
+ throw e;
+ } catch (ShellCommandUnresponsiveException e) {
+ Log.w(LOG_TAG, String.format(
+ "ShellCommandUnresponsiveException %1$s when running tests %2$s on %3$s",
+ e.toString(), getPackageName(), mRemoteDevice.getName()));
+ mParser.handleTestRunFailed(String
+ .format("Failed to receive adb shell test output within %1$d ms. "
+ + "Test may have timed out, or adb connection to device became"
+ + "unresponsive", mMaxTimeToOutputResponse));
+ throw e;
+ } catch (TimeoutException e) {
+ Log.w(LOG_TAG, String.format("TimeoutException when running tests %1$s on %2$s",
+ getPackageName(), mRemoteDevice.getName()));
+ mParser.handleTestRunFailed(e.toString());
+ throw e;
+ } catch (AdbCommandRejectedException e) {
+ Log.w(LOG_TAG, String.format(
+ "AdbCommandRejectedException %1$s when running tests %2$s on %3$s",
+ e.toString(), getPackageName(), mRemoteDevice.getName()));
+ mParser.handleTestRunFailed(e.toString());
+ throw e;
+ }
+ }
+
+ @Override
+ public void cancel() {
+ if (mParser != null) {
+ mParser.cancel();
+ }
+ }
+
+ /**
+ * Returns the full instrumentation command line syntax for the provided
+ * instrumentation arguments. Returns an empty string if no arguments were
+ * specified.
+ */
+ private String getArgsCommand() {
+ StringBuilder commandBuilder = new StringBuilder();
+ for (Entry<String, String> argPair : mArgMap.entrySet()) {
+ final String argCmd = String.format(" -e %1$s %2$s", argPair.getKey(),
+ argPair.getValue());
+ commandBuilder.append(argCmd);
+ }
+ return commandBuilder.toString();
+ }
+}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/PrintTestRunner.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/PrintTestRunner.java
new file mode 100644
index 0000000..a7a6ccc
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/PrintTestRunner.java
@@ -0,0 +1,221 @@
+/*
+ * 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.tradefed.testtype;
+
+import com.android.cts.tradefed.build.CtsBuildHelper;
+import com.android.cts.tradefed.targetprep.SettingsToggler;
+import com.android.ddmlib.testrunner.IRemoteAndroidTestRunner;
+import com.android.ddmlib.testrunner.IRemoteAndroidTestRunner.TestSize;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.testtype.IDeviceTest;
+import com.android.tradefed.testtype.IRemoteTest;
+import com.android.tradefed.util.StringEscapeUtils;
+
+import java.io.FileNotFoundException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Running the print tests requires modification of secure settings. Secure
+ * settings cannot be changed from device CTS tests since system signature
+ * permission is required. Such settings can be modified by the shell user,
+ * so a host side test driver is used for enabling these services, running
+ * the tests, and disabling the services.
+ */
+public class PrintTestRunner implements IBuildReceiver, IRemoteTest, IDeviceTest {
+
+ private static final String PRINT_TEST_AND_SERVICES_APP_NAME =
+ "CtsPrintTestCases.apk";
+
+ private static final String PRINT_TESTS_PACKAGE_NAME =
+ "com.android.cts.print";
+
+ private static final String FIRST_PRINT_SERVICE_NAME =
+ "android.print.cts.services.FirstPrintService";
+
+ private static final String SECOND_PRINT_SERVICE_NAME =
+ "android.print.cts.services.SecondPrintService";
+
+ private static final String SHELL_USER_FOLDER = "data/local/tmp";
+
+ private static final String PRINT_INSTRUMENT_JAR = "CtsPrintInstrument.jar";
+
+ private static final String PRINT_INSTRUMENT_SCRIPT = "print-instrument";
+
+ private ITestDevice mDevice;
+
+ private CtsBuildHelper mCtsBuild;
+
+ private String mPackageName;
+ private String mRunnerName = "android.test.InstrumentationTestRunner";
+ private String mTestClassName;
+ private String mTestMethodName;
+ private String mTestPackageName;
+ private int mTestTimeout = 10 * 60 * 1000; // 10 minutes
+ private String mTestSize;
+ private String mRunName = null;
+ private Map<String, String> mInstrArgMap = new HashMap<String, String>();
+
+ @Override
+ public void setBuild(IBuildInfo buildInfo) {
+ mCtsBuild = CtsBuildHelper.createBuildHelper(buildInfo);
+ }
+
+ @Override
+ public void setDevice(ITestDevice device) {
+ mDevice = device;
+ }
+
+ @Override
+ public ITestDevice getDevice() {
+ return mDevice;
+ }
+
+ public void setPackageName(String packageName) {
+ mPackageName = packageName;
+ }
+
+ public void setRunnerName(String runnerName) {
+ mRunnerName = runnerName;
+ }
+
+ public void setClassName(String testClassName) {
+ mTestClassName = testClassName;
+ }
+
+ public void setMethodName(String testMethodName) {
+ mTestMethodName = StringEscapeUtils.escapeShell(testMethodName);
+ }
+
+ public void setTestPackageName(String testPackageName) {
+ mTestPackageName = testPackageName;
+ }
+
+ public void setTestSize(String size) {
+ mTestSize = size;
+ }
+
+ public void setRunName(String runName) {
+ mRunName = runName;
+ }
+
+ @Override
+ public void run(final ITestInvocationListener listener) throws DeviceNotAvailableException {
+ installShellProgramAndScriptFiles();
+ installTestsAndServicesApk();
+ enablePrintServices();
+ doRunTests(listener);
+ disablePrintServices();
+ uninstallTestsAndServicesApk();
+ uninstallShellProgramAndScriptFiles();
+ }
+
+ private void doRunTests(ITestInvocationListener listener)
+ throws DeviceNotAvailableException {
+ if (mPackageName == null) {
+ throw new IllegalArgumentException("package name has not been set");
+ }
+ if (mDevice == null) {
+ throw new IllegalArgumentException("Device has not been set");
+ }
+
+ IRemoteAndroidTestRunner runner = new PrintTestRemoteTestRunner(mPackageName,
+ mRunnerName, mDevice.getIDevice());
+
+ if (mTestClassName != null) {
+ if (mTestMethodName != null) {
+ runner.setMethodName(mTestClassName, mTestMethodName);
+ } else {
+ runner.setClassName(mTestClassName);
+ }
+ } else if (mTestPackageName != null) {
+ runner.setTestPackageName(mTestPackageName);
+ }
+ if (mTestSize != null) {
+ runner.setTestSize(TestSize.getTestSize(mTestSize));
+ }
+ runner.setMaxTimeToOutputResponse(mTestTimeout, TimeUnit.MILLISECONDS);
+ if (mRunName != null) {
+ runner.setRunName(mRunName);
+ }
+ for (Map.Entry<String, String> argEntry : mInstrArgMap.entrySet()) {
+ runner.addInstrumentationArg(argEntry.getKey(), argEntry.getValue());
+ }
+
+ mDevice.runInstrumentationTests(runner, listener);
+ }
+
+ private void installShellProgramAndScriptFiles() throws DeviceNotAvailableException {
+ installFile(PRINT_INSTRUMENT_JAR);
+ installFile(PRINT_INSTRUMENT_SCRIPT);
+ }
+
+ private void installFile(String fileName) throws DeviceNotAvailableException {
+ try {
+ final boolean success = getDevice().pushFile(mCtsBuild.getTestApp(
+ fileName), SHELL_USER_FOLDER + "/" + fileName);
+ if (!success) {
+ throw new IllegalArgumentException("Failed to install "
+ + fileName + " on " + getDevice().getSerialNumber());
+ }
+ } catch (FileNotFoundException fnfe) {
+ throw new IllegalArgumentException("Cannot find file: " + fileName);
+ }
+ }
+
+ private void uninstallShellProgramAndScriptFiles() throws DeviceNotAvailableException {
+ getDevice().executeShellCommand("rm " + SHELL_USER_FOLDER + "/"
+ + PRINT_INSTRUMENT_JAR);
+ getDevice().executeShellCommand("rm " + SHELL_USER_FOLDER + "/"
+ + PRINT_INSTRUMENT_SCRIPT);
+ }
+
+ private void installTestsAndServicesApk() throws DeviceNotAvailableException {
+ try {
+ String installCode = getDevice().installPackage(mCtsBuild.getTestApp(
+ PRINT_TEST_AND_SERVICES_APP_NAME), true);
+ if (installCode != null) {
+ throw new IllegalArgumentException("Failed to install "
+ + PRINT_TEST_AND_SERVICES_APP_NAME + " on " + getDevice().getSerialNumber()
+ + ". Reason: " + installCode);
+ }
+ } catch (FileNotFoundException fnfe) {
+ throw new IllegalArgumentException("Cannot find file: "
+ + PRINT_TEST_AND_SERVICES_APP_NAME);
+ }
+ }
+
+ private void uninstallTestsAndServicesApk() throws DeviceNotAvailableException {
+ getDevice().uninstallPackage(PRINT_TESTS_PACKAGE_NAME);
+ }
+
+ private void enablePrintServices() throws DeviceNotAvailableException {
+ String enabledServicesValue = PRINT_TESTS_PACKAGE_NAME + "/" + FIRST_PRINT_SERVICE_NAME
+ + ":" + PRINT_TESTS_PACKAGE_NAME + "/" + SECOND_PRINT_SERVICE_NAME;
+ SettingsToggler.setSecureString(getDevice(), "enabled_print_services",
+ enabledServicesValue);
+ }
+
+ private void disablePrintServices() throws DeviceNotAvailableException {
+ SettingsToggler.setSecureString(getDevice(), "enabled_print_services", "");
+ }
+}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestFilter.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestFilter.java
index f1a0485..4d1b3e2 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestFilter.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestFilter.java
@@ -25,6 +25,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.regex.Pattern;
/**
* Filter for {@link TestIdentifier}s.
@@ -38,7 +39,7 @@
private final Set<TestIdentifier> mIncludedTests;
private String mIncludedClass = null;
- private String mIncludedMethod = null;
+ private Pattern mIncludedMethod = null;
/**
* Creates a {@link TestFilter}
@@ -124,7 +125,9 @@
*/
public void setTestInclusion(String className, String method) {
mIncludedClass = className;
- mIncludedMethod = method;
+ if (method != null) {
+ mIncludedMethod = Pattern.compile(method);
+ }
}
/**
@@ -140,7 +143,7 @@
// skip
continue;
}
- if (mIncludedMethod != null && !test.getTestName().equals(mIncludedMethod)) {
+ if (mIncludedMethod != null && !mIncludedMethod.matcher(test.getTestName()).matches()) {
// skip
continue;
}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageDef.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageDef.java
index 8ab5d18..994da0b 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageDef.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageDef.java
@@ -47,9 +47,11 @@
public static final String WRAPPED_NATIVE_TEST = "wrappednative";
public static final String VM_HOST_TEST = "vmHostTest";
public static final String ACCESSIBILITY_TEST =
- "com.android.cts.tradefed.testtype.AccessibilityTestRunner";
+ "com.android.cts.tradefed.testtype.AccessibilityTestRunner";
public static final String ACCESSIBILITY_SERVICE_TEST =
- "com.android.cts.tradefed.testtype.AccessibilityServiceTestRunner";
+ "com.android.cts.tradefed.testtype.AccessibilityServiceTestRunner";
+ public static final String PRINT_TEST =
+ "com.android.cts.tradefed.testtype.PrintTestRunner";
public static final String DISPLAY_TEST =
"com.android.cts.tradefed.testtype.DisplayTestRunner";
public static final String UIAUTOMATOR_TEST = "uiAutomator";
@@ -61,7 +63,6 @@
private String mAppNameSpace = null;
private String mName = null;
private String mRunner = null;
- private boolean mIsVMHostTest = false;
private String mTestType = null;
private String mJarPath = null;
private boolean mIsSignatureTest = false;
@@ -230,6 +231,9 @@
} else if (ACCESSIBILITY_TEST.equals(mTestType)) {
AccessibilityTestRunner test = new AccessibilityTestRunner();
return setInstrumentationTest(test, testCaseDir);
+ } else if (PRINT_TEST.equals(mTestType)) {
+ PrintTestRunner test = new PrintTestRunner();
+ return setPrintTest(test, testCaseDir);
} else if (ACCESSIBILITY_SERVICE_TEST.equals(mTestType)) {
AccessibilityServiceTestRunner test = new AccessibilityServiceTestRunner();
return setInstrumentationTest(test, testCaseDir);
@@ -270,6 +274,18 @@
}
}
+ private PrintTestRunner setPrintTest(PrintTestRunner printTest,
+ File testCaseDir) {
+ printTest.setRunName(getUri());
+ printTest.setPackageName(mAppNameSpace);
+ printTest.setRunnerName(mRunner);
+ printTest.setTestPackageName(mTestPackageName);
+ printTest.setClassName(mClassName);
+ printTest.setMethodName(mMethodName);
+ mDigest = generateDigest(testCaseDir, String.format("%s.apk", mName));
+ return printTest;
+ }
+
/**
* Populates given {@link InstrumentationApkTest} with data from the package xml.
*
diff --git a/tools/utils/CollectAllTests.java b/tools/utils/CollectAllTests.java
index fe13e00..367fb93 100644
--- a/tools/utils/CollectAllTests.java
+++ b/tools/utils/CollectAllTests.java
@@ -14,27 +14,23 @@
* limitations under the License.
*/
+import org.junit.runner.RunWith;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
-import vogar.Expectation;
import vogar.ExpectationStore;
-import vogar.ModeId;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
-import java.io.FilenameFilter;
import java.io.IOException;
import java.lang.annotation.Annotation;
-import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
@@ -47,11 +43,7 @@
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
-import junit.framework.Test;
import junit.framework.TestCase;
-import junit.framework.TestResult;
-import junit.textui.ResultPrinter;
-import junit.textui.TestRunner;
public class CollectAllTests extends DescriptionGenerator {
@@ -203,18 +195,19 @@
Class<?> klass = Class.forName(className,
false,
CollectAllTests.class.getClassLoader());
- if (!TestCase.class.isAssignableFrom(klass)) {
+ final int modifiers = klass.getModifiers();
+ if (Modifier.isAbstract(modifiers) || !Modifier.isPublic(modifiers)) {
continue;
}
- if (Modifier.isAbstract(klass.getModifiers())) {
+
+ final boolean isJunit4Class = isJunit4Class(klass);
+ if (!isJunit4Class && !isJunit3Test(klass)) {
continue;
}
- if (!Modifier.isPublic(klass.getModifiers())) {
- continue;
- }
+
try {
klass.getConstructor(new Class<?>[] { String.class } );
- addToTests(expectations, testCases, klass.asSubclass(TestCase.class));
+ addToTests(expectations, testCases, klass);
continue;
} catch (NoSuchMethodException e) {
} catch (SecurityException e) {
@@ -222,9 +215,10 @@
+ className);
e.printStackTrace();
}
+
try {
klass.getConstructor(new Class<?>[0]);
- addToTests(expectations, testCases, klass.asSubclass(TestCase.class));
+ addToTests(expectations, testCases, klass);
continue;
} catch (NoSuchMethodException e) {
} catch (SecurityException e) {
@@ -276,7 +270,7 @@
BufferedReader reader = new BufferedReader(new FileReader(makeFileName));
String line;
- while ((line =reader.readLine())!=null) {
+ while ((line = reader.readLine())!=null) {
if (line.startsWith(TEST_TYPE)) {
if (line.indexOf(ATTRIBUTE_VM_HOST_TEST) >= 0) {
type = VM_HOST_TEST;
@@ -314,32 +308,22 @@
}
}
- private static String getKnownFailure(final Class<? extends TestCase> testClass,
+ private static String getKnownFailure(final Class<?> testClass,
final String testName) {
return getAnnotation(testClass, testName, KNOWN_FAILURE);
}
- private static boolean isKnownFailure(final Class<? extends TestCase> testClass,
+ private static boolean isKnownFailure(final Class<?> testClass,
final String testName) {
return getAnnotation(testClass, testName, KNOWN_FAILURE) != null;
}
- private static boolean isBrokenTest(final Class<? extends TestCase> testClass,
- final String testName) {
- return getAnnotation(testClass, testName, BROKEN_TEST) != null;
- }
-
- private static boolean isSuppressed(final Class<? extends TestCase> testClass,
+ private static boolean isSuppressed(final Class<?> testClass,
final String testName) {
return getAnnotation(testClass, testName, SUPPRESSED_TEST) != null;
}
- private static boolean hasSideEffects(final Class<? extends TestCase> testClass,
- final String testName) {
- return getAnnotation(testClass, testName, SIDE_EFFECT) != null;
- }
-
- private static String getAnnotation(final Class<? extends TestCase> testClass,
+ private static String getAnnotation(final Class<?> testClass,
final String testName, final String annotationName) {
try {
Method testMethod = testClass.getMethod(testName, (Class[])null);
@@ -373,38 +357,42 @@
private static void addToTests(ExpectationStore[] expectations,
Map<String,TestClass> testCases,
- Class<? extends TestCase> test) {
- Class testClass = test;
+ Class<?> testClass) {
Set<String> testNames = new HashSet<String>();
- while (TestCase.class.isAssignableFrom(testClass)) {
- Method[] testMethods = testClass.getDeclaredMethods();
- for (Method testMethod : testMethods) {
- String testName = testMethod.getName();
- if (testNames.contains(testName)) {
- continue;
- }
- if (!testName.startsWith("test")) {
- continue;
- }
- if (testMethod.getParameterTypes().length != 0) {
- continue;
- }
- if (!testMethod.getReturnType().equals(Void.TYPE)) {
- continue;
- }
- if (!Modifier.isPublic(testMethod.getModifiers())) {
- continue;
- }
- testNames.add(testName);
- addToTests(expectations, testCases, test, testName);
+
+ boolean isJunit3Test = isJunit3Test(testClass);
+
+ Method[] testMethods = testClass.getMethods();
+ for (Method testMethod : testMethods) {
+ String testName = testMethod.getName();
+ if (testNames.contains(testName)) {
+ continue;
}
- testClass = testClass.getSuperclass();
+
+ /* Make sure the method has the right signature. */
+ if (!Modifier.isPublic(testMethod.getModifiers())) {
+ continue;
+ }
+ if (!testMethod.getReturnType().equals(Void.TYPE)) {
+ continue;
+ }
+ if (testMethod.getParameterTypes().length != 0) {
+ continue;
+ }
+
+ if ((isJunit3Test && !testName.startsWith("test"))
+ || (!isJunit3Test && !isJunit4TestMethod(testMethod))) {
+ continue;
+ }
+
+ testNames.add(testName);
+ addToTests(expectations, testCases, testClass, testName);
}
}
private static void addToTests(ExpectationStore[] expectations,
Map<String,TestClass> testCases,
- Class<? extends TestCase> test,
+ Class<?> test,
String testName) {
String testClassName = test.getName();
@@ -413,15 +401,9 @@
if (isKnownFailure(test, testName)) {
System.out.println("ignoring known failure: " + test + "#" + testName);
return;
- } else if (isBrokenTest(test, testName)) {
- System.out.println("ignoring broken test: " + test + "#" + testName);
- return;
} else if (isSuppressed(test, testName)) {
System.out.println("ignoring suppressed test: " + test + "#" + testName);
return;
- } else if (hasSideEffects(test, testName)) {
- System.out.println("ignoring test with side effects: " + test + "#" + testName);
- return;
} else if (VogarUtils.isVogarKnownFailure(expectations,
testClassName,
testName)) {
@@ -430,7 +412,7 @@
return;
}
- TestClass testClass = null;
+ TestClass testClass;
if (testCases.containsKey(testClassName)) {
testClass = testCases.get(testClassName);
} else {
@@ -441,6 +423,40 @@
testClass.mCases.add(new TestMethod(testName, "", "", knownFailure, false, false));
}
+ private static boolean isJunit3Test(Class<?> klass) {
+ return TestCase.class.isAssignableFrom(klass);
+ }
+
+ private static boolean isJunit4Class(Class<?> klass) {
+ for (Annotation a : klass.getAnnotations()) {
+ if (RunWith.class.isAssignableFrom(a.annotationType())) {
+ // @RunWith is currently not supported for CTS tests because tradefed cannot handle
+ // a single test spawning other tests with different names.
+ System.out.println("Skipping test class " + klass.getName()
+ + ": JUnit4 @RunWith is not supported");
+ return false;
+ }
+ }
+
+ for (Method m : klass.getMethods()) {
+ if (isJunit4TestMethod(m)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static boolean isJunit4TestMethod(Method method) {
+ for (Annotation a : method.getAnnotations()) {
+ if (org.junit.Test.class.isAssignableFrom(a.annotationType())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
/**
* Determines if a given string is a valid java package name
* @param javaPackageName
diff --git a/tools/utils/DescriptionGenerator.java b/tools/utils/DescriptionGenerator.java
index 0731b49..607d2e5 100644
--- a/tools/utils/DescriptionGenerator.java
+++ b/tools/utils/DescriptionGenerator.java
@@ -66,8 +66,6 @@
public class DescriptionGenerator extends Doclet {
static final String HOST_CONTROLLER = "dalvik.annotation.HostController";
static final String KNOWN_FAILURE = "dalvik.annotation.KnownFailure";
- static final String BROKEN_TEST = "dalvik.annotation.BrokenTest";
- static final String SIDE_EFFECT = "dalvik.annotation.SideEffect";
static final String SUPPRESSED_TEST = "android.test.suitebuilder.annotation.Suppress";
static final String CTS_EXPECTATION_DIR = "cts/tests/expectations";
@@ -545,8 +543,6 @@
controller = getAnnotationDescription(cAnnot);
} else if (atype.toString().equals(KNOWN_FAILURE)) {
knownFailure = getAnnotationDescription(cAnnot);
- } else if (atype.toString().equals(BROKEN_TEST)) {
- isBroken = true;
} else if (atype.toString().equals(SUPPRESSED_TEST)) {
isSuppressed = true;
}
diff --git a/tools/utils/buildCts.py b/tools/utils/buildCts.py
index cb70c54..0f74a81 100755
--- a/tools/utils/buildCts.py
+++ b/tools/utils/buildCts.py
@@ -72,8 +72,6 @@
self.test_repository = os.path.join(self.out_dir, 'repository/testcases')
self.plan_repository = os.path.join(self.out_dir, 'repository/plans')
-
- #dirty hack to copy over prepopulated CTS test plans, stable vs flaky, for autoCTS
self.definedplans_repository = os.path.join(self.android_root, 'cts/tests/plans')
def GenerateTestDescriptions(self):
@@ -160,14 +158,76 @@
plan.Include('android\.telephony')
plan.Include('android\.nativemedia.*')
plan.Include('com\.android\.cts\..*')#TODO(stuartscott): Should PDK have all these?
- #TODO(stuartscott): Maybe move away from com.android.* to android.* - less typing
self.__WritePlan(plan, 'PDK')
- #dirty hack to copy over pre-populated CTS plans - flaky vs stable - to streamline autoCTS
- shutil.copyfile(os.path.join(self.definedplans_repository, 'CTS-flaky.xml'),
- os.path.join(self.plan_repository, 'CTS-flaky.xml'))
- shutil.copyfile(os.path.join(self.definedplans_repository, 'CTS-stable.xml'),
- os.path.join(self.plan_repository, 'CTS-stable.xml'))
+ flaky_tests = BuildCtsFlakyTestList()
+
+ # CTS Stable plan
+ plan = tools.TestPlan(packages)
+ plan.Exclude(r'android\.display')
+ for package, test_list in flaky_tests.iteritems():
+ plan.ExcludeTests(package, test_list)
+ self.__WritePlan(plan, 'CTS-stable')
+
+ # CTS Flaky plan - inversion of CTS Stable
+ plan = tools.TestPlan(packages)
+ plan.Exclude('.*')
+ plan.Include(r'android\.display')
+ for package, test_list in flaky_tests.iteritems():
+ plan.Include(package)
+ plan.IncludeTests(package, test_list)
+ self.__WritePlan(plan, 'CTS-flaky')
+
+
+def BuildCtsFlakyTestList():
+ """ Construct a defaultdict that maps package name to a list of tests
+ that are known to be flaky. """
+ return {
+ 'android.app' : [
+ 'cts.ActivityManagerTest#testIsRunningInTestHarness',
+ 'cts.AlertDialogTest#testAlertDialogCancelable',
+ 'cts.ExpandableListActivityTest#testCallback',],
+ 'android.hardware' : [
+ 'camera2.cts.CameraDeviceTest#testCameraDeviceRepeatingRequest',
+ 'camera2.cts.ImageReaderTest#testImageReaderFromCameraJpeg',
+ 'cts.CameraTest#testImmediateZoom',
+ 'cts.CameraTest#testPreviewCallback',
+ 'cts.CameraTest#testSmoothZoom',
+ 'cts.CameraTest#testVideoSnapshot',
+ 'cts.CameraGLTest#testCameraToSurfaceTextureMetadata',
+ 'cts.CameraGLTest#testSetPreviewTextureBothCallbacks',
+ 'cts.CameraGLTest#testSetPreviewTexturePreviewCallback',],
+ 'android.media' : [
+ 'cts.DecoderTest#testCodecResetsH264WithSurface',
+ 'cts.StreamingMediaPlayerTest#testHLS',],
+ 'android.mediastress' : [
+ 'cts.NativeMediaTest#test480pPlay',],
+ 'android.net' : [
+ 'cts.ConnectivityManagerTest#testStartUsingNetworkFeature_enableHipri',
+ 'cts.DnsTest#testDnsWorks',
+ 'cts.SSLCertificateSocketFactoryTest#testCreateSocket',
+ 'cts.SSLCertificateSocketFactoryTest#test_createSocket_bind',
+ 'cts.SSLCertificateSocketFactoryTest#test_createSocket_simple',
+ 'cts.SSLCertificateSocketFactoryTest#test_createSocket_wrapping',
+ 'cts.TrafficStatsTest#testTrafficStatsForLocalhost',
+ 'wifi.cts.NsdManagerTest#testAndroidTestCaseSetupProperly',
+ 'wifi.cts.ScanResultTest#testAndroidTestCaseSetupProperly',
+ 'wifi.cts.ScanResultTest#testScanResultTimeStamp',],
+ 'android.security' : [
+ 'cts.BannedFilesTest#testNoSu',
+ 'cts.BannedFilesTest#testNoSuInPath',
+ 'cts.ListeningPortsTest#testNoRemotelyAccessibleListeningUdp6Ports',
+ 'cts.ListeningPortsTest#testNoRemotelyAccessibleListeningUdpPorts',
+ 'cts.PackageSignatureTest#testPackageSignatures',],
+ 'android.webkit' : [
+ 'cts.WebViewClientTest#testDoUpdateVisitedHistory',
+ 'cts.WebViewClientTest#testLoadPage',
+ 'cts.WebViewClientTest#testOnFormResubmission',
+ 'cts.WebViewClientTest#testOnReceivedError',
+ 'cts.WebViewClientTest#testOnReceivedHttpAuthRequest',
+ 'cts.WebViewClientTest#testOnScaleChanged',
+ 'cts.WebViewClientTest#testOnUnhandledKeyEvent',
+ 'cts.WebViewTest#testSetInitialScale',]}
def LogGenerateDescription(name):
print 'Generating test description for package %s' % name
diff --git a/tools/utils/cts/tools.py b/tools/utils/cts/tools.py
index 6cd1b95..0a4c97c 100644
--- a/tools/utils/cts/tools.py
+++ b/tools/utils/cts/tools.py
@@ -120,6 +120,45 @@
self.all_packages = all_packages
self.map = None
+ self.includedTestsMap = {}
+ self.excludedTestsMap = {}
+
+
+ def IncludeTests(self, package, test_list):
+ """Include only specific tests in this plan.
+
+ package The package that contains the tests. e.g. android.mypackage
+ This package should must be included via Include.
+ test_list A list of tests with methods to be included. e.g.
+ ['TestClass#testA', 'TestClass#testB']
+ """
+ packaged_test_list = []
+ for test in test_list:
+ packaged_test_list.append(package + '.' + test)
+
+ if package in self.includedTestsMap:
+ self.includedTestsMap[package] += packaged_test_list
+ else:
+ self.includedTestsMap[package] = packaged_test_list
+
+
+ def ExcludeTests(self, package, test_list):
+ """Exclude specific tests from this plan.
+
+ package The package that contains the tests. e.g. android.mypackage
+ This package should must be included via Include.
+ test_list A list of tests with methods to be excluded. e.g.
+ ['TestClass#testA', 'TestClass#textB']
+ """
+ packaged_test_list = []
+ for test in test_list:
+ packaged_test_list.append(package + '.' + test)
+ if package in self.excludedTestsMap:
+ self.excludedTestsMap[package] += packaged_test_list
+ else:
+ self.excludedTestsMap[package] = packaged_test_list
+
+
def Exclude(self, pattern):
"""Exclude all packages matching the given regular expression from the plan.
@@ -167,6 +206,10 @@
if self.map[package]:
entry = doc.createElement('Entry')
entry.setAttribute('uri', package)
+ if package in self.excludedTestsMap:
+ entry.setAttribute('exclude', ';'.join(self.excludedTestsMap[package]))
+ if package in self.includedTestsMap:
+ entry.setAttribute('include', ';'.join(self.includedTestsMap[package]))
plan.appendChild(entry)
stream = open(file_name, 'w')
doc.writexml(stream, addindent=' ', newl='\n', encoding='UTF-8')
diff --git a/tools/utils/monsoon.py b/tools/utils/monsoon.py
new file mode 100755
index 0000000..f3d63c5
--- /dev/null
+++ b/tools/utils/monsoon.py
@@ -0,0 +1,422 @@
+#!/usr/bin/python2.6
+
+# 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.
+
+"""Interface for a USB-connected Monsoon power meter
+(http://msoon.com/LabEquipment/PowerMonitor/).
+This file requires gflags, which requires setuptools.
+To install setuptools: sudo apt-get install python-setuptools
+To install gflags, see http://code.google.com/p/python-gflags/
+To install pyserial, see http://pyserial.sourceforge.net/
+
+Example usages:
+ Set the voltage of the device 7536 to 4.0V
+ python2.6 monsoon.py --voltage=4.0 --serialno 7536
+
+ Get 5000hz data from device number 7536, with unlimited number of samples
+ python2.6 monsoon.py --samples -1 --hz 5000 --serialno 7536
+
+ Get 200Hz data for 5 seconds (1000 events) from default device
+ python2.6 monsoon.py --samples 100 --hz 200
+
+ Get unlimited 200Hz data from device attached at /dev/ttyACM0
+ python2.6 monsoon.py --samples -1 --hz 200 --device /dev/ttyACM0
+"""
+
+import fcntl
+import os
+import select
+import signal
+import stat
+import struct
+import sys
+import time
+import collections
+
+import gflags as flags # http://code.google.com/p/python-gflags/
+
+import serial # http://pyserial.sourceforge.net/
+
+FLAGS = flags.FLAGS
+
+class Monsoon:
+ """
+ Provides a simple class to use the power meter, e.g.
+ mon = monsoon.Monsoon()
+ mon.SetVoltage(3.7)
+ mon.StartDataCollection()
+ mydata = []
+ while len(mydata) < 1000:
+ mydata.extend(mon.CollectData())
+ mon.StopDataCollection()
+ """
+
+ def __init__(self, device=None, serialno=None, wait=1):
+ """
+ Establish a connection to a Monsoon.
+ By default, opens the first available port, waiting if none are ready.
+ A particular port can be specified with "device", or a particular Monsoon
+ can be specified with "serialno" (using the number printed on its back).
+ With wait=0, IOError is thrown if a device is not immediately available.
+ """
+
+ self._coarse_ref = self._fine_ref = self._coarse_zero = self._fine_zero = 0
+ self._coarse_scale = self._fine_scale = 0
+ self._last_seq = 0
+ self.start_voltage = 0
+
+ if device:
+ self.ser = serial.Serial(device, timeout=1)
+ return
+
+ while True: # try all /dev/ttyACM* until we find one we can use
+ for dev in os.listdir("/dev"):
+ if not dev.startswith("ttyACM"): continue
+ tmpname = "/tmp/monsoon.%s.%s" % (os.uname()[0], dev)
+ self._tempfile = open(tmpname, "w")
+ try:
+ os.chmod(tmpname, 0666)
+ except OSError:
+ pass
+ try: # use a lockfile to ensure exclusive access
+ fcntl.lockf(self._tempfile, fcntl.LOCK_EX | fcntl.LOCK_NB)
+ except IOError as e:
+ print >>sys.stderr, "device %s is in use" % dev
+ continue
+
+ try: # try to open the device
+ self.ser = serial.Serial("/dev/%s" % dev, timeout=1)
+ self.StopDataCollection() # just in case
+ self._FlushInput() # discard stale input
+ status = self.GetStatus()
+ except Exception as e:
+ print >>sys.stderr, "error opening device %s: %s" % (dev, e)
+ continue
+
+ if not status:
+ print >>sys.stderr, "no response from device %s" % dev
+ elif serialno and status["serialNumber"] != serialno:
+ print >>sys.stderr, ("Note: another device serial #%d seen on %s" %
+ (status["serialNumber"], dev))
+ else:
+ self.start_voltage = status["voltage1"]
+ return
+
+ self._tempfile = None
+ if not wait: raise IOError("No device found")
+ print >>sys.stderr, "waiting for device..."
+ time.sleep(1)
+
+
+ def GetStatus(self):
+ """ Requests and waits for status. Returns status dictionary. """
+
+ # status packet format
+ STATUS_FORMAT = ">BBBhhhHhhhHBBBxBbHBHHHHBbbHHBBBbbbbbbbbbBH"
+ STATUS_FIELDS = [
+ "packetType", "firmwareVersion", "protocolVersion",
+ "mainFineCurrent", "usbFineCurrent", "auxFineCurrent", "voltage1",
+ "mainCoarseCurrent", "usbCoarseCurrent", "auxCoarseCurrent", "voltage2",
+ "outputVoltageSetting", "temperature", "status", "leds",
+ "mainFineResistor", "serialNumber", "sampleRate",
+ "dacCalLow", "dacCalHigh",
+ "powerUpCurrentLimit", "runTimeCurrentLimit", "powerUpTime",
+ "usbFineResistor", "auxFineResistor",
+ "initialUsbVoltage", "initialAuxVoltage",
+ "hardwareRevision", "temperatureLimit", "usbPassthroughMode",
+ "mainCoarseResistor", "usbCoarseResistor", "auxCoarseResistor",
+ "defMainFineResistor", "defUsbFineResistor", "defAuxFineResistor",
+ "defMainCoarseResistor", "defUsbCoarseResistor", "defAuxCoarseResistor",
+ "eventCode", "eventData", ]
+
+ self._SendStruct("BBB", 0x01, 0x00, 0x00)
+ while True: # Keep reading, discarding non-status packets
+ bytes = self._ReadPacket()
+ if not bytes: return None
+ if len(bytes) != struct.calcsize(STATUS_FORMAT) or bytes[0] != "\x10":
+ print >>sys.stderr, "wanted status, dropped type=0x%02x, len=%d" % (
+ ord(bytes[0]), len(bytes))
+ continue
+
+ status = dict(zip(STATUS_FIELDS, struct.unpack(STATUS_FORMAT, bytes)))
+ assert status["packetType"] == 0x10
+ for k in status.keys():
+ if k.endswith("VoltageSetting"):
+ status[k] = 2.0 + status[k] * 0.01
+ elif k.endswith("FineCurrent"):
+ pass # needs calibration data
+ elif k.endswith("CoarseCurrent"):
+ pass # needs calibration data
+ elif k.startswith("voltage") or k.endswith("Voltage"):
+ status[k] = status[k] * 0.000125
+ elif k.endswith("Resistor"):
+ status[k] = 0.05 + status[k] * 0.0001
+ if k.startswith("aux") or k.startswith("defAux"): status[k] += 0.05
+ elif k.endswith("CurrentLimit"):
+ status[k] = 8 * (1023 - status[k]) / 1023.0
+ return status
+
+ def RampVoltage(self, start, end):
+ v = start
+ if v < 3.0: v = 3.0 # protocol doesn't support lower than this
+ while (v < end):
+ self.SetVoltage(v)
+ v += .1
+ time.sleep(.1)
+ self.SetVoltage(end)
+
+ def SetVoltage(self, v):
+ """ Set the output voltage, 0 to disable. """
+ if v == 0:
+ self._SendStruct("BBB", 0x01, 0x01, 0x00)
+ else:
+ self._SendStruct("BBB", 0x01, 0x01, int((v - 2.0) * 100))
+
+
+ def SetMaxCurrent(self, i):
+ """Set the max output current."""
+ assert i >= 0 and i <= 8
+
+ val = 1023 - int((i/8)*1023)
+ self._SendStruct("BBB", 0x01, 0x0a, val & 0xff)
+ self._SendStruct("BBB", 0x01, 0x0b, val >> 8)
+
+ def SetUsbPassthrough(self, val):
+ """ Set the USB passthrough mode: 0 = off, 1 = on, 2 = auto. """
+ self._SendStruct("BBB", 0x01, 0x10, val)
+
+
+ def StartDataCollection(self):
+ """ Tell the device to start collecting and sending measurement data. """
+ self._SendStruct("BBB", 0x01, 0x1b, 0x01) # Mystery command
+ self._SendStruct("BBBBBBB", 0x02, 0xff, 0xff, 0xff, 0xff, 0x03, 0xe8)
+
+
+ def StopDataCollection(self):
+ """ Tell the device to stop collecting measurement data. """
+ self._SendStruct("BB", 0x03, 0x00) # stop
+
+
+ def CollectData(self):
+ """ Return some current samples. Call StartDataCollection() first. """
+ while True: # loop until we get data or a timeout
+ bytes = self._ReadPacket()
+ if not bytes: return None
+ if len(bytes) < 4 + 8 + 1 or bytes[0] < "\x20" or bytes[0] > "\x2F":
+ print >>sys.stderr, "wanted data, dropped type=0x%02x, len=%d" % (
+ ord(bytes[0]), len(bytes))
+ continue
+
+ seq, type, x, y = struct.unpack("BBBB", bytes[:4])
+ data = [struct.unpack(">hhhh", bytes[x:x+8])
+ for x in range(4, len(bytes) - 8, 8)]
+
+ if self._last_seq and seq & 0xF != (self._last_seq + 1) & 0xF:
+ print >>sys.stderr, "data sequence skipped, lost packet?"
+ self._last_seq = seq
+
+ if type == 0:
+ if not self._coarse_scale or not self._fine_scale:
+ print >>sys.stderr, "waiting for calibration, dropped data packet"
+ continue
+
+ out = []
+ for main, usb, aux, voltage in data:
+ if main & 1:
+ out.append(((main & ~1) - self._coarse_zero) * self._coarse_scale)
+ else:
+ out.append((main - self._fine_zero) * self._fine_scale)
+ return out
+
+ elif type == 1:
+ self._fine_zero = data[0][0]
+ self._coarse_zero = data[1][0]
+ # print >>sys.stderr, "zero calibration: fine 0x%04x, coarse 0x%04x" % (
+ # self._fine_zero, self._coarse_zero)
+
+ elif type == 2:
+ self._fine_ref = data[0][0]
+ self._coarse_ref = data[1][0]
+ # print >>sys.stderr, "ref calibration: fine 0x%04x, coarse 0x%04x" % (
+ # self._fine_ref, self._coarse_ref)
+
+ else:
+ print >>sys.stderr, "discarding data packet type=0x%02x" % type
+ continue
+
+ if self._coarse_ref != self._coarse_zero:
+ self._coarse_scale = 2.88 / (self._coarse_ref - self._coarse_zero)
+ if self._fine_ref != self._fine_zero:
+ self._fine_scale = 0.0332 / (self._fine_ref - self._fine_zero)
+
+
+ def _SendStruct(self, fmt, *args):
+ """ Pack a struct (without length or checksum) and send it. """
+ data = struct.pack(fmt, *args)
+ data_len = len(data) + 1
+ checksum = (data_len + sum(struct.unpack("B" * len(data), data))) % 256
+ out = struct.pack("B", data_len) + data + struct.pack("B", checksum)
+ self.ser.write(out)
+
+
+ def _ReadPacket(self):
+ """ Read a single data record as a string (without length or checksum). """
+ len_char = self.ser.read(1)
+ if not len_char:
+ print >>sys.stderr, "timeout reading from serial port"
+ return None
+
+ data_len = struct.unpack("B", len_char)
+ data_len = ord(len_char)
+ if not data_len: return ""
+
+ result = self.ser.read(data_len)
+ if len(result) != data_len: return None
+ body = result[:-1]
+ checksum = (data_len + sum(struct.unpack("B" * len(body), body))) % 256
+ if result[-1] != struct.pack("B", checksum):
+ print >>sys.stderr, "invalid checksum from serial port"
+ return None
+ return result[:-1]
+
+ def _FlushInput(self):
+ """ Flush all read data until no more available. """
+ self.ser.flush()
+ flushed = 0
+ while True:
+ ready_r, ready_w, ready_x = select.select([self.ser], [], [self.ser], 0)
+ if len(ready_x) > 0:
+ print >>sys.stderr, "exception from serial port"
+ return None
+ elif len(ready_r) > 0:
+ flushed += 1
+ self.ser.read(1) # This may cause underlying buffering.
+ self.ser.flush() # Flush the underlying buffer too.
+ else:
+ break
+ if flushed > 0:
+ print >>sys.stderr, "dropped >%d bytes" % flushed
+
+def main(argv):
+ """ Simple command-line interface for Monsoon."""
+ useful_flags = ["voltage", "status", "usbpassthrough", "samples", "current"]
+ if not [f for f in useful_flags if FLAGS.get(f, None) is not None]:
+ print __doc__.strip()
+ print FLAGS.MainModuleHelp()
+ return
+
+ if FLAGS.avg and FLAGS.avg < 0:
+ print "--avg must be greater than 0"
+ return
+
+ mon = Monsoon(device=FLAGS.device, serialno=FLAGS.serialno)
+
+ if FLAGS.voltage is not None:
+ if FLAGS.ramp is not None:
+ mon.RampVoltage(mon.start_voltage, FLAGS.voltage)
+ else:
+ mon.SetVoltage(FLAGS.voltage)
+
+ if FLAGS.current is not None:
+ mon.SetMaxCurrent(FLAGS.current)
+
+ if FLAGS.status:
+ items = sorted(mon.GetStatus().items())
+ print "\n".join(["%s: %s" % item for item in items])
+
+ if FLAGS.usbpassthrough:
+ if FLAGS.usbpassthrough == 'off':
+ mon.SetUsbPassthrough(0)
+ elif FLAGS.usbpassthrough == 'on':
+ mon.SetUsbPassthrough(1)
+ elif FLAGS.usbpassthrough == 'auto':
+ mon.SetUsbPassthrough(2)
+ else:
+ sys.exit('bad passthrough flag: %s' % FLAGS.usbpassthrough)
+
+ if FLAGS.samples:
+ # Make sure state is normal
+ mon.StopDataCollection()
+ status = mon.GetStatus()
+ native_hz = status["sampleRate"] * 1000
+
+ # Collect and average samples as specified
+ mon.StartDataCollection()
+
+ # In case FLAGS.hz doesn't divide native_hz exactly, use this invariant:
+ # 'offset' = (consumed samples) * FLAGS.hz - (emitted samples) * native_hz
+ # This is the error accumulator in a variation of Bresenham's algorithm.
+ emitted = offset = 0
+ collected = []
+ history_deque = collections.deque() # past n samples for rolling average
+
+ try:
+ last_flush = time.time()
+ while emitted < FLAGS.samples or FLAGS.samples == -1:
+ # The number of raw samples to consume before emitting the next output
+ need = (native_hz - offset + FLAGS.hz - 1) / FLAGS.hz
+ if need > len(collected): # still need more input samples
+ samples = mon.CollectData()
+ if not samples: break
+ collected.extend(samples)
+ else:
+ # Have enough data, generate output samples.
+ # Adjust for consuming 'need' input samples.
+ offset += need * FLAGS.hz
+ while offset >= native_hz: # maybe multiple, if FLAGS.hz > native_hz
+ this_sample = sum(collected[:need]) / need
+
+ if FLAGS.timestamp: print int(time.time()),
+
+ if FLAGS.avg:
+ history_deque.appendleft(this_sample)
+ if len(history_deque) > FLAGS.avg: history_deque.pop()
+ print "%f %f" % (this_sample,
+ sum(history_deque) / len(history_deque))
+ else:
+ print "%f" % this_sample
+ sys.stdout.flush()
+
+ offset -= native_hz
+ emitted += 1 # adjust for emitting 1 output sample
+ collected = collected[need:]
+ now = time.time()
+ if now - last_flush >= 0.99: # flush every second
+ sys.stdout.flush()
+ last_flush = now
+ except KeyboardInterrupt:
+ print >>sys.stderr, "interrupted"
+
+ mon.StopDataCollection()
+
+
+if __name__ == '__main__':
+ # Define flags here to avoid conflicts with people who use us as a library
+ flags.DEFINE_boolean("status", None, "Print power meter status")
+ flags.DEFINE_integer("avg", None,
+ "Also report average over last n data points")
+ flags.DEFINE_float("voltage", None, "Set output voltage (0 for off)")
+ flags.DEFINE_float("current", None, "Set max output current")
+ flags.DEFINE_string("usbpassthrough", None, "USB control (on, off, auto)")
+ flags.DEFINE_integer("samples", None, "Collect and print this many samples")
+ flags.DEFINE_integer("hz", 5000, "Print this many samples/sec")
+ flags.DEFINE_string("device", None,
+ "Path to the device in /dev/... (ex:/dev/ttyACM1)")
+ flags.DEFINE_integer("serialno", None, "Look for a device with this serial number")
+ flags.DEFINE_boolean("timestamp", None,
+ "Also print integer (seconds) timestamp on each line")
+ flags.DEFINE_boolean("ramp", True, "Gradually increase voltage")
+
+ main(FLAGS(sys.argv))
diff --git a/tools/vm-tests-tf/etc/starttests b/tools/vm-tests-tf/etc/starttests
new file mode 100755
index 0000000..be8fad4
--- /dev/null
+++ b/tools/vm-tests-tf/etc/starttests
@@ -0,0 +1,296 @@
+#!/bin/bash
+#
+# Copyright (C) 2008 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Set up prog to be the path of this script, including following symlinks,
+# and set up progdir to be the fully-qualified pathname of its directory.
+
+prog="$0"
+while [ -h "${prog}" ]; do
+ newProg=`/bin/ls -ld "${prog}"`
+ newProg=`expr "${newProg}" : ".* -> \(.*\)$"`
+ if expr "x${newProg}" : 'x/' >/dev/null; then
+ prog="${newProg}"
+ else
+ progdir=`dirname "${prog}"`
+ prog="${progdir}/${newProg}"
+ fi
+done
+oldwd=`pwd`
+progdir=`dirname "${prog}"`
+cd "${progdir}"
+progdir=`pwd`
+prog="${progdir}"/`basename "${prog}"`
+cd "${oldwd}"
+
+libdir=`dirname $progdir`/framework
+
+javaOpts=""
+while expr "x$1" : 'x-J' >/dev/null; do
+ opt=`expr "$1" : '-J\(.*\)'`
+ javaOpts="${javaOpts} -${opt}"
+ shift
+done
+
+
+#######################################################################
+# Original content of invocation script follows. Uses values cleverly
+# deduced by the above code.
+#######################################################################
+
+selection=$1
+interpreter="fast"
+if [ "$selection" = "--portable" ]; then
+ selection=$2;
+ interpreter="portable"
+fi
+
+dalviktest=$ANDROID_BUILD_TOP/out/host/common/obj/JAVA_LIBRARIES/vm-tests-tf_intermediates
+dalviktestdir=$dalviktest/tests
+dexcore=$dalviktest/tests/dot/junit/dexcore.jar
+scriptdata=$dalviktestdir/data/scriptdata
+report=$dalviktest/report.html
+curdate=`date`
+curmode=""
+datadir=/tmp/${USER}
+base=$OUT
+framework=$base/system/framework
+export ANDROID_PRINTF_LOG=tag
+export ANDROID_LOG_TAGS='*:s' # was: jdwp:i dalvikvm:i dalvikvmi:i'
+export ANDROID_DATA=$datadir
+export ANDROID_ROOT=$base/system
+export LD_LIBRARY_PATH=$base/system/lib
+export DYLD_LIBRARY_PATH=$base/system/lib
+debug_opts="-Xcheck:jni -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n"
+exe=$base/system/bin/dalvikvm
+bpath=$framework/core.jar
+
+echo "--------------------------------------------------"
+echo "Dalvik VM Test Suite"
+echo "Version 1.0"
+echo "Copyright (c) 2008 The Android Open Source Project"
+echo ""
+
+if [ "$selection" = "--help" ]; then
+ echo "Usage: vm-tests [--help|--portable] [<mnemonic>]"
+ echo ""
+ echo " --help prints this help message"
+ echo " --portable uses the portable interpreter;"
+ echo " default is the fast one"
+ echo ""
+ echo " <mnemonic> specifies the instruction to test;"
+ echo " default is to run all tests"
+ echo ""
+ exit 1;
+fi
+
+rm -rf --preserve-root $datadir/dalvik-cache
+mkdir -p $datadir
+mkdir -p $datadir/dalvik-cache
+
+if [ "$TARGET_SIMULATOR" = "true" ]; then
+ echo "Simulator mode, $interpreter interpreter";
+ curmode="simulator"
+else
+ echo "Emulator mode, $interpreter interpreter";
+ curmode="emulator"
+fi
+
+echo ""
+
+pre_report="<html><head><style>
+table tr.ok { background:#a0ffa0; }
+table tr.nok { background:#ffa0a0; }
+table tr.wok { background:#ffffa0; }
+table tr.lok { background:#aaaaff; }
+</style></head>
+<body>
+<h1>Dalvik VM test suite results</h1>
+Generated $curdate (using the $curmode)
+<p>
+<table width='100%'>
+<tr><td>Status</td><td>Target</td><td>Category</td><td>Details</td></tr>"
+post_report="</body></html>"
+
+rm -f $report
+echo $pre_report > $report
+
+# ----------- running each opcode test ------------
+
+export jpassedcnt=0
+export jfailedcnt=0
+export jvfefailedcnt=0
+export jwarningcnt=0
+export jallcnt=0
+export jcolumns=0
+
+# TODO unhack
+if [ "$TARGET_SIMULATOR" = "true" ]; then
+ echo -n ""
+else
+ adb push $dexcore /data/local/tmp/dexcore.jar >> /dev/null 2>&1
+fi
+
+function classnameToJar()
+{
+ echo $1 | sed -e 's#\.#/#g;s#$#.jar#'
+}
+
+while read -u 3 myline;
+do
+ # dot.junit.opcodes.add_double.Main_testB1;dot.junit.opcodes.add_double.d.T_add_double_1 ;opcode add_double;test B #1 (border edge case)
+ # ->
+ # mainclass: dot.junit.opcodes.add_double.Main_testB1
+ # testcasedir: opcodes/add_double
+ # testname: testB1 ->
+ # dir dot/junit/opcodes/add_double/testB1
+
+ # e.g dot.junit.opcodes.add_double.Main_testB1
+ mainclass=`echo $myline | cut -d";" -f1`
+ # e.g dot.junit.opcodes.add_double.d.T_add_double_1, space sep. >=1 entries
+ deps=`echo $myline | cut -d";" -f2`
+
+ jtitle=`echo $myline | cut -d";" -f3`
+ jcomment=`echo $myline | cut -d";" -f4`
+ details=`echo $myline | cut -d";" -f5`
+
+ if [ "$selection" == "" ] || [ "$jtitle" == "$selection" ]; then
+
+ (( jallcnt += 1 ))
+
+ cd $dalviktestdir
+ rm -f $datadir/dalvikout
+ # write dalvik output to file
+ echo -n "mk_b:" > $datadir/dalvikout
+
+ if [ "$TARGET_SIMULATOR" = "true" ]; then
+ classpath=`classnameToJar ${mainclass}`
+ for dep in ${deps}; do
+ depJar=`classnameToJar ${dep}`
+ classpath=${classpath}:${depJar}
+ done
+ $valgrind $exe -Xmx512M -Xss32K -Xbootclasspath:$bpath $debug_opts \
+ -classpath $dexcore:$classpath $mainclass >> $datadir/dalvikout 2>&1
+
+ RESULTCODE=$?
+ if [ ${RESULTCODE} -ne 0 ]; then
+ echo "Dalvik VM failed, result=${RESULTCODE}" >> $datadir/dalvikout 2>&1
+ fi
+ else
+ classpath="/data/local/tmp/dexcore.jar"
+ deps=${deps}" "${mainclass}
+ pushedjars=""
+ for dep in ${deps}; do
+ depJar=`classnameToJar ${dep}`
+ depFileName=`basename ${depJar}`
+ deviceFileName=/data/local/tmp/${depFileName}
+ adb push ${depJar} ${deviceFileName} &> /dev/null
+ classpath=${classpath}:${deviceFileName}
+ pushedjars=${pushedjars}" "${deviceFileName}
+ done
+
+ adb shell dalvikvm -Djava.io.tmpdir=/data/local/tmp \
+ -classpath $classpath $mainclass >> $datadir/dalvikout 2>&1 && \
+ echo -n dvmpassed: >> $datadir/dalvikout 2>&1
+
+ for jar in ${pushedjars}; do
+ adb shell rm ${jar} &> /dev/null
+ done
+ fi
+
+ echo -n "mk_s:" >> $datadir/dalvikout
+ # Verify tmpout only contains mkdxc_start;mkdxc_stop -> no system.out/err
+ # because of exception. If ok -> green report line else red report with info
+ # between mkdxc_start and stop
+ vmresult=`cat $datadir/dalvikout`
+
+ if [[ ("$vmresult" == "mk_b:mk_s:") || ("$vmresult" == "mk_b:dvmpassed:mk_s:") ]]; then
+ (( jpassedcnt += 1 ))
+ echo -n "<tr class=\"ok\"><td>Success</td><td>$jtitle</td>" >> $report
+ echo "<td>$jcomment</td><td>$details</td></tr>" >> $report
+ echo -n "."
+ else
+ vmres=`cat $datadir/dalvikout | sed -e 's/mk_b://;s/mk_s://'`
+ vmres="$details<br><pre>$vmres</pre>"
+
+ stacktraces=`echo $vmresult | grep "java\.lang\." | grep -c "at dot\.junit\."`
+ if [[ $stacktraces > 0 ]]; then
+ jtype=`echo "$mainclass" | sed -e 's/.*_test\([^0-9]*\)[0-9].*/\1/' `
+ if [ "$jtype" == "VFE" ]; then
+ (( jvfefailedcnt += 1 ))
+ echo -n "V"
+ else
+ (( jfailedcnt += 1 ))
+ echo -n "F"
+ fi
+
+ echo "<tr class=\"nok\"><td>Failure</td><td>$jtitle</td><td>" >> $report
+ echo "$jcomment</td><td>$vmres</td></tr>" >> $report
+ else
+ (( jwarningcnt += 1 ))
+ echo "<tr class=\"wok\"><td>Failure</td><td>$jtitle</td><td>" >> $report
+ echo "$jcomment</td><td>(No stacktrace, but errors on console)" >> $report
+ echo "<br>$vmres</td></tr>" >> $report
+ echo -n "C"
+ fi
+ fi
+
+ (( jcolumns += 1 ))
+ if [ ${jcolumns} -eq 40 ]; then
+ echo ""
+ (( jcolumns = 0 ))
+ fi
+
+ fi
+# Use fd nr 3 to avoid subshelling via cat since this looses all
+# variables(and thus also the counters we are interested in).
+done 3<$scriptdata
+
+echo "</table>" >> $report
+let jallcalccnt=$jpassedcnt+$jfailedcnt+$jvfefailedcnt+$jwarningcnt
+if [ $jallcalccnt -ne $jallcnt ]; then
+ echo "<br>error: green & red != total , $jallcalccnt -ne $jallcnt" >> $report
+ exit 1;
+fi
+
+echo $post_report >> $report
+
+echo "<br>Tests run: ${jallcnt}" >> $report
+echo "<br>Functional failures: ${jfailedcnt}" >> $report
+echo "<br>Verifier failures: ${jvfefailedcnt}" >> $report
+echo "<br>Console errors: ${jwarningcnt}" >> $report
+
+echo $post_report >> $report
+
+if [[ jcolumns -ne 0 ]]; then
+ echo ""
+fi
+
+echo ""
+
+if [[ jallcnt -eq jpassedcnt ]]; then
+ echo "OK (${jpassedcnt} tests)"
+else
+ echo "FAILURES!!!"
+ echo ""
+ echo "Tests run : ${jallcnt}"
+ echo "Functional failures: ${jfailedcnt}"
+ echo "Verifier failures : ${jvfefailedcnt}"
+ echo "Console errors : ${jwarningcnt}"
+fi
+
+echo ""
+echo "Please see complete report in ${report}"
+echo "--------------------------------------------------"
diff --git a/tools/vm-tests-tf/src/dot/junit/opcodes/invoke_virtual/d/T_invoke_virtual_12.d b/tools/vm-tests-tf/src/dot/junit/opcodes/invoke_virtual/d/T_invoke_virtual_12.d
index 02d509a..8068732 100644
--- a/tools/vm-tests-tf/src/dot/junit/opcodes/invoke_virtual/d/T_invoke_virtual_12.d
+++ b/tools/vm-tests-tf/src/dot/junit/opcodes/invoke_virtual/d/T_invoke_virtual_12.d
@@ -25,6 +25,7 @@
.end method
.method public test(Ljava/lang/String;)V
+.limit regs 2
return-void
.end method
diff --git a/tools/vm-tests-tf/src/dot/junit/opcodes/invoke_virtual_range/d/T_invoke_virtual_range_12.d b/tools/vm-tests-tf/src/dot/junit/opcodes/invoke_virtual_range/d/T_invoke_virtual_range_12.d
index 9b63ef8..93df0f5 100644
--- a/tools/vm-tests-tf/src/dot/junit/opcodes/invoke_virtual_range/d/T_invoke_virtual_range_12.d
+++ b/tools/vm-tests-tf/src/dot/junit/opcodes/invoke_virtual_range/d/T_invoke_virtual_range_12.d
@@ -25,6 +25,7 @@
.end method
.method public test(Ljava/lang/String;)V
+.limit regs 2
return-void
.end method
diff --git a/tools/vm-tests-tf/src/util/build/BuildDalvikSuite.java b/tools/vm-tests-tf/src/util/build/BuildDalvikSuite.java
index 9d4bbed..7f52edf 100644
--- a/tools/vm-tests-tf/src/util/build/BuildDalvikSuite.java
+++ b/tools/vm-tests-tf/src/util/build/BuildDalvikSuite.java
@@ -217,7 +217,7 @@
}
private String getShellExecJavaLine(String classpath, String mainclass) {
- String cmd = String.format("ANDROID_DATA=%s dalvikvm -Xint:portable -Xmx512M -Xss32K " +
+ String cmd = String.format("ANDROID_DATA=%s dalvikvm -Xmx512M -Xss32K " +
"-Djava.io.tmpdir=%s -classpath %s %s", TARGET_JAR_ROOT_PATH, TARGET_JAR_ROOT_PATH,
classpath, mainclass);
return "String res = getDevice().executeShellCommand(\""+ cmd + "\");\n" +
@@ -260,6 +260,7 @@
private void handleTests() throws IOException {
System.out.println("collected " + testMethodsCnt + " test methods in " +
testClassCnt + " junit test classes");
+ String datafileContent = "";
Set<BuildStep> targets = new TreeSet<BuildStep>();
javacHostJunitBuildStep = new JavacBuildStep(HOSTJUNIT_CLASSES_OUTPUT_FOLDER, CLASS_PATH);
@@ -336,6 +337,86 @@
targets.add(dexBuildStep);
// }
+
+ // prepare the entry in the data file for the bash script.
+ // e.g.
+ // main class to execute; opcode/constraint; test purpose
+ // dxc.junit.opcodes.aaload.Main_testN1;aaload;normal case test
+ // (#1)
+
+ char ca = method.charAt("test".length()); // either N,B,E,
+ // or V (VFE)
+ String comment;
+ switch (ca) {
+ case 'N':
+ comment = "Normal #" + method.substring(5);
+ break;
+ case 'B':
+ comment = "Boundary #" + method.substring(5);
+ break;
+ case 'E':
+ comment = "Exception #" + method.substring(5);
+ break;
+ case 'V':
+ comment = "Verifier #" + method.substring(7);
+ break;
+ default:
+ throw new RuntimeException("unknown test abbreviation:"
+ + method + " for " + fqcn);
+ }
+
+ String line = pName + ".Main_" + method + ";";
+ for (String className : dependentTestClassNames) {
+ line += className + " ";
+ }
+
+
+ // test description
+ String[] pparts = pName.split("\\.");
+ // detail e.g. add_double
+ String detail = pparts[pparts.length-1];
+ // type := opcode | verify
+ String type = pparts[pparts.length-2];
+
+ String description;
+ if ("format".equals(type)) {
+ description = "format";
+ } else if ("opcodes".equals(type)) {
+ // Beautify name, so it matches the actual mnemonic
+ detail = detail.replaceAll("_", "-");
+ detail = detail.replace("-from16", "/from16");
+ detail = detail.replace("-high16", "/high16");
+ detail = detail.replace("-lit8", "/lit8");
+ detail = detail.replace("-lit16", "/lit16");
+ detail = detail.replace("-4", "/4");
+ detail = detail.replace("-16", "/16");
+ detail = detail.replace("-32", "/32");
+ detail = detail.replace("-jumbo", "/jumbo");
+ detail = detail.replace("-range", "/range");
+ detail = detail.replace("-2addr", "/2addr");
+
+ // Unescape reserved words
+ detail = detail.replace("opc-", "");
+
+ description = detail;
+ } else if ("verify".equals(type)) {
+ description = "verifier";
+ } else {
+ description = type + " " + detail;
+ }
+
+ String details = (md.title != null ? md.title : "");
+ if (md.constraint != null) {
+ details = " Constraint " + md.constraint + ", " + details;
+ }
+ if (details.length() != 0) {
+ details = details.substring(0, 1).toUpperCase()
+ + details.substring(1);
+ }
+
+ line += ";" + description + ";" + comment + ";" + details;
+
+ datafileContent += line + "\n";
generateBuildStepFor(pName, method, dependentTestClassNames,
targets);
}
@@ -346,6 +427,10 @@
// write latest HOSTJUNIT generated file.
flushHostJunitFile();
+ File scriptDataDir = new File(OUTPUT_FOLDER + "/data/");
+ scriptDataDir.mkdirs();
+ writeToFile(new File(scriptDataDir, "scriptdata"), datafileContent);
+
if (!javacHostJunitBuildStep.build()) {
System.out.println("main javac cts-host-hostjunit-classes build step failed");
System.exit(1);