am 66c4138f: am 82a45527: Merge "SeccompTest: add note about kernels between 3.5 and 3.8"
* commit '66c4138fbd1fc6ec59e82916b79df610ee551064':
SeccompTest: add note about kernels between 3.5 and 3.8
diff --git a/Android.mk b/Android.mk
index 4251262..dbcc97b 100644
--- a/Android.mk
+++ b/Android.mk
@@ -14,6 +14,7 @@
# limitations under the License.
#
+include cts/suite/pts/PtsBenchmarkingList.mk
include cts/CtsBuild.mk
include cts/CtsCoverage.mk
include $(call all-subdir-makefiles)
diff --git a/CtsBuild.mk b/CtsBuild.mk
index d396006..6e57086 100644
--- a/CtsBuild.mk
+++ b/CtsBuild.mk
@@ -36,6 +36,10 @@
$(foreach lib,$(1),$(HOST_OUT_JAVA_LIBRARIES)/$(lib).jar)
endef
+define cts-get-ui-lib-paths
+ $(foreach lib,$(1),$(CTS_TESTCASES_OUT)/$(lib).jar)
+endef
+
define cts-get-native-paths
$(foreach exe,$(1),$(call intermediates-dir-for,EXECUTABLES,$(exe))/$(exe))
endef
diff --git a/CtsCoverage.mk b/CtsCoverage.mk
index 61ad9c9..8d67a99 100644
--- a/CtsCoverage.mk
+++ b/CtsCoverage.mk
@@ -24,7 +24,7 @@
coverage_out := $(HOST_OUT)/cts-api-coverage
-api_text_description := $(SRC_API_DIR)/current.txt
+api_text_description := frameworks/base/api/current.txt
api_xml_description := $(coverage_out)/api.xml
$(api_xml_description) : $(api_text_description) $(APICHECK)
$(hide) echo "Converting API file to XML: $@"
diff --git a/CtsTestCaseList.mk b/CtsTestCaseList.mk
index 5511ebd..1e794f9 100644
--- a/CtsTestCaseList.mk
+++ b/CtsTestCaseList.mk
@@ -29,6 +29,7 @@
CtsMultiUserStorageApp
cts_support_packages := \
+ $(PTS_SUPPORT_PACKAGES) \
CtsAccelerationTestStubs \
CtsDelegatingAccessibilityService \
CtsDeviceAdmin \
@@ -38,6 +39,7 @@
CtsTestStubs \
SignatureTest \
TestDeviceSetup \
+ CtsUiAutomatorApp \
$(cts_security_apps_list)
cts_external_packages := \
@@ -51,6 +53,7 @@
# Test packages that require an associated test package XML.
cts_test_packages := \
+ $(PTS_TEST_PACKAGES) \
CtsAccelerationTestCases \
CtsAccountManagerTestCases \
CtsAccessibilityServiceTestCases \
@@ -62,8 +65,10 @@
CtsCalendarcommon2TestCases \
CtsContentTestCases \
CtsDatabaseTestCases \
+ CtsDisplayTestCases \
CtsDpiTestCases \
CtsDpiTestCases2 \
+ CtsDreamsTestCases \
CtsDrmTestCases \
CtsEffectTestCases \
CtsExampleTestCases \
@@ -76,6 +81,7 @@
CtsLocationTestCases \
CtsMediaStressTestCases \
CtsMediaTestCases \
+ CtsNativeOpenGLTestCases \
CtsNdefTestCases \
CtsNetTestCases \
CtsOpenGLTestCases \
@@ -107,6 +113,7 @@
# Host side only tests
cts_host_libraries := \
+ $(PTS_HOST_CASES) \
CtsAppSecurityTests \
CtsMonkeyTestCases
@@ -115,17 +122,23 @@
NativeMediaTest_SL \
NativeMediaTest_XA
+cts_ui_tests := \
+ CtsUiAutomatorTests
+
# All the files that will end up under the repository/testcases
# directory of the final CTS distribution.
CTS_TEST_CASES := $(call cts-get-lib-paths,$(cts_host_libraries)) \
$(call cts-get-package-paths,$(cts_test_packages)) \
- $(call cts-get-native-paths,$(cts_native_exes))
+ $(call cts-get-native-paths,$(cts_native_exes)) \
+ $(call cts-get-ui-lib-paths,$(cts_ui_tests))
# All the XMLs that will end up under the repository/testcases
# and that need to be created before making the final CTS distribution.
CTS_TEST_XMLS := $(call cts-get-test-xmls,$(cts_host_libraries)) \
$(call cts-get-test-xmls,$(cts_test_packages)) \
- $(call cts-get-test-xmls,$(cts_native_exes))
+ $(call cts-get-test-xmls,$(cts_native_exes)) \
+ $(call cts-get-test-xmls,$(cts_ui_tests))
+
# The following files will be placed in the tools directory of the CTS distribution
CTS_TOOLS_LIST :=
diff --git a/apps/CtsVerifier/Android.mk b/apps/CtsVerifier/Android.mk
index aa3f117..c14728e 100644
--- a/apps/CtsVerifier/Android.mk
+++ b/apps/CtsVerifier/Android.mk
@@ -36,7 +36,7 @@
# Builds and launches CTS Verifier on a device.
.PHONY: cts-verifier
cts-verifier: CtsVerifier adb
- adb install -r $(ANDROID_PRODUCT_OUT)/data/app/CtsVerifier.apk \
+ adb install -r $(PRODUCT_OUT)/data/app/CtsVerifier.apk \
&& adb shell "am start -n com.android.cts.verifier/.CtsVerifierActivity"
#
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 7bf8387..9b8d5b1 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -18,7 +18,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.cts.verifier"
android:versionCode="1"
- android:versionName="4.2_r4">
+ android:versionName="1337">
<!-- Using 10 for more complete NFC support... -->
<uses-sdk android:minSdkVersion="10"></uses-sdk>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/features/FeatureSummaryActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/features/FeatureSummaryActivity.java
index ca3c2a9..afbf97e 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/features/FeatureSummaryActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/features/FeatureSummaryActivity.java
@@ -172,10 +172,6 @@
new Feature(PackageManager.FEATURE_TELEVISION, false),
};
- public static final Feature[] ALL_JELLY_BEAN_MR1_FEATURES = {
- new Feature(PackageManager.FEATURE_CAMERA_ANY, false),
- };
-
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -206,9 +202,6 @@
// add features from latest to last so that the latest requirements are put in the set first
int apiVersion = Build.VERSION.SDK_INT;
- if (apiVersion >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
- Collections.addAll(features, ALL_JELLY_BEAN_MR1_FEATURES);
- }
if (apiVersion >= Build.VERSION_CODES.JELLY_BEAN) {
Collections.addAll(features, ALL_JELLY_BEAN_FEATURES);
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/location/GpsTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/location/GpsTestActivity.java
index bb12b2b..da1ee88 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/location/GpsTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/location/GpsTestActivity.java
@@ -95,25 +95,25 @@
case 1:
// Test GPS with minTime = 0
mLocationVerifier = new LocationVerifier(this, mLocationManager,
- LocationManager.GPS_PROVIDER, 0, 8);
+ LocationManager.GPS_PROVIDER, 0, 8, false);
mLocationVerifier.start();
break;
case 2:
// Test GPS with minTime = 1s
mLocationVerifier = new LocationVerifier(this, mLocationManager,
- LocationManager.GPS_PROVIDER, 1 * 1000, 8);
+ LocationManager.GPS_PROVIDER, 1 * 1000, 8, false);
mLocationVerifier.start();
break;
case 3:
// Test GPS with minTime = 5s
mLocationVerifier = new LocationVerifier(this, mLocationManager,
- LocationManager.GPS_PROVIDER, 5 * 1000, 8);
+ LocationManager.GPS_PROVIDER, 5 * 1000, 8, false);
mLocationVerifier.start();
break;
case 4:
// Test GPS with minTime = 15s
mLocationVerifier = new LocationVerifier(this, mLocationManager,
- LocationManager.GPS_PROVIDER, 15 * 1000, 8);
+ LocationManager.GPS_PROVIDER, 15 * 1000, 8, false);
mLocationVerifier.start();
break;
case 5:
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/location/LocationVerifier.java b/apps/CtsVerifier/src/com/android/cts/verifier/location/LocationVerifier.java
index 3315ba3..ee4068f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/location/LocationVerifier.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/location/LocationVerifier.java
@@ -66,6 +66,7 @@
private long mLastPassiveTimestamp = -1;
private int mNumActiveUpdates = 0;
private int mNumPassiveUpdates = 0;
+ private boolean mIsMockProvider = false;
private boolean mRunning = false;
private boolean mActiveLocationArrive = false;
@@ -89,6 +90,11 @@
mCb.log("(ignored) active " + mProvider + " update (" + delta + "ms)");
return;
}
+ if (location.isFromMockProvider() != mIsMockProvider) {
+ fail("location coming from \"" + mProvider +
+ "\" provider reports isFromMockProvider() to be " +
+ location.isFromMockProvider());
+ }
mActiveDeltas.add(delta);
mCb.log("active " + mProvider + " update (" + delta + "ms)");
@@ -171,6 +177,11 @@
mCb.log("(ignored) passive " + mProvider + " update (" + delta + "ms)");
return;
}
+ if (location.isFromMockProvider() != mIsMockProvider) {
+ fail("location coming from \"" + mProvider +
+ "\" provider reports isFromMockProvider() to be " +
+ location.isFromMockProvider());
+ }
mPassiveDeltas.add(delta);
mCb.log("passive " + mProvider + " update (" + delta + "ms)");
@@ -185,7 +196,7 @@
}
public LocationVerifier(PassFailLog cb, LocationManager locationManager,
- String provider, long requestedInterval, int numUpdates) {
+ String provider, long requestedInterval, int numUpdates, boolean isMockProvider) {
mProvider = provider;
mInterval = requestedInterval;
// timeout at 60 seconds after interval time
@@ -196,6 +207,7 @@
mHandler = new Handler(this);
mActiveListener = new ActiveListener();
mPassiveListener = new PassiveListener();
+ mIsMockProvider = isMockProvider;
}
public void start() {
diff --git a/build/config.mk b/build/config.mk
index c9fa709..b737ef7 100644
--- a/build/config.mk
+++ b/build/config.mk
@@ -14,4 +14,6 @@
BUILD_CTS_EXECUTABLE := cts/build/test_executable.mk
BUILD_CTS_PACKAGE := cts/build/test_package.mk
+BUILD_CTS_GTEST_PACKAGE := cts/build/test_gtest_package.mk
BUILD_CTS_HOST_JAVA_LIBRARY := cts/build/test_host_java_library.mk
+BUILD_CTS_UI_JAVA_LIBRARY := cts/build/test_uiautomator.mk
diff --git a/build/test_gtest_package.mk b/build/test_gtest_package.mk
new file mode 100644
index 0000000..2f4c9bb
--- /dev/null
+++ b/build/test_gtest_package.mk
@@ -0,0 +1,49 @@
+# Copyright 2012 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# Builds a package and defines a rule to generate the associated test
+# package XML needed by CTS.
+#
+# Replace "include $(BUILD_PACKAGE)" with "include $(BUILD_CTS_GTEST_PACKAGE)"
+#
+
+# Disable by default so "m cts" will work in emulator builds
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_PACKAGE)
+
+cts_package_apk := $(CTS_TESTCASES_OUT)/$(LOCAL_PACKAGE_NAME).apk
+cts_package_xml := $(CTS_TESTCASES_OUT)/$(LOCAL_PACKAGE_NAME).xml
+
+$(cts_package_apk): PRIVATE_PACKAGE := $(LOCAL_PACKAGE_NAME)
+$(cts_package_apk): $(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME))/package.apk | $(ACP)
+ $(hide) mkdir -p $(CTS_TESTCASES_OUT)
+ $(hide) $(ACP) -fp $(call intermediates-dir-for,APPS,$(PRIVATE_PACKAGE))/package.apk $@
+
+$(cts_package_xml): PRIVATE_PATH := $(LOCAL_PATH)
+$(cts_package_xml): PRIVATE_TEST_PACKAGE := android.$(notdir $(LOCAL_PATH))
+$(cts_package_xml): PRIVATE_EXECUTABLE := $(LOCAL_MODULE)
+$(cts_package_xml): PRIVATE_MANIFEST := $(LOCAL_PATH)/AndroidManifest.xml
+$(cts_package_xml): $(addprefix $(LOCAL_PATH)/,$(LOCAL_SRC_FILES)) $(CTS_NATIVE_TEST_SCANNER) $(CTS_XML_GENERATOR)
+ $(hide) echo Generating test description for wrapped native package $(PRIVATE_EXECUTABLE)
+ $(hide) mkdir -p $(CTS_TESTCASES_OUT)
+ $(hide) $(CTS_NATIVE_TEST_SCANNER) -s $(PRIVATE_PATH) \
+ -t $(PRIVATE_TEST_PACKAGE) | \
+ $(CTS_XML_GENERATOR) -t wrappednative \
+ -m $(PRIVATE_MANIFEST) \
+ -n $(PRIVATE_EXECUTABLE) \
+ -p $(PRIVATE_TEST_PACKAGE) \
+ -e $(CTS_EXPECTATIONS) \
+ -o $@
diff --git a/build/test_package.mk b/build/test_package.mk
index 071acee..aa9a4dd 100644
--- a/build/test_package.mk
+++ b/build/test_package.mk
@@ -35,7 +35,12 @@
$(cts_package_xml): PRIVATE_PATH := $(LOCAL_PATH)
$(cts_package_xml): PRIVATE_INSTRUMENTATION := $(LOCAL_INSTRUMENTATION_FOR)
$(cts_package_xml): PRIVATE_PACKAGE := $(LOCAL_PACKAGE_NAME)
-$(cts_package_xml): PRIVATE_TEST_PACKAGE := android.$(notdir $(LOCAL_PATH))
+ifneq ($(filter cts/suite/pts/%, $(LOCAL_PATH)),) # PTS
+PRIVATE_CTS_TEST_PACKAGE_NANE_ := com.android.pts.$(notdir $(LOCAL_PATH))
+else # CTS
+PRIVATE_CTS_TEST_PACKAGE_NANE_ := android.$(notdir $(LOCAL_PATH))
+endif # PTS
+$(cts_package_xml): PRIVATE_TEST_PACKAGE := $(PRIVATE_CTS_TEST_PACKAGE_NANE_)
$(cts_package_xml): PRIVATE_MANIFEST := $(LOCAL_PATH)/AndroidManifest.xml
$(cts_package_xml): PRIVATE_TEST_TYPE := $(if $(LOCAL_CTS_TEST_RUNNER),$(LOCAL_CTS_TEST_RUNNER),'')
$(cts_package_xml): $(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME))/package.apk $(CTS_EXPECTATIONS) $(CTS_JAVA_TEST_SCANNER_DOCLET) $(CTS_JAVA_TEST_SCANNER) $(CTS_XML_GENERATOR)
diff --git a/build/test_uiautomator.mk b/build/test_uiautomator.mk
new file mode 100644
index 0000000..255f70b
--- /dev/null
+++ b/build/test_uiautomator.mk
@@ -0,0 +1,49 @@
+# Copyright (C) 2012 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# Builds a uiautomator library and defines a rule to generate the associated test
+# package XML needed by CTS.
+#
+
+include $(BUILD_JAVA_LIBRARY)
+
+cts_library_xml := $(CTS_TESTCASES_OUT)/$(LOCAL_MODULE).xml
+cts_library_jar := $(CTS_TESTCASES_OUT)/$(LOCAL_MODULE).jar
+
+$(cts_library_jar): PRIVATE_MODULE := $(LOCAL_MODULE)
+$(cts_library_jar): $(call intermediates-dir-for,JAVA_LIBRARIES,$(LOCAL_MODULE))/javalib.jar | $(ACP)
+ $(hide) mkdir -p $(CTS_TESTCASES_OUT)
+ $(hide) $(ACP) -fp $(call intermediates-dir-for,JAVA_LIBRARIES,$(PRIVATE_MODULE))/javalib.jar $@
+
+$(cts_library_xml): PRIVATE_PATH := $(LOCAL_PATH)/src
+$(cts_library_xml): PRIVATE_TEST_APP_PACKAGE := $(LOCAL_CTS_TEST_APP_PACKAGE)
+$(cts_library_xml): PRIVATE_TEST_PACKAGE := $(LOCAL_CTS_TEST_PACKAGE)
+$(cts_library_xml): PRIVATE_TEST_APK := $(LOCAL_CTS_TEST_APK)
+$(cts_library_xml): PRIVATE_LIBRARY := $(LOCAL_MODULE)
+$(cts_library_xml): PRIVATE_JAR_PATH := $(LOCAL_MODULE).jar
+$(cts_library_xml): $(call intermediates-dir-for,JAVA_LIBRARIES,$(LOCAL_MODULE))/javalib.jar $(CTS_EXPECTATIONS) $(CTS_JAVA_TEST_SCANNER_DOCLET) $(CTS_JAVA_TEST_SCANNER) $(CTS_XML_GENERATOR)
+ $(hide) echo Generating test description for uiautomator library $(PRIVATE_LIBRARY)
+ $(hide) mkdir -p $(CTS_TESTCASES_OUT)
+ $(hide) $(CTS_JAVA_TEST_SCANNER) -s $(PRIVATE_PATH) \
+ -d $(CTS_JAVA_TEST_SCANNER_DOCLET) | \
+ $(CTS_XML_GENERATOR) -t uiAutomator \
+ -i $(PRIVATE_TEST_APK) \
+ -j $(PRIVATE_JAR_PATH) \
+ -a $(PRIVATE_TEST_PACKAGE) \
+ -n $(PRIVATE_LIBRARY) \
+ -p $(PRIVATE_TEST_PACKAGE) \
+ -r $(PRIVATE_TEST_APP_PACKAGE) \
+ -e $(CTS_EXPECTATIONS) \
+ -o $@
diff --git a/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/AmbiguousContentProvider.java b/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/AmbiguousContentProvider.java
index 3536979..09ddbc2 100644
--- a/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/AmbiguousContentProvider.java
+++ b/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/AmbiguousContentProvider.java
@@ -20,6 +20,10 @@
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+
+import java.io.File;
+import java.io.FileNotFoundException;
/**
* Empty content provider, manifest did not declare exported=true nor exported=false.
@@ -58,4 +62,10 @@
String[] selectionArgs) {
return 0;
}
+
+ @Override
+ public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
+ return ParcelFileDescriptor.open(
+ new File("/dev/null"), ParcelFileDescriptor.MODE_READ_ONLY);
+ }
}
diff --git a/hostsidetests/appsecurity/test-apps/PermissionDeclareAppCompat/src/com/android/cts/permissiondeclareappcompat/AmbiguousContentProvider.java b/hostsidetests/appsecurity/test-apps/PermissionDeclareAppCompat/src/com/android/cts/permissiondeclareappcompat/AmbiguousContentProvider.java
index 8665b70..9727047 100644
--- a/hostsidetests/appsecurity/test-apps/PermissionDeclareAppCompat/src/com/android/cts/permissiondeclareappcompat/AmbiguousContentProvider.java
+++ b/hostsidetests/appsecurity/test-apps/PermissionDeclareAppCompat/src/com/android/cts/permissiondeclareappcompat/AmbiguousContentProvider.java
@@ -20,6 +20,10 @@
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+
+import java.io.File;
+import java.io.FileNotFoundException;
/**
* Empty content provider, all permissions are enforced in manifest
@@ -58,4 +62,10 @@
String[] selectionArgs) {
return 0;
}
+
+ @Override
+ public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
+ return ParcelFileDescriptor.open(
+ new File("/dev/null"), ParcelFileDescriptor.MODE_READ_ONLY);
+ }
}
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java
index e7d333c..8f40cf1 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java
@@ -102,7 +102,7 @@
}
}
- public void assertWritingContentUriNotAllowed(Uri uri, String msg) {
+ private void assertWritingContentUriNotAllowed(Uri uri, String msg) {
final ContentResolver resolver = getContext().getContentResolver();
try {
resolver.insert(uri, new ContentValues());
diff --git a/libs/testserver/src/android/webkit/cts/CtsTestServer.java b/libs/testserver/src/android/webkit/cts/CtsTestServer.java
index 16d57ea..a18d329 100755
--- a/libs/testserver/src/android/webkit/cts/CtsTestServer.java
+++ b/libs/testserver/src/android/webkit/cts/CtsTestServer.java
@@ -423,11 +423,20 @@
}
/**
+ * Hook for adding stuffs for HTTP POST. Default implementation does nothing.
+ * @return null to use the default response mechanism of sending the requested uri as it is.
+ * Otherwise, the whole response should be handled inside onPost.
+ */
+ protected HttpResponse onPost(HttpRequest request) throws Exception {
+ return null;
+ }
+
+ /**
* Generate a response to the given request.
* @throws InterruptedException
* @throws IOException
*/
- private HttpResponse getResponse(HttpRequest request) throws InterruptedException, IOException {
+ private HttpResponse getResponse(HttpRequest request) throws Exception {
RequestLine requestLine = request.getRequestLine();
HttpResponse response = null;
String uriString = requestLine.getUri();
@@ -441,6 +450,13 @@
}
}
+ if (requestLine.getMethod().equals("POST")) {
+ HttpResponse responseOnPost = onPost(request);
+ if (responseOnPost != null) {
+ return responseOnPost;
+ }
+ }
+
URI uri = URI.create(uriString);
String path = uri.getPath();
String query = uri.getQuery();
@@ -544,12 +560,11 @@
Header[] cookies = request.getHeaders("Cookie");
Pattern p = Pattern.compile("count=(\\d+)");
StringBuilder cookieString = new StringBuilder(100);
+ cookieString.append(cookies.length);
int count = 0;
for (Header cookie : cookies) {
+ cookieString.append("|");
String value = cookie.getValue();
- if (cookieString.length() > 0) {
- cookieString.append("|");
- }
cookieString.append(value);
Matcher m = p.matcher(value);
if (m.find()) {
@@ -864,7 +879,7 @@
}
@Override
- public Void call() throws IOException, InterruptedException, HttpException {
+ public Void call() throws Exception {
HttpResponse response = mServer.getResponse(mRequest);
mConnection.sendResponseHeader(response);
mConnection.sendResponseEntity(response);
diff --git a/tests/tests/openglperf/src/android/openglperf/cts/RenderingWatchDog.java b/libs/util/src/android/cts/util/WatchDog.java
similarity index 72%
rename from tests/tests/openglperf/src/android/openglperf/cts/RenderingWatchDog.java
rename to libs/util/src/android/cts/util/WatchDog.java
index 4872af2..ab2a9d9 100644
--- a/tests/tests/openglperf/src/android/openglperf/cts/RenderingWatchDog.java
+++ b/libs/util/src/android/cts/util/WatchDog.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.openglperf.cts;
+package android.cts.util;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
@@ -26,17 +26,23 @@
* class for checking if rendering function is alive or not.
* panic if watch-dog is not reset over certain amount of time
*/
-public class RenderingWatchDog implements Runnable {
- private static final String TAG = "RenderingWatchDog";
+public class WatchDog implements Runnable {
+ private static final String TAG = "WatchDog";
private Thread mThread;
private Semaphore mSemaphore;
private volatile boolean mStopRequested;
private final long mTimeoutInMilliSecs;
+ private TimeoutCallback mCallback = null;
- public RenderingWatchDog(long timeoutInMilliSecs) {
+ public WatchDog(long timeoutInMilliSecs) {
mTimeoutInMilliSecs = timeoutInMilliSecs;
}
+ public WatchDog(long timeoutInMilliSecs, TimeoutCallback callback) {
+ this(timeoutInMilliSecs);
+ mCallback = callback;
+ }
+
/** start watch-dog */
public void start() {
Log.i(TAG, "start");
@@ -74,12 +80,24 @@
public void run() {
while (!mStopRequested) {
try {
- Assert.assertTrue("Watchdog timed-out",
- mSemaphore.tryAcquire(mTimeoutInMilliSecs, TimeUnit.MILLISECONDS));
+ boolean success = mSemaphore.tryAcquire(mTimeoutInMilliSecs, TimeUnit.MILLISECONDS);
+ if (mCallback == null) {
+ Assert.assertTrue("Watchdog timed-out", success);
+ } else if (!success) {
+ mCallback.onTimeout();
+ }
} catch (InterruptedException e) {
// this thread will not be interrupted,
// but if it happens, just check the exit condition.
}
}
}
+
+ /**
+ * Called by the Watchdog when it has timed out.
+ */
+ public interface TimeoutCallback {
+
+ public void onTimeout();
+ }
}
diff --git a/suite/pts/tools/tradefed/Android.mk b/libs/wrappedgtest/Android.mk
similarity index 74%
rename from suite/pts/tools/tradefed/Android.mk
rename to libs/wrappedgtest/Android.mk
index 09e49b1..f89ba9d 100644
--- a/suite/pts/tools/tradefed/Android.mk
+++ b/libs/wrappedgtest/Android.mk
@@ -1,4 +1,4 @@
-# Copyright (C) 2011 The Android Open Source Project
+# Copyright 2012 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -12,11 +12,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-LOCAL_PATH := $(call my-dir)
+LOCAL_PATH:= $(call my-dir)
+
include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
LOCAL_MODULE_TAGS := optional
-LOCAL_PREBUILT_EXECUTABLES := pts-tradefed
-include $(BUILD_HOST_PREBUILT)
+LOCAL_MODULE := ctswrappedgtest
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/libs/wrappedgtest/src/WrappedGTestActivity.java b/libs/wrappedgtest/src/WrappedGTestActivity.java
new file mode 100644
index 0000000..0633a5b
--- /dev/null
+++ b/libs/wrappedgtest/src/WrappedGTestActivity.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.test.wrappedgtest;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.os.Bundle;
+
+public class WrappedGTestActivity extends Activity {
+
+ private WrappedGTestInstrumentation mInstrumentation;
+
+ public void setInstrumentation(WrappedGTestInstrumentation instrumentation) {
+ mInstrumentation = instrumentation;
+ }
+
+ public int runGTests() {
+ return runTests(this);
+ }
+
+ public void sendStatus(String output) {
+ Bundle outputBundle = new Bundle();
+ outputBundle.putString("gtest", output);
+ mInstrumentation.sendStatus(1, outputBundle);
+ }
+
+ protected static native int runTests(WrappedGTestActivity activity);
+}
diff --git a/libs/wrappedgtest/src/WrappedGTestInstrumentation.java b/libs/wrappedgtest/src/WrappedGTestInstrumentation.java
new file mode 100644
index 0000000..b29aaab
--- /dev/null
+++ b/libs/wrappedgtest/src/WrappedGTestInstrumentation.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.test.wrappedgtest;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+
+public class WrappedGTestInstrumentation extends Instrumentation {
+
+ private static final String TAG = "WrappedGTestInstrumentation";
+ private WrappedGTestActivity mActivity;
+ protected Class mActivityClass;
+
+ public WrappedGTestInstrumentation() {
+ }
+
+ @Override
+ public void onCreate(Bundle arguments) {
+ // attempt to disable keyguard, if current test has permission to do so
+ if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DISABLE_KEYGUARD)
+ == PackageManager.PERMISSION_GRANTED) {
+ Log.i(TAG, "Disabling keyguard");
+ KeyguardManager keyguardManager =
+ (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
+ keyguardManager.newKeyguardLock("cts").disableKeyguard();
+ } else {
+ Log.i(TAG, "Test lacks permission to disable keyguard. " +
+ "UI based tests may fail if keyguard is up");
+ }
+ super.onCreate(arguments);
+ start();
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+
+ Intent intent = new Intent(getTargetContext(), mActivityClass);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ mActivity = (WrappedGTestActivity)startActivitySync(intent);
+ mActivity.setInstrumentation(this);
+ mActivity.runGTests();
+
+ finish(Activity.RESULT_OK, new Bundle());
+ }
+}
diff --git a/suite/pts/Android.mk b/suite/pts/Android.mk
index 07b30e3..c141484 100644
--- a/suite/pts/Android.mk
+++ b/suite/pts/Android.mk
@@ -14,5 +14,4 @@
# limitations under the License.
#
-include cts/suite/pts/PtsBuild.mk
include $(call all-subdir-makefiles)
diff --git a/suite/pts/PtsBenchmarkingList.mk b/suite/pts/PtsBenchmarkingList.mk
new file mode 100644
index 0000000..7c0ad47
--- /dev/null
+++ b/suite/pts/PtsBenchmarkingList.mk
@@ -0,0 +1,36 @@
+#
+# Copyright (C) 2012 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# package definitions for PTS
+
+# New packages should be added here
+PTS_TEST_PACKAGES := \
+ PtsDeviceFilePerf \
+ PtsDeviceUi \
+ PtsDeviceDram \
+ PtsDeviceSimpleCpu \
+ PtsDeviceBrowserBench \
+ PtsDeviceVideoPerf \
+ PtsDeviceOpenGl
+
+PTS_SUPPORT_PACKAGES := \
+ PtsDeviceTaskswitchingAppA \
+ PtsDeviceTaskswitchingAppB \
+ PtsDeviceTaskswitchingControl
+
+PTS_HOST_CASES := \
+ PtsHostBootup \
+ PtsHostUi
diff --git a/suite/pts/PtsBuild.mk b/suite/pts/PtsBuild.mk
deleted file mode 100644
index 18306b9..0000000
--- a/suite/pts/PtsBuild.mk
+++ /dev/null
@@ -1,152 +0,0 @@
-#
-# Copyright (C) 2012 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# several makefiles for CTS merged for PTS
-
-LOCAL_PATH:= $(call my-dir)
-
-# New packages should be added here
-PTS_TEST_PACKAGES := \
- PtsDeviceFilePerf \
- PtsDeviceUi \
- PtsDeviceDram
-
-
-PTS_SUPPORT_PACKAGES := \
- TestDeviceSetup \
- PtsDeviceTaskswitchingAppA \
- PtsDeviceTaskswitchingAppB \
- PtsDeviceTaskswitchingControl \
- com.replica.replicaisland \
- PtsDeviceBrowserLauncher
-
-PTS_HOST_CASES := \
- PtsHostBootup \
- PtsHostUi \
- PtsHostBrowser
-
-PTS_HOST_LIBS := \
- $(HOST_OUT_JAVA_LIBRARIES)/ptscommonutilhost.jar \
- $(HOST_OUT_JAVA_LIBRARIES)/ptshostutil.jar
-
-BUILD_PTS_PACKAGE := cts/suite/pts/build/test_package.mk
-BUILD_PTS_HOST_JAVA_LIBRARY := cts/suite/pts/build/test_host_java_library.mk
-
-PTS_JAVA_TEST_SCANNER := $(HOST_OUT_EXECUTABLES)/cts-java-scanner
-PTS_JAVA_TEST_SCANNER_DOCLET := $(HOST_OUT_JAVA_LIBRARIES)/cts-java-scanner-doclet.jar
-
-# Generator of test XMLs from scanner output.
-PTS_XML_GENERATOR := $(HOST_OUT_EXECUTABLES)/cts-xml-generator
-
-# File indicating which tests should be blacklisted due to problems.
-PTS_EXPECTATIONS := cts/suite/pts/expectations/knownfailures.txt
-
-PTS_TESTCASES_OUT := $(HOST_OUT)/pts-testcases
-
-define pts-get-package-paths
- $(foreach pkg,$(1),$(PTS_TESTCASES_OUT)/$(pkg).apk)
-endef
-
-define pts-get-test-xmls
- $(foreach name,$(1),$(PTS_TESTCASES_OUT)/$(name).xml)
-endef
-
-define pts-get-lib-paths
- $(foreach lib,$(1),$(HOST_OUT_JAVA_LIBRARIES)/$(lib).jar)
-endef
-
-PTS_TEST_CASE_LIST := \
- $(PTS_SUPPORT_PACKAGES)
-
-PTS_TEST_CASES := \
- $(call pts-get-package-paths,$(PTS_TEST_PACKAGES)) \
- $(call pts-get-lib-paths,$(PTS_HOST_CASES))
-
-PTS_TEST_XMLS := \
- $(call pts-get-test-xmls,$(PTS_TEST_PACKAGES)) \
- $(call pts-get-test-xmls,$(PTS_HOST_CASES))
-
-pts_dir := $(HOST_OUT)/pts
-pts_tools_src_dir := cts/tools
-
-pts_name := android-pts
-
-DDMLIB_JAR := $(HOST_OUT_JAVA_LIBRARIES)/ddmlib-prebuilt.jar
-junit_host_jar := $(HOST_OUT_JAVA_LIBRARIES)/junit.jar
-HOSTTESTLIB_JAR := $(HOST_OUT_JAVA_LIBRARIES)/hosttestlib.jar
-TF_JAR := $(HOST_OUT_JAVA_LIBRARIES)/tradefed-prebuilt.jar
-PTS_TF_JAR := $(HOST_OUT_JAVA_LIBRARIES)/cts-tradefed.jar
-PTS_TF_EXEC := $(HOST_OUT_EXECUTABLES)/pts-tradefed
-PTS_TF_README := $(pts_tools_src_dir)/tradefed-host/README
-
-
-DEFAULT_TEST_PLAN := $(pts_dir)/$(pts_name)/resource/plans/PTS.xml
-
-$(pts_dir)/all_pts_files_stamp: PRIVATE_JUNIT_HOST_JAR := $(junit_host_jar)
-
-$(pts_dir)/all_pts_files_stamp: $(PTS_TEST_CASES) $(PTS_TEST_CASE_LIST) $(junit_host_jar) $(HOSTTESTLIB_JAR) $(PTS_HOST_LIBRARY_JARS) $(TF_JAR) $(VMTESTSTF_JAR) $(PTS_TF_JAR) $(PTS_TF_EXEC) $(PTS_TF_README) $(ACP) $(PTS_HOST_LIBS)
-# Make necessary directory for PTS
- $(hide) rm -rf $(PRIVATE_PTS_DIR)
- $(hide) mkdir -p $(TMP_DIR)
- $(hide) mkdir -p $(PRIVATE_DIR)/docs
- $(hide) mkdir -p $(PRIVATE_DIR)/tools
- $(hide) mkdir -p $(PRIVATE_DIR)/repository/testcases
- $(hide) mkdir -p $(PRIVATE_DIR)/repository/plans
-# Copy executable and JARs to PTS directory
- $(hide) $(ACP) -fp $(DDMLIB_JAR) $(PRIVATE_JUNIT_HOST_JAR) $(HOSTTESTLIB_JAR) $(PTS_HOST_LIBRARY_JARS) $(TF_JAR) $(PTS_TF_JAR) $(PTS_TF_EXEC) $(PTS_TF_README) $(PTS_HOST_LIBS) $(PRIVATE_DIR)/tools
-# Change mode of the executables
- $(foreach apk,$(PTS_TEST_CASE_LIST),$(call copy-testcase-apk,$(apk)))
- $(foreach testcase,$(PTS_TEST_CASES),$(call copy-testcase,$(testcase)))
- $(hide) touch $@
-
-# Generate the default test plan for User.
-# Usage: buildCts.py <testRoot> <ctsOutputDir> <tempDir> <androidRootDir> <docletPath>
-
-$(DEFAULT_TEST_PLAN): $(pts_dir)/all_pts_files_stamp $(pts_tools_src_dir)/utils/buildCts.py $(HOST_OUT_JAVA_LIBRARIES)/descGen.jar $(PTS_TEST_XMLS) | $(ACP)
- $(hide) $(ACP) -fp $(PTS_TEST_XMLS) $(PRIVATE_DIR)/repository/testcases
- $(hide) $(pts_tools_src_dir)/utils/buildCts.py cts/suite/pts $(PRIVATE_DIR) $(TMP_DIR) \
- $(TOP) $(HOST_OUT_JAVA_LIBRARIES)/descGen.jar -pts
-
-# Package PTS and clean up.
-INTERNAL_PTS_TARGET := $(pts_dir)/$(pts_name).zip
-$(INTERNAL_PTS_TARGET): PRIVATE_NAME := $(pts_name)
-$(INTERNAL_PTS_TARGET): PRIVATE_PTS_DIR := $(pts_dir)
-$(INTERNAL_PTS_TARGET): PRIVATE_DIR := $(pts_dir)/$(pts_name)
-$(INTERNAL_PTS_TARGET): TMP_DIR := $(pts_dir)/temp
-$(INTERNAL_PTS_TARGET): $(pts_dir)/all_pts_files_stamp $(DEFAULT_TEST_PLAN)
- $(hide) echo "Package PTS: $@"
- $(hide) cd $(dir $@) && zip -rq $(notdir $@) $(PRIVATE_NAME)
-
-.PHONY: pts
-pts: $(INTERNAL_PTS_TARGET)
-cts: pts
-# generate PTS during CTS build
-ifneq ($(filter cts, $(MAKECMDGOALS)),)
-$(call dist-for-goals,cts,$(INTERNAL_PTS_TARGET))
-endif
-
-define copy-testcase-apk
-
-$(hide) $(ACP) -fp $(call intermediates-dir-for,APPS,$(1))/package.apk \
- $(PRIVATE_DIR)/repository/testcases/$(1).apk
-
-endef
-
-define copy-testcase
-
-$(hide) $(ACP) -fp $(1) $(PRIVATE_DIR)/repository/testcases/$(notdir $1)
-
-endef
diff --git a/suite/pts/build/test_host_java_library.mk b/suite/pts/build/test_host_java_library.mk
deleted file mode 100644
index 3f410d1..0000000
--- a/suite/pts/build/test_host_java_library.mk
+++ /dev/null
@@ -1,38 +0,0 @@
-# Copyright (C) 2012 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-#
-# Builds a host library and defines a rule to generate the associated test
-# package XML needed by PTS.
-#
-
-include $(BUILD_HOST_JAVA_LIBRARY)
-
-pts_library_xml := $(PTS_TESTCASES_OUT)/$(LOCAL_MODULE).xml
-
-$(pts_library_xml): PRIVATE_PATH := $(LOCAL_PATH)/src
-$(pts_library_xml): PRIVATE_TEST_PACKAGE := $(LOCAL_PTS_TEST_PACKAGE)
-$(pts_library_xml): PRIVATE_LIBRARY := $(LOCAL_MODULE)
-$(pts_library_xml): PRIVATE_JAR_PATH := $(LOCAL_MODULE).jar
-$(pts_library_xml): $(HOST_OUT_JAVA_LIBRARIES)/$(LOCAL_MODULE).jar $(PTS_EXPECTATIONS) $(PTS_JAVA_TEST_SCANNER_DOCLET) $(PTS_JAVA_TEST_SCANNER) $(PTS_XML_GENERATOR)
- $(hide) echo Generating test description for host library $(PRIVATE_LIBRARY)
- $(hide) mkdir -p $(PTS_TESTCASES_OUT)
- $(hide) $(PTS_JAVA_TEST_SCANNER) -s $(PRIVATE_PATH) \
- -d $(PTS_JAVA_TEST_SCANNER_DOCLET) | \
- $(PTS_XML_GENERATOR) -t hostSideOnly \
- -j $(PRIVATE_JAR_PATH) \
- -n $(PRIVATE_LIBRARY) \
- -p $(PRIVATE_TEST_PACKAGE) \
- -e $(PTS_EXPECTATIONS) \
- -o $@
diff --git a/suite/pts/build/test_package.mk b/suite/pts/build/test_package.mk
deleted file mode 100644
index f677c3b..0000000
--- a/suite/pts/build/test_package.mk
+++ /dev/null
@@ -1,49 +0,0 @@
-# Copyright (C) 2012 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# copied from cts/build. modified for PTS
-
-# Disable by default so "m pts" will work in emulator builds
-LOCAL_DEX_PREOPT := false
-
-include $(BUILD_PACKAGE)
-
-pts_package_apk := $(PTS_TESTCASES_OUT)/$(LOCAL_PACKAGE_NAME).apk
-pts_package_xml := $(PTS_TESTCASES_OUT)/$(LOCAL_PACKAGE_NAME).xml
-
-$(pts_package_apk): PRIVATE_PACKAGE := $(LOCAL_PACKAGE_NAME)
-$(pts_package_apk): $(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME))/package.apk | $(ACP)
- $(hide) mkdir -p $(PTS_TESTCASES_OUT)
- $(hide) $(ACP) -fp $< $@
-
-$(pts_package_xml): PRIVATE_PATH := $(LOCAL_PATH)
-$(pts_package_xml): PRIVATE_INSTRUMENTATION := $(LOCAL_INSTRUMENTATION_FOR)
-$(pts_package_xml): PRIVATE_PACKAGE := $(LOCAL_PACKAGE_NAME)
-$(pts_package_xml): PRIVATE_TEST_PACKAGE := com.android.pts.$(notdir $(LOCAL_PATH))
-$(pts_package_xml): PRIVATE_MANIFEST := $(LOCAL_PATH)/AndroidManifest.xml
-$(pts_package_xml): PRIVATE_TEST_TYPE := $(if $(LOCAL_PTS_TEST_RUNNER),$(LOCAL_PTS_TEST_RUNNER),'')
-$(pts_package_xml): $(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME))/package.apk $(PTS_EXPECTATIONS) $(PTS_JAVA_TEST_SCANNER_DOCLET) $(PTS_JAVA_TEST_SCANNER) $(PTS_XML_GENERATOR)
- $(hide) echo Generating test description for java package $(PRIVATE_PACKAGE)
- $(hide) mkdir -p $(PTS_TESTCASES_OUT)
- $(hide) $(PTS_JAVA_TEST_SCANNER) \
- -s $(PRIVATE_PATH) \
- -d $(PTS_JAVA_TEST_SCANNER_DOCLET) | \
- $(PTS_XML_GENERATOR) \
- -t $(PRIVATE_TEST_TYPE) \
- -m $(PRIVATE_MANIFEST) \
- -i "$(PRIVATE_INSTRUMENTATION)" \
- -n $(PRIVATE_PACKAGE) \
- -p $(PRIVATE_TEST_PACKAGE) \
- -e $(PTS_EXPECTATIONS) \
- -o $@
diff --git a/suite/pts/hostTests/browser/browserlauncher/Android.mk b/suite/pts/deviceTests/browserbench/Android.mk
similarity index 92%
rename from suite/pts/hostTests/browser/browserlauncher/Android.mk
rename to suite/pts/deviceTests/browserbench/Android.mk
index 0592017..1ce0e49 100644
--- a/suite/pts/hostTests/browser/browserlauncher/Android.mk
+++ b/suite/pts/deviceTests/browserbench/Android.mk
@@ -24,10 +24,10 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_PACKAGE_NAME := PtsDeviceBrowserLauncher
+LOCAL_PACKAGE_NAME := PtsDeviceBrowserBench
LOCAL_SDK_VERSION := 16
-include $(BUILD_PTS_PACKAGE)
+include $(BUILD_CTS_PACKAGE)
include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/suite/pts/hostTests/browser/browserlauncher/AndroidManifest.xml b/suite/pts/deviceTests/browserbench/AndroidManifest.xml
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/AndroidManifest.xml
rename to suite/pts/deviceTests/browserbench/AndroidManifest.xml
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/base.js b/suite/pts/deviceTests/browserbench/assets/octane/base.js
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/base.js
rename to suite/pts/deviceTests/browserbench/assets/octane/base.js
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/box2d.js b/suite/pts/deviceTests/browserbench/assets/octane/box2d.js
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/box2d.js
rename to suite/pts/deviceTests/browserbench/assets/octane/box2d.js
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/code-load.js b/suite/pts/deviceTests/browserbench/assets/octane/code-load.js
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/code-load.js
rename to suite/pts/deviceTests/browserbench/assets/octane/code-load.js
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/crypto.js b/suite/pts/deviceTests/browserbench/assets/octane/crypto.js
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/crypto.js
rename to suite/pts/deviceTests/browserbench/assets/octane/crypto.js
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/css/bootstrap-responsive.css b/suite/pts/deviceTests/browserbench/assets/octane/css/bootstrap-responsive.css
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/css/bootstrap-responsive.css
rename to suite/pts/deviceTests/browserbench/assets/octane/css/bootstrap-responsive.css
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/css/bootstrap.css b/suite/pts/deviceTests/browserbench/assets/octane/css/bootstrap.css
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/css/bootstrap.css
rename to suite/pts/deviceTests/browserbench/assets/octane/css/bootstrap.css
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/css/docs.css b/suite/pts/deviceTests/browserbench/assets/octane/css/docs.css
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/css/docs.css
rename to suite/pts/deviceTests/browserbench/assets/octane/css/docs.css
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/deltablue.js b/suite/pts/deviceTests/browserbench/assets/octane/deltablue.js
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/deltablue.js
rename to suite/pts/deviceTests/browserbench/assets/octane/deltablue.js
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/earley-boyer.js b/suite/pts/deviceTests/browserbench/assets/octane/earley-boyer.js
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/earley-boyer.js
rename to suite/pts/deviceTests/browserbench/assets/octane/earley-boyer.js
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/gbemu.js b/suite/pts/deviceTests/browserbench/assets/octane/gbemu.js
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/gbemu.js
rename to suite/pts/deviceTests/browserbench/assets/octane/gbemu.js
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/ico/apple-touch-icon-114-precomposed.png b/suite/pts/deviceTests/browserbench/assets/octane/ico/apple-touch-icon-114-precomposed.png
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/ico/apple-touch-icon-114-precomposed.png
rename to suite/pts/deviceTests/browserbench/assets/octane/ico/apple-touch-icon-114-precomposed.png
Binary files differ
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/ico/apple-touch-icon-144-precomposed.png b/suite/pts/deviceTests/browserbench/assets/octane/ico/apple-touch-icon-144-precomposed.png
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/ico/apple-touch-icon-144-precomposed.png
rename to suite/pts/deviceTests/browserbench/assets/octane/ico/apple-touch-icon-144-precomposed.png
Binary files differ
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/ico/apple-touch-icon-57-precomposed.png b/suite/pts/deviceTests/browserbench/assets/octane/ico/apple-touch-icon-57-precomposed.png
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/ico/apple-touch-icon-57-precomposed.png
rename to suite/pts/deviceTests/browserbench/assets/octane/ico/apple-touch-icon-57-precomposed.png
Binary files differ
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/ico/apple-touch-icon-72-precomposed.png b/suite/pts/deviceTests/browserbench/assets/octane/ico/apple-touch-icon-72-precomposed.png
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/ico/apple-touch-icon-72-precomposed.png
rename to suite/pts/deviceTests/browserbench/assets/octane/ico/apple-touch-icon-72-precomposed.png
Binary files differ
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/ico/favicon.ico b/suite/pts/deviceTests/browserbench/assets/octane/ico/favicon.ico
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/ico/favicon.ico
rename to suite/pts/deviceTests/browserbench/assets/octane/ico/favicon.ico
Binary files differ
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/index.html b/suite/pts/deviceTests/browserbench/assets/octane/index.html
similarity index 98%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/index.html
rename to suite/pts/deviceTests/browserbench/assets/octane/index.html
index 0e2a79d..704123f 100644
--- a/suite/pts/hostTests/browser/browserlauncher/assets/octane/index.html
+++ b/suite/pts/deviceTests/browserbench/assets/octane/index.html
@@ -15,6 +15,8 @@
<script src="js/bootstrap-transition.js"></script>
<script src="js/bootstrap-collapse.js"></script>
<!-- Octane benchmark code -->
+<!-- PTS -->
+<script type="text/javascript" src="pts_report.js"></script>
<script type="text/javascript" src="base.js"></script>
<script type="text/javascript" src="richards.js"></script>
<script type="text/javascript" src="deltablue.js"></script>
@@ -43,7 +45,8 @@
}
function AddResult(name, result) {
- console.log(name + ': ' + result);
+ //PTS
+ PtsReport(name, result, false);
var box = document.getElementById("Result-" + name);
box.innerHTML = result;
}
@@ -65,8 +68,8 @@
} else {
status.innerHTML = "Octane Score (incomplete): " + score;
}
- // print the result for the host
- console.log(status.innerHTML);
+ //PTS
+ PtsReport("Octane Score" + (success ? " " : "(incomplete)"), score, true);
document.getElementById("progress-bar-container").style.visibility = 'hidden';
document.getElementById("bottom-text").style.visibility = 'visible';
document.getElementById("inside-anchor").removeChild(document.getElementById("bar-appendix"));
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/js/bootstrap-collapse.js b/suite/pts/deviceTests/browserbench/assets/octane/js/bootstrap-collapse.js
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/js/bootstrap-collapse.js
rename to suite/pts/deviceTests/browserbench/assets/octane/js/bootstrap-collapse.js
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/js/bootstrap-transition.js b/suite/pts/deviceTests/browserbench/assets/octane/js/bootstrap-transition.js
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/js/bootstrap-transition.js
rename to suite/pts/deviceTests/browserbench/assets/octane/js/bootstrap-transition.js
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/js/jquery.js b/suite/pts/deviceTests/browserbench/assets/octane/js/jquery.js
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/js/jquery.js
rename to suite/pts/deviceTests/browserbench/assets/octane/js/jquery.js
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/mandreel.js b/suite/pts/deviceTests/browserbench/assets/octane/mandreel.js
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/mandreel.js
rename to suite/pts/deviceTests/browserbench/assets/octane/mandreel.js
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/navier-stokes.js b/suite/pts/deviceTests/browserbench/assets/octane/navier-stokes.js
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/navier-stokes.js
rename to suite/pts/deviceTests/browserbench/assets/octane/navier-stokes.js
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/pdfjs.js b/suite/pts/deviceTests/browserbench/assets/octane/pdfjs.js
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/pdfjs.js
rename to suite/pts/deviceTests/browserbench/assets/octane/pdfjs.js
diff --git a/suite/pts/deviceTests/browserbench/assets/octane/pts_report.html b/suite/pts/deviceTests/browserbench/assets/octane/pts_report.html
new file mode 100644
index 0000000..40e2de6
--- /dev/null
+++ b/suite/pts/deviceTests/browserbench/assets/octane/pts_report.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<html>
+ <head>
+ <title>pts_report</title>
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/suite/pts/deviceTests/browserbench/assets/octane/pts_report.js b/suite/pts/deviceTests/browserbench/assets/octane/pts_report.js
new file mode 100644
index 0000000..fb320ae
--- /dev/null
+++ b/suite/pts/deviceTests/browserbench/assets/octane/pts_report.js
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Utility to report benchmarking result via HTTP POST
+ * to PTS.
+ * @param msg message to add to the report
+ * @param score resulting score
+ * @param isFinal true if this is the last / final score
+ */
+function PtsReport(msg, score, isFinal)
+{
+ req = new XMLHttpRequest();
+ req.open("POST", "pts_report.html?final=" + (isFinal ? "1" : "0") +
+ "&score=" + score + "&message=" + msg, false);
+ req.send(null)
+}
+
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/raytrace.js b/suite/pts/deviceTests/browserbench/assets/octane/raytrace.js
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/raytrace.js
rename to suite/pts/deviceTests/browserbench/assets/octane/raytrace.js
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/regexp.js b/suite/pts/deviceTests/browserbench/assets/octane/regexp.js
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/regexp.js
rename to suite/pts/deviceTests/browserbench/assets/octane/regexp.js
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/richards.js b/suite/pts/deviceTests/browserbench/assets/octane/richards.js
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/richards.js
rename to suite/pts/deviceTests/browserbench/assets/octane/richards.js
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/run.js b/suite/pts/deviceTests/browserbench/assets/octane/run.js
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/run.js
rename to suite/pts/deviceTests/browserbench/assets/octane/run.js
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/octane/splay.js b/suite/pts/deviceTests/browserbench/assets/octane/splay.js
similarity index 100%
rename from suite/pts/hostTests/browser/browserlauncher/assets/octane/splay.js
rename to suite/pts/deviceTests/browserbench/assets/octane/splay.js
diff --git a/suite/pts/deviceTests/browserbench/src/com/android/pts/browser/BrowserBenchTest.java b/suite/pts/deviceTests/browserbench/src/com/android/pts/browser/BrowserBenchTest.java
new file mode 100644
index 0000000..07f7763
--- /dev/null
+++ b/suite/pts/deviceTests/browserbench/src/com/android/pts/browser/BrowserBenchTest.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.pts.browser;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.cts.util.TimeoutReq;
+import android.cts.util.WatchDog;
+import android.net.Uri;
+import android.provider.Browser;
+import android.util.Log;
+import android.webkit.cts.CtsTestServer;
+
+import com.android.pts.util.PtsAndroidTestCase;
+import com.android.pts.util.ResultType;
+import com.android.pts.util.ResultUnit;
+import com.android.pts.util.Stat;
+
+import java.net.URLDecoder;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.RequestLine;
+/**
+ * Browser benchmarking.
+ * It launches an activity with URL and wait for POST from the client.
+ */
+public class BrowserBenchTest extends PtsAndroidTestCase {
+ private static final String TAG = "BrowserBench";
+ private static final boolean DEBUG = false;
+ private static final String OCTANE_START_FILE = "octane/index.html";
+ private static final String ROBOHORNET_START_FILE = "robohornet/robohornet.html";
+ private static final String HOST_COMPLETION_BROADCAST = "com.android.pts.browser.completion";
+ // time-out for watch-dog. POST should happen within this time.
+ private static long BROWSER_POST_TIMEOUT_IN_MS = 10 * 60 * 1000L;
+ // watch-dog will time-out first. So make it long enough.
+ private static long BROWSER_COMPLETION_TIMEOUT_IN_MS = 60 * 60 * 1000L;
+ private static final String HTTP_USER_AGENT = "User-Agent";
+ private CtsTestServer mWebServer;
+ // used for final score
+ private ResultType mTypeNonFinal = ResultType.NEUTRAL;
+ private ResultUnit mUnitNonFinal = ResultUnit.NONE;
+ // used for all other scores
+ private ResultType mTypeFinal = ResultType.NEUTRAL;
+ private ResultUnit mUnitFinal = ResultUnit.SCORE;
+ private WatchDog mWatchDog;
+ private CountDownLatch mLatch;
+ // can be changed by each test before starting
+ private volatile int mNumberRepeat;
+ /** tells how many tests have run up to now */
+ private volatile int mRunIndex;
+ /** stores results for each runs. last entry will be the final score. */
+ private LinkedHashMap<String, double[]> mResultsMap;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mWebServer = new CtsTestServer(getContext()) {
+ @Override
+ protected HttpResponse onPost(HttpRequest request) throws Exception {
+ // post uri will look like "pts_report.html?final=1&score=10.1&message=hello"
+ RequestLine requestLine = request.getRequestLine();
+ String uriString = URLDecoder.decode(requestLine.getUri(), "UTF-8");
+ if (DEBUG) {
+ Log.i(TAG, "uri:" + uriString);
+ }
+ String resultRe =
+ ".*pts_report.html\\?final=([\\d])&score=([\\d]+\\.?[\\d]*)&message=([\\w][\\w ]*)";
+ Pattern resultPattern = Pattern.compile(resultRe);
+ Matcher matchResult = resultPattern.matcher(uriString);
+ if (matchResult.find()) {
+ int isFinal = Integer.parseInt(matchResult.group(1));
+ double score = Double.parseDouble(matchResult.group(2));
+ String message = matchResult.group(3);
+ Log.i(TAG, message + ":" + score);
+ if (!mResultsMap.containsKey(message)) {
+ mResultsMap.put(message, new double[mNumberRepeat]);
+ }
+ double[] scores = mResultsMap.get(message);
+ scores[mRunIndex] = score;
+ if (isFinal == 1) {
+ String userAgent = request.getFirstHeader(HTTP_USER_AGENT).getValue();
+ getReportLog().printValue(HTTP_USER_AGENT + "=" + userAgent, 0,
+ ResultType.NEUTRAL, ResultUnit.NONE);
+ mLatch.countDown();
+ }
+ mWatchDog.reset();
+ }
+ return null; // default response is OK as it will be ignored by client anyway.
+ }
+ };
+ mResultsMap = new LinkedHashMap<String, double[]>();
+ mWatchDog = new WatchDog(BROWSER_POST_TIMEOUT_IN_MS);
+ mWatchDog.start();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ mWatchDog.stop();
+ mWebServer.shutdown();
+ mWebServer = null;
+ mResultsMap = null;
+ super.tearDown();
+ }
+
+ @TimeoutReq(minutes = 60)
+ public void testOctane() throws InterruptedException {
+ String url = mWebServer.getAssetUrl(OCTANE_START_FILE) + "?auto=1";
+ final int kRepeat = 5;
+ doTest(url, ResultType.LOWER_BETTER, ResultUnit.MS,
+ ResultType.HIGHER_BETTER, ResultUnit.SCORE, kRepeat);
+ }
+
+ private void doTest(String url, ResultType typeNonFinal, ResultUnit unitNonFinal,
+ ResultType typeFinal, ResultUnit unitFinal, int numberRepeat)
+ throws InterruptedException {
+ mTypeNonFinal = typeNonFinal;
+ mUnitNonFinal = unitNonFinal;
+ mTypeFinal = typeFinal;
+ mUnitFinal = unitFinal;
+ mNumberRepeat = numberRepeat;
+ Uri uri = Uri.parse(url);
+ for (mRunIndex = 0; mRunIndex < numberRepeat; mRunIndex++) {
+ Log.i(TAG, mRunIndex + "-th round");
+ mLatch = new CountDownLatch(1);
+ Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ // force using only one window or tab
+ intent.putExtra(Browser.EXTRA_APPLICATION_ID, getContext().getPackageName());
+ getContext().startActivity(intent);
+ boolean ok = mLatch.await(BROWSER_COMPLETION_TIMEOUT_IN_MS, TimeUnit.MILLISECONDS);
+ assertTrue("timed-out", ok);
+ }
+ // it is somewhat awkward to handle the last one specially with Map
+ int numberEntries = mResultsMap.size();
+ int numberToProcess = 1;
+ for (Map.Entry<String, double[]> entry : mResultsMap.entrySet()) {
+ String message = entry.getKey();
+ double[] scores = entry.getValue();
+ if (numberToProcess == numberEntries) { // final score
+ // store the whole results first
+ getReportLog().printArray(message, scores, mTypeFinal, mUnitFinal);
+ getReportLog().printSummary(message, Stat.getAverage(scores), mTypeFinal,
+ mUnitFinal);
+ } else { // interim results
+ getReportLog().printArray(message, scores, mTypeNonFinal, mUnitNonFinal);
+ }
+ numberToProcess++;
+ }
+ }
+}
diff --git a/suite/pts/deviceTests/dram/Android.mk b/suite/pts/deviceTests/dram/Android.mk
index dbe0ad3..47e5b52 100644
--- a/suite/pts/deviceTests/dram/Android.mk
+++ b/suite/pts/deviceTests/dram/Android.mk
@@ -30,6 +30,6 @@
LOCAL_SDK_VERSION := 16
-include $(BUILD_PTS_PACKAGE)
+include $(BUILD_CTS_PACKAGE)
include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/suite/pts/deviceTests/dram/jni/MemoryNativeJni.cpp b/suite/pts/deviceTests/dram/jni/MemoryNativeJni.cpp
index 0c429b7..a54ab71 100644
--- a/suite/pts/deviceTests/dram/jni/MemoryNativeJni.cpp
+++ b/suite/pts/deviceTests/dram/jni/MemoryNativeJni.cpp
@@ -19,14 +19,14 @@
#include <string.h>
#include <sys/time.h>
-long currentTimeMillis()
+double currentTimeMillis()
{
struct timeval tv;
gettimeofday(&tv, (struct timezone *) NULL);
- return (long)tv.tv_sec * 1000 + tv.tv_usec / 1000;
+ return tv.tv_sec * 1000.0 + tv.tv_usec / 1000.0;
}
-extern "C" JNIEXPORT jlong JNICALL Java_com_android_pts_dram_MemoryNative_runMemcpy(JNIEnv* env,
+extern "C" JNIEXPORT jdouble JNICALL Java_com_android_pts_dram_MemoryNative_runMemcpy(JNIEnv* env,
jclass clazz, jint bufferSize, jint repetition)
{
char* src = new char[bufferSize];
@@ -39,13 +39,33 @@
}
memset(src, 0, bufferSize);
memset(dst, 0, bufferSize);
- long start = currentTimeMillis();
+ double start = currentTimeMillis();
for (int i = 0; i < repetition; i++) {
memcpy(dst, src, bufferSize);
src[bufferSize - 1] = i & 0xff;
}
- long end = currentTimeMillis();
+ double end = currentTimeMillis();
delete[] src;
delete[] dst;
return end - start;
}
+
+extern "C" JNIEXPORT jdouble JNICALL Java_com_android_pts_dram_MemoryNative_runMemset(JNIEnv* env,
+ jclass clazz, jint bufferSize, jint repetition, jint c)
+{
+ char* dst = new char[bufferSize];
+ if (dst == NULL) {
+ delete[] dst;
+ env->ThrowNew(env->FindClass("java/lang/OutOfMemoryError"), "No memory");
+ return -1;
+ }
+ memset(dst, 0, bufferSize);
+ double start = currentTimeMillis();
+ for (int i = 0; i < repetition; i++) {
+ memset(dst, (c + i) & 0xff, bufferSize);
+ }
+ double end = currentTimeMillis();
+ delete[] dst;
+ return end - start;
+}
+
diff --git a/suite/pts/deviceTests/dram/src/com/android/pts/dram/BandwidthTest.java b/suite/pts/deviceTests/dram/src/com/android/pts/dram/BandwidthTest.java
index c731cc1..8f6860f 100644
--- a/suite/pts/deviceTests/dram/src/com/android/pts/dram/BandwidthTest.java
+++ b/suite/pts/deviceTests/dram/src/com/android/pts/dram/BandwidthTest.java
@@ -21,6 +21,8 @@
import android.util.Log;
import android.view.WindowManager;
+import com.android.pts.util.ResultType;
+import com.android.pts.util.ResultUnit;
import com.android.pts.util.PtsAndroidTestCase;
import com.android.pts.util.ReportLog;
import com.android.pts.util.Stat;
@@ -37,6 +39,14 @@
private static final int REPEAT_IN_EACH_CALL = 100;
private static final int KB = 1024;
private static final int MB = 1024 * 1024;
+ private static final int MEMSET_CHAR = 0xa5;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ // warm-up
+ MemoryNative.runMemcpy(2 * MB, 100);
+ }
public void testMemcpyK004() {
doRunMemcpy(4 * KB);
@@ -90,15 +100,74 @@
doRunMemcpy(16 * MB);
}
+ public void testMemsetK004() {
+ doRunMemset(4 * KB);
+ }
+
+ public void testMemsetK008() {
+ doRunMemset(8 * KB);
+ }
+
+ public void testMemsetK016() {
+ doRunMemset(16 * KB);
+ }
+
+ public void testMemsetK032() {
+ doRunMemset(32 * KB);
+ }
+
+ public void testMemsetK064() {
+ doRunMemset(64 * KB);
+ }
+
+ public void testMemsetK128() {
+ doRunMemset(128 * KB);
+ }
+
+ public void testMemsetK256() {
+ doRunMemset(256 * KB);
+ }
+
+ public void testMemsetK512() {
+ doRunMemset(512 * KB);
+ }
+
+ public void testMemsetM001() {
+ doRunMemset(1 * MB);
+ }
+
+ public void testMemsetM002() {
+ doRunMemset(2 * MB);
+ }
+
+ public void testMemsetM004() {
+ doRunMemset(4 * MB);
+ }
+
+ public void testMemsetM008() {
+ doRunMemset(8 * MB);
+ }
+
+ public void testMemsetM016() {
+ doRunMemset(16 * MB);
+ }
+
private void doRunMemcpy(int bufferSize) {
double[] result = new double[REPETITION];
- for (int i = 0; i < REPETITION; i++) {
- result[i] = MemoryNative.runMemcpy(bufferSize, REPEAT_IN_EACH_CALL);
+ int repeatInEachCall = REPEAT_IN_EACH_CALL;
+ if (bufferSize < (1 * MB)) {
+ // too small buffer size finishes too early to give accurate result.
+ repeatInEachCall *= (1 * MB / bufferSize);
}
- getReportLog().printArray("ms", result, false);
+ for (int i = 0; i < REPETITION; i++) {
+ result[i] = MemoryNative.runMemcpy(bufferSize, repeatInEachCall);
+ }
+ getReportLog().printArray("memcpy time", result, ResultType.LOWER_BETTER,
+ ResultUnit.MS);
double[] mbps = ReportLog.calcRatePerSecArray(
- (double)bufferSize * REPEAT_IN_EACH_CALL / 1024.0 / 1024.0, result);
- getReportLog().printArray("MB/s", mbps, true);
+ (double)bufferSize * repeatInEachCall / 1024.0 / 1024.0, result);
+ getReportLog().printArray("memcpy throughput", mbps, ResultType.HIGHER_BETTER,
+ ResultUnit.MBPS);
Stat.StatResult stat = Stat.getStat(mbps);
WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
Point size = new Point();
@@ -107,8 +176,39 @@
double pixels = size.x * size.y;
// now this represents how many times the whole screen can be copied in a sec.
double screensPerSecAverage = stat.mAverage / pixels * 1024.0 * 1024.0 / 4.0;
- double screensPerSecStddev = stat.mStddev / pixels * 1024.0 * 1024.0 / 4.0;
- getReportLog().printSummary("screen copies per sec", screensPerSecAverage,
- screensPerSecStddev);
+ getReportLog().printValue("memcpy in fps", screensPerSecAverage,
+ ResultType.HIGHER_BETTER, ResultUnit.FPS);
+ getReportLog().printSummary("memcpy throughput", stat.mAverage, ResultType.HIGHER_BETTER,
+ ResultUnit.MBPS);
+ }
+
+ private void doRunMemset(int bufferSize) {
+ double[] result = new double[REPETITION];
+ int repeatInEachCall = REPEAT_IN_EACH_CALL;
+ if (bufferSize < (1 * MB)) {
+ // too small buffer size finishes too early to give accurate result.
+ repeatInEachCall *= (1 * MB / bufferSize);
+ }
+ for (int i = 0; i < REPETITION; i++) {
+ result[i] = MemoryNative.runMemset(bufferSize, repeatInEachCall, MEMSET_CHAR);
+ }
+ getReportLog().printArray("memset time", result, ResultType.LOWER_BETTER,
+ ResultUnit.MS);
+ double[] mbps = ReportLog.calcRatePerSecArray(
+ (double)bufferSize * repeatInEachCall / 1024.0 / 1024.0, result);
+ getReportLog().printArray("memset throughput", mbps, ResultType.HIGHER_BETTER,
+ ResultUnit.MBPS);
+ Stat.StatResult stat = Stat.getStat(mbps);
+ WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
+ Point size = new Point();
+ wm.getDefaultDisplay().getSize(size);
+ Log.i(TAG, " x " + size.x + " y " + size.y);
+ double pixels = size.x * size.y;
+ // now this represents how many times the whole screen can be copied in a sec.
+ double screensPerSecAverage = stat.mAverage / pixels * 1024.0 * 1024.0 / 4.0;
+ getReportLog().printValue("memset in fps", screensPerSecAverage,
+ ResultType.HIGHER_BETTER, ResultUnit.FPS);
+ getReportLog().printSummary("memset throughput", stat.mAverage, ResultType.HIGHER_BETTER,
+ ResultUnit.MBPS);
}
}
diff --git a/suite/pts/deviceTests/dram/src/com/android/pts/dram/MemoryNative.java b/suite/pts/deviceTests/dram/src/com/android/pts/dram/MemoryNative.java
index 991de1e..71b60a0 100644
--- a/suite/pts/deviceTests/dram/src/com/android/pts/dram/MemoryNative.java
+++ b/suite/pts/deviceTests/dram/src/com/android/pts/dram/MemoryNative.java
@@ -27,5 +27,15 @@
* @param repeatition
* @return time spent in copying in ms.
*/
- public static native long runMemcpy(int bufferSize, int repetition);
+ public static native double runMemcpy(int bufferSize, int repetition);
+
+ /**
+ * run memset for given number of repetition from a source to a destination buffers
+ * with each having the size of bufferSize.
+ * @param bufferSize
+ * @param repetition
+ * @param c char to set. Only LSBs will be used to get char value.
+ * @return time spent in memset in ms.
+ */
+ public static native double runMemset(int bufferSize, int repetition, int c);
}
diff --git a/suite/pts/deviceTests/filesystemperf/Android.mk b/suite/pts/deviceTests/filesystemperf/Android.mk
index b42d48e..ca7996c 100644
--- a/suite/pts/deviceTests/filesystemperf/Android.mk
+++ b/suite/pts/deviceTests/filesystemperf/Android.mk
@@ -28,5 +28,5 @@
LOCAL_SDK_VERSION := 16
-include $(BUILD_PTS_PACKAGE)
+include $(BUILD_CTS_PACKAGE)
diff --git a/suite/pts/deviceTests/filesystemperf/src/com/android/pts/filesystemperf/FileUtil.java b/suite/pts/deviceTests/filesystemperf/src/com/android/pts/filesystemperf/FileUtil.java
index 14fc45a..087cf0d 100644
--- a/suite/pts/deviceTests/filesystemperf/src/com/android/pts/filesystemperf/FileUtil.java
+++ b/suite/pts/deviceTests/filesystemperf/src/com/android/pts/filesystemperf/FileUtil.java
@@ -19,7 +19,6 @@
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
-import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
@@ -28,6 +27,8 @@
import com.android.pts.util.MeasureRun;
import com.android.pts.util.MeasureTime;
+import com.android.pts.util.ResultType;
+import com.android.pts.util.ResultUnit;
import com.android.pts.util.ReportLog;
import com.android.pts.util.Stat;
import com.android.pts.util.SystemUtil;
@@ -298,12 +299,14 @@
randomFile.close();
double[] mbps = ReportLog.calcRatePerSecArray((double)fileSize / runsInOneGo / 1024 / 1024,
times);
- report.printArray("MB/s",
- mbps, true);
- report.printArray("Rd amount", rdAmount, true);
+ report.printArray("read throughput",
+ mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS);
+ // This is just the amount of IO returned from kernel. So this is performance neutral.
+ report.printArray("read amount", rdAmount, ResultType.NEUTRAL, ResultUnit.BYTE);
Stat.StatResult stat = Stat.getStat(mbps);
- report.printSummary("MB/s", stat.mAverage, stat.mStddev);
+ report.printSummary("read throughput", stat.mAverage, ResultType.HIGHER_BETTER,
+ ResultUnit.MBPS);
}
/**
@@ -348,12 +351,14 @@
randomFile.close();
double[] mbps = ReportLog.calcRatePerSecArray((double)fileSize / runsInOneGo / 1024 / 1024,
times);
- report.printArray("MB/s",
- mbps, true);
- report.printArray("Wr amount", wrAmount, true);
+ report.printArray("write throughput",
+ mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS);
+ report.printArray("write amount", wrAmount, ResultType.NEUTRAL,
+ ResultUnit.BYTE);
Stat.StatResult stat = Stat.getStat(mbps);
- report.printSummary("MB/s", stat.mAverage, stat.mStddev);
+ report.printSummary("write throughput", stat.mAverage, ResultType.HIGHER_BETTER,
+ ResultUnit.MBPS);
}
/**
@@ -386,11 +391,12 @@
randomFile.close();
double[] mbps = ReportLog.calcRatePerSecArray((double)bufferSize / 1024 / 1024,
times);
- report.printArray(i + "-th round MB/s",
- mbps, true);
+ report.printArray(i + "-th round throughput",
+ mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS);
ReportLog.copyArray(mbps, mbpsAll, i * numberRepeatInOneRun);
}
Stat.StatResult stat = Stat.getStat(mbpsAll);
- report.printSummary("MB/s", stat.mAverage, stat.mStddev);
+ report.printSummary("update throughput", stat.mAverage, ResultType.HIGHER_BETTER,
+ ResultUnit.MBPS);
}
}
diff --git a/suite/pts/deviceTests/filesystemperf/src/com/android/pts/filesystemperf/SequentialRWTest.java b/suite/pts/deviceTests/filesystemperf/src/com/android/pts/filesystemperf/SequentialRWTest.java
index 252c59e..4a9c3dd 100644
--- a/suite/pts/deviceTests/filesystemperf/src/com/android/pts/filesystemperf/SequentialRWTest.java
+++ b/suite/pts/deviceTests/filesystemperf/src/com/android/pts/filesystemperf/SequentialRWTest.java
@@ -19,6 +19,8 @@
import android.cts.util.TimeoutReq;
import com.android.pts.util.MeasureRun;
import com.android.pts.util.MeasureTime;
+import com.android.pts.util.ResultType;
+import com.android.pts.util.ResultUnit;
import com.android.pts.util.PtsAndroidTestCase;
import com.android.pts.util.ReportLog;
import com.android.pts.util.Stat;
@@ -47,7 +49,8 @@
public void testSingleSequentialWrite() throws Exception {
final int numberOfFiles =(int)(FileUtil.getFileSizeExceedingMemory(
getContext(), BUFFER_SIZE) / BUFFER_SIZE);
- getReportLog().printValue("files", numberOfFiles);
+ getReportLog().printValue("files", numberOfFiles, ResultType.NEUTRAL,
+ ResultUnit.COUNT);
final byte[] data = FileUtil.generateRandomData(BUFFER_SIZE);
final File[] files = FileUtil.createNewFiles(getContext(), DIR_SEQ_WR,
numberOfFiles);
@@ -61,11 +64,13 @@
}
});
double[] mbps = ReportLog.calcRatePerSecArray((double)BUFFER_SIZE / 1024 / 1024, times);
- getReportLog().printArray("try " + numberOfFiles + " files, result MB/s",
- mbps, true);
- getReportLog().printArray("Wr amount", wrAmount, true);
+ getReportLog().printArray("write throughput",
+ mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS);
+ getReportLog().printArray("write amount", wrAmount, ResultType.NEUTRAL,
+ ResultUnit.BYTE);
Stat.StatResult stat = Stat.getStat(mbps);
- getReportLog().printSummary("MB/s", stat.mAverage, stat.mStddev);
+ getReportLog().printSummary("write throughput", stat.mAverage, ResultType.HIGHER_BETTER,
+ ResultUnit.MBPS);
}
@TimeoutReq(minutes = 60)
@@ -83,8 +88,9 @@
final File file = FileUtil.createNewFilledFile(getContext(),
DIR_SEQ_RD, fileSize);
long finish = System.currentTimeMillis();
- getReportLog().printValue("write size " + fileSize + " result MB/s",
- ReportLog.calcRatePerSec((double)fileSize / 1024 / 1024, finish - start));
+ getReportLog().printValue("write throughput for test file of length " + fileSize,
+ ReportLog.calcRatePerSec((double)fileSize / 1024 / 1024, finish - start),
+ ResultType.HIGHER_BETTER, ResultUnit.MBPS);
final int NUMBER_READ = 10;
@@ -103,9 +109,10 @@
}
});
double[] mbps = ReportLog.calcRatePerSecArray((double)fileSize / 1024 / 1024, times);
- getReportLog().printArray("read MB/s",
- mbps, true);
+ getReportLog().printArray("read throughput",
+ mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS);
Stat.StatResult stat = Stat.getStat(mbps);
- getReportLog().printSummary("MB/s", stat.mAverage, stat.mStddev);
+ getReportLog().printSummary("read throughput", stat.mAverage, ResultType.HIGHER_BETTER,
+ ResultUnit.MBPS);
}
}
diff --git a/suite/pts/hostTests/browser/browserlauncher/Android.mk b/suite/pts/deviceTests/opengl/Android.mk
similarity index 78%
copy from suite/pts/hostTests/browser/browserlauncher/Android.mk
copy to suite/pts/deviceTests/opengl/Android.mk
index 0592017..9346724 100644
--- a/suite/pts/hostTests/browser/browserlauncher/Android.mk
+++ b/suite/pts/deviceTests/opengl/Android.mk
@@ -1,4 +1,4 @@
-# Copyright (C) 2012 The Android Open Source Project
+# Copyright (C) 2013 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -20,14 +20,16 @@
LOCAL_JAVA_LIBRARIES := android.test.runner
-LOCAL_STATIC_JAVA_LIBRARIES := ptsutil ctsutil ctstestrunner ctstestserver
+LOCAL_STATIC_JAVA_LIBRARIES := ptsutil ctsutil ctstestrunner
+
+LOCAL_JNI_SHARED_LIBRARIES := libptsopengl_jni
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_PACKAGE_NAME := PtsDeviceBrowserLauncher
+LOCAL_PACKAGE_NAME := PtsDeviceOpenGl
LOCAL_SDK_VERSION := 16
-include $(BUILD_PTS_PACKAGE)
+include $(BUILD_CTS_PACKAGE)
-include $(call all-makefiles-under,$(LOCAL_PATH))
+include $(call all-makefiles-under,$(LOCAL_PATH))
\ No newline at end of file
diff --git a/suite/pts/deviceTests/opengl/AndroidManifest.xml b/suite/pts/deviceTests/opengl/AndroidManifest.xml
new file mode 100644
index 0000000..a91766f
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.pts.opengl"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-sdk
+ android:minSdkVersion="16" />
+
+ <uses-feature
+ android:glEsVersion="0x00020000"
+ android:required="true" />
+
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />\
+ <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+
+ <application android:allowBackup="false" >
+ <uses-library android:name="android.test.runner" />
+
+ <activity
+ android:name="com.android.pts.opengl.primitive.GLActivity"
+ android:theme="@android:style/Theme.NoTitleBar.Fullscreen" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+ <instrumentation
+ android:name="android.test.InstrumentationCtsTestRunner"
+ android:label="OpenGL ES 2.0 Benchmark"
+ android:targetPackage="com.android.pts.opengl" />
+
+</manifest>
\ No newline at end of file
diff --git a/suite/pts/hostTests/ptshostutil/Android.mk b/suite/pts/deviceTests/opengl/jni/Android.mk
similarity index 63%
copy from suite/pts/hostTests/ptshostutil/Android.mk
copy to suite/pts/deviceTests/opengl/jni/Android.mk
index 14e786a..7ab7f89 100644
--- a/suite/pts/hostTests/ptshostutil/Android.mk
+++ b/suite/pts/deviceTests/opengl/jni/Android.mk
@@ -1,4 +1,4 @@
-# Copyright (C) 2012 The Android Open Source Project
+# Copyright (C) 2013 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -11,19 +11,21 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-
+#
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_SRC_FILES := \
- $(call all-java-files-under, src)
-
-LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed-prebuilt ddmlib-prebuilt junit
+LOCAL_MODULE := libptsopengl_jni
LOCAL_MODULE_TAGS := optional
-LOCAL_MODULE := ptshostutil
+# Get all cpp files but not hidden files
+LOCAL_SRC_FILES := $(patsubst ./%,%, $(shell cd $(LOCAL_PATH); \
+ find . -name "*.cpp" -and -not -name ".*"))
-include $(BUILD_HOST_JAVA_LIBRARY)
+LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
+LOCAL_SHARED_LIBRARIES := libEGL libGLESv2 libandroid libutils
+
+include $(BUILD_SHARED_LIBRARY)
\ No newline at end of file
diff --git a/suite/pts/deviceTests/opengl/jni/GLNative.cpp b/suite/pts/deviceTests/opengl/jni/GLNative.cpp
new file mode 100644
index 0000000..bd132b9
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/GLNative.cpp
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+#include <jni.h>
+
+#include <stdlib.h>
+#include <sys/time.h>
+
+#include <android/native_window.h>
+#include <android/native_window_jni.h>
+
+#include "Renderer.h"
+#include "fullpipeline/FullPipelineRenderer.h"
+#include "pixeloutput/PixelOutputRenderer.h"
+#include "shaderperf/ShaderPerfRenderer.h"
+#include "contextswitch/ContextSwitchRenderer.h"
+
+// Holds the current benchmark's renderer.
+Renderer* gRenderer = NULL;
+
+double currentTimeMillis() {
+ struct timeval tv;
+ gettimeofday(&tv, (struct timezone *) NULL);
+ return tv.tv_sec * 1000.0 + tv.tv_usec / 1000.0;
+}
+
+extern "C" JNIEXPORT jboolean JNICALL
+Java_com_android_pts_opengl_primitive_GLActivity_startBenchmark(
+ JNIEnv* env, jclass clazz, jint numFrames, jdoubleArray frameTimes) {
+ if (gRenderer == NULL) {
+ return false;
+ }
+
+ // Sets up the renderer.
+ bool success = gRenderer->setUp();
+
+ // Records the start time.
+ double start = currentTimeMillis();
+
+ // Draw off the screen.
+ for (int i = 0; i < numFrames && success; i++) {
+ // Draw a frame.
+ success = gRenderer->draw();
+ }
+
+ // Records the end time.
+ double end = currentTimeMillis();
+
+ // Sets the times in the Java array.
+ double times[] = {start, end};
+ env->SetDoubleArrayRegion(frameTimes, 0, 2, times);
+
+ // Tears down and deletes the renderer.
+ success = gRenderer->tearDown() && success;
+ delete gRenderer;
+ gRenderer = NULL;
+ return success;
+}
+
+// The following functions create the renderers for the various benchmarks.
+extern "C" JNIEXPORT void JNICALL
+Java_com_android_pts_opengl_primitive_GLActivity_setupFullPipelineBenchmark(
+ JNIEnv* env, jclass clazz, jobject surface, jboolean offscreen, jint workload) {
+ gRenderer = new FullPipelineRenderer(
+ ANativeWindow_fromSurface(env, surface), offscreen, workload);
+}
+
+extern "C" JNIEXPORT void JNICALL
+Java_com_android_pts_opengl_primitive_GLActivity_setupPixelOutputBenchmark(
+ JNIEnv* env, jclass clazz, jobject surface, jboolean offscreen, jint workload) {
+ gRenderer = new PixelOutputRenderer(
+ ANativeWindow_fromSurface(env, surface), offscreen, workload);
+}
+
+extern "C" JNIEXPORT void JNICALL
+Java_com_android_pts_opengl_primitive_GLActivity_setupShaderPerfBenchmark(
+ JNIEnv* env, jclass clazz, jobject surface, jboolean offscreen, jint workload) {
+ gRenderer = new ShaderPerfRenderer(
+ ANativeWindow_fromSurface(env, surface), offscreen, workload);
+}
+
+extern "C" JNIEXPORT void JNICALL
+Java_com_android_pts_opengl_primitive_GLActivity_setupContextSwitchBenchmark(
+ JNIEnv* env, jclass clazz, jobject surface, jboolean offscreen, jint workload) {
+ gRenderer = new ContextSwitchRenderer(
+ ANativeWindow_fromSurface(env, surface), offscreen, workload);
+}
diff --git a/suite/pts/deviceTests/opengl/jni/GLUtils.cpp b/suite/pts/deviceTests/opengl/jni/GLUtils.cpp
new file mode 100644
index 0000000..f62f5b8
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/GLUtils.cpp
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+#include <GLUtils.h>
+#include <stdlib.h>
+
+// Loads the given source code as a shader of the given type.
+static GLuint loadShader(GLenum shaderType, const char** source) {
+ GLuint shader = glCreateShader(shaderType);
+ if (shader) {
+ glShaderSource(shader, 1, source, NULL);
+ glCompileShader(shader);
+ GLint compiled = 0;
+ glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
+ if (!compiled) {
+ glDeleteShader(shader);
+ shader = 0;
+ }
+ }
+ return shader;
+}
+
+GLuint GLUtils::createProgram(const char** vertexSource,
+ const char** fragmentSource) {
+ GLuint vertexShader = loadShader(GL_VERTEX_SHADER, vertexSource);
+ if (!vertexShader) {
+ return 0;
+ }
+
+ GLuint pixelShader = loadShader(GL_FRAGMENT_SHADER, fragmentSource);
+ if (!pixelShader) {
+ return 0;
+ }
+
+ GLuint program = glCreateProgram();
+ if (program) {
+ bool success = true;
+ glAttachShader(program, vertexShader);
+ if (GLenum(GL_NO_ERROR) != glGetError()) {
+ success = false;
+ }
+ glAttachShader(program, pixelShader);
+ if (GLenum(GL_NO_ERROR) != glGetError()) {
+ success = false;
+ }
+
+ GLint linkStatus = GL_FALSE;
+ if (success) {
+ glLinkProgram(program);
+ glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
+ }
+ if (linkStatus != GL_TRUE || !success) {
+ glDeleteProgram(program);
+ program = 0;
+ }
+ }
+ return program;
+}
+
+// Rounds a number up to the smallest power of 2 that is greater than the original number.
+int GLUtils::roundUpToSmallestPowerOf2(int x) {
+ if (x < 0) {
+ return 0;
+ }
+ --x;
+ x |= x >> 1;
+ x |= x >> 2;
+ x |= x >> 4;
+ x |= x >> 8;
+ x |= x >> 16;
+ return x + 1;
+}
+
+GLuint GLUtils::genRandTex(int texWidth, int texHeight) {
+ GLuint textureId = 0;
+ int w = roundUpToSmallestPowerOf2(texWidth);
+ int h = roundUpToSmallestPowerOf2(texHeight);
+ uint32_t* m = new uint32_t[w * h];
+ if (m != NULL) {
+ uint32_t* d = m;
+ for (int y = 0; y < h; y++) {
+ for (int x = 0; x < w; x++) {
+ *d = 0xff000000 | ((y & 0xff) << 16) | ((x & 0xff) << 8)
+ | ((x + y) & 0xff);
+ d++;
+ }
+ }
+ glGenTextures(1, &textureId);
+ glBindTexture(GL_TEXTURE_2D, textureId);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA,
+ GL_UNSIGNED_BYTE, m);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+ }
+ delete[] m;
+ return textureId;
+}
diff --git a/suite/pts/deviceTests/opengl/jni/GLUtils.h b/suite/pts/deviceTests/opengl/jni/GLUtils.h
new file mode 100644
index 0000000..a0525bc
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/GLUtils.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+#ifndef GLUTILS_H
+#define GLUTILS_H
+
+#include <EGL/egl.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+class GLUtils {
+public:
+ // Creates a program with the given vertex and fragment shader source code.
+ static GLuint createProgram(const char** vertexSource,
+ const char** fragmentSource);
+ // Rounds a number up to the smallest power of 2 that is greater than the original number.
+ static int roundUpToSmallestPowerOf2(int x);
+ // Generates a random texture of the given dimensions.
+ static GLuint genRandTex(int texWidth, int texHeight);
+};
+
+#endif
diff --git a/suite/pts/deviceTests/opengl/jni/Renderer.cpp b/suite/pts/deviceTests/opengl/jni/Renderer.cpp
new file mode 100644
index 0000000..0f5c3ba
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/Renderer.cpp
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+#include "Renderer.h"
+#include <GLUtils.h>
+
+#define LOG_TAG "PTS_OPENGL"
+#define LOG_NDEBUG 0
+#include "utils/Log.h"
+
+static const EGLint contextAttribs[] = {
+ EGL_CONTEXT_CLIENT_VERSION, 2,
+ EGL_NONE };
+
+static const EGLint configAttribs[] = {
+ EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
+ EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+ EGL_RED_SIZE, 8,
+ EGL_GREEN_SIZE, 8,
+ EGL_BLUE_SIZE, 8,
+ EGL_ALPHA_SIZE, 8,
+ EGL_DEPTH_SIZE, 16,
+ EGL_STENCIL_SIZE, 8,
+ EGL_NONE };
+
+Renderer::Renderer(ANativeWindow* window, bool offscreen, int workload) :
+ mWindow(window), mEglDisplay(EGL_NO_DISPLAY), mEglSurface(EGL_NO_SURFACE),
+ mEglContext(EGL_NO_CONTEXT), mOffscreen(offscreen), mWorkload(workload) {
+}
+
+bool Renderer::setUp() {
+ mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+ if (EGL_NO_DISPLAY == mEglDisplay || EGL_SUCCESS != eglGetError()) {
+ return false;
+ }
+
+ EGLint major;
+ EGLint minor;
+ if (!eglInitialize(mEglDisplay, &major, &minor) || EGL_SUCCESS != eglGetError()) {
+ return false;
+ }
+
+ EGLint numConfigs = 0;
+ if (!eglChooseConfig(mEglDisplay, configAttribs, &mGlConfig, 1, &numConfigs)
+ || EGL_SUCCESS != eglGetError()) {
+ return false;
+ }
+
+ mEglSurface = eglCreateWindowSurface(mEglDisplay, mGlConfig, mWindow, NULL);
+ if (EGL_NO_SURFACE == mEglSurface || EGL_SUCCESS != eglGetError()) {
+ return false;
+ }
+
+ mEglContext = eglCreateContext(mEglDisplay, mGlConfig, EGL_NO_CONTEXT, contextAttribs);
+ if (EGL_NO_CONTEXT == mEglContext || EGL_SUCCESS != eglGetError()) {
+ return false;
+ }
+
+ if (!eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)
+ || EGL_SUCCESS != eglGetError()) {
+ return false;
+ }
+
+ if (!eglQuerySurface(mEglDisplay, mEglSurface, EGL_WIDTH, &width)
+ || EGL_SUCCESS != eglGetError()) {
+ return false;
+ }
+ if (!eglQuerySurface(mEglDisplay, mEglSurface, EGL_HEIGHT, &height)
+ || EGL_SUCCESS != eglGetError()) {
+ return false;
+ }
+
+ glViewport(0, 0, width, height);
+
+ if (mOffscreen) {
+ int w = GLUtils::roundUpToSmallestPowerOf2(width);
+ int h = GLUtils::roundUpToSmallestPowerOf2(height);
+ if (!createFBO(mFboId, mRboId, mCboId, w, h)) {
+ return false;
+ }
+ } else {
+ mFboId = 0;
+ mRboId = 0;
+ mCboId = 0;
+ }
+
+ GLuint err = glGetError();
+ if (err != GL_NO_ERROR) {
+ ALOGV("GLError %d", err);
+ return false;
+ }
+ return true;
+}
+
+bool Renderer::createFBO(GLuint& fboId, GLuint& rboId, GLuint& cboId, int width, int height) {
+ glGenFramebuffers(1, &fboId);
+ glBindFramebuffer(GL_FRAMEBUFFER, fboId);
+
+ glGenRenderbuffers(1, &rboId);
+ glBindRenderbuffer(GL_RENDERBUFFER, rboId);
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height);
+ glBindRenderbuffer(GL_RENDERBUFFER, 0);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rboId);
+
+ glGenRenderbuffers(1, &cboId);
+ glBindRenderbuffer(GL_RENDERBUFFER, cboId);
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB565, width, height);
+ glBindRenderbuffer(GL_RENDERBUFFER, 0);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, cboId);
+
+ GLuint err = glGetError();
+ if (err != GL_NO_ERROR) {
+ ALOGV("GLError %d", err);
+ return false;
+ }
+
+ return glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE;
+}
+
+bool Renderer::tearDown() {
+ if (mFboId != 0) {
+ glDeleteFramebuffers(1, &mFboId);
+ mFboId = 0;
+ }
+ if (mRboId != 0) {
+ glDeleteRenderbuffers(1, &mRboId);
+ mRboId = 0;
+ }
+ if (mCboId != 0) {
+ glDeleteRenderbuffers(1, &mCboId);
+ mCboId = 0;
+ }
+ if (mEglContext != EGL_NO_CONTEXT) {
+ eglDestroyContext(mEglDisplay, mEglContext);
+ mEglContext = EGL_NO_CONTEXT;
+ }
+ if (mEglSurface != EGL_NO_SURFACE) {
+ eglDestroySurface(mEglDisplay, mEglSurface);
+ mEglSurface = EGL_NO_SURFACE;
+ }
+ if (mEglDisplay != EGL_NO_DISPLAY) {
+ eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+ eglTerminate(mEglDisplay);
+ mEglDisplay = EGL_NO_DISPLAY;
+ }
+ return EGL_SUCCESS == eglGetError();
+}
diff --git a/suite/pts/deviceTests/opengl/jni/Renderer.h b/suite/pts/deviceTests/opengl/jni/Renderer.h
new file mode 100644
index 0000000..a50d81c
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/Renderer.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+#ifndef RENDERER_H
+#define RENDERER_H
+
+#include <android/native_window.h>
+
+#include <EGL/egl.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+class Renderer {
+public:
+ Renderer(ANativeWindow* window, bool offscreen, int workload);
+ virtual bool setUp();
+ virtual bool tearDown();
+ virtual bool draw() = 0;
+ virtual ~Renderer() {};
+protected:
+ bool createFBO(GLuint& fboId, GLuint& rboId, GLuint& cboId, int width, int height);
+ ANativeWindow* mWindow;
+ EGLDisplay mEglDisplay;
+ EGLSurface mEglSurface;
+ EGLContext mEglContext;
+ EGLConfig mGlConfig;
+ GLuint mFboId; //Frame buffer
+ GLuint mRboId; //Depth buffer
+ GLuint mCboId; //Color buffer
+ GLuint mProgramId;
+ EGLint width;
+ EGLint height;
+ bool mOffscreen;
+ int mWorkload;
+};
+#endif
diff --git a/suite/pts/deviceTests/opengl/jni/contextswitch/ContextSwitchRenderer.cpp b/suite/pts/deviceTests/opengl/jni/contextswitch/ContextSwitchRenderer.cpp
new file mode 100644
index 0000000..0d85cae
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/contextswitch/ContextSwitchRenderer.cpp
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+#include <android/native_window.h>
+
+#include <stdlib.h>
+
+#include <EGL/egl.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+#include "ContextSwitchRenderer.h"
+#include <GLUtils.h>
+
+#define LOG_TAG "PTS_OPENGL"
+#define LOG_NDEBUG 0
+#include "utils/Log.h"
+
+static const EGLint contextAttribs[] =
+ { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
+
+static const int CS_NUM_VERTICES = 6;
+
+static const float CS_VERTICES[CS_NUM_VERTICES * 3] = {
+ 1.0f, 1.0f, -1.0f,
+ -1.0f, 1.0f, -1.0f,
+ -1.0f, -1.0f, -1.0f,
+ -1.0f, -1.0f, -1.0f,
+ 1.0f, -1.0f, -1.0f,
+ 1.0f, 1.0f, -1.0f };
+
+static const float CS_TEX_COORDS[CS_NUM_VERTICES * 2] = {
+ 1.0f, 1.0f,
+ 0.0f, 1.0f,
+ 0.0f, 0.0f,
+ 0.0f, 0.0f,
+ 1.0f, 0.0f,
+ 1.0f, 1.0f };
+
+static const char* CS_VERTEX =
+ "attribute vec4 a_Position;"
+ "attribute vec2 a_TexCoord;"
+ "varying vec2 v_TexCoord;"
+ "void main() {"
+ " v_TexCoord = a_TexCoord;"
+ " gl_Position = a_Position;"
+ "}";
+
+static const char* CS_FRAGMENT =
+ "precision mediump float;"
+ "uniform sampler2D u_Texture;"
+ "varying vec2 v_TexCoord;"
+ "void main() {"
+ " gl_FragColor = texture2D(u_Texture, v_TexCoord);"
+ "}";
+
+ContextSwitchRenderer::ContextSwitchRenderer(ANativeWindow* window, bool offscreen, int workload) :
+ Renderer(window, offscreen, workload), mContexts(NULL) {
+}
+
+bool ContextSwitchRenderer::setUp() {
+ if (!Renderer::setUp()) {
+ return false;
+ }
+
+ // We don't need the context created by Renderer.
+ eglDestroyContext(mEglDisplay, mEglContext);
+ mEglContext = EGL_NO_CONTEXT;
+
+ int w = GLUtils::roundUpToSmallestPowerOf2(width);
+ int h = GLUtils::roundUpToSmallestPowerOf2(height);
+
+ mContexts = new EGLContext[mWorkload];
+ mTextureIds = new GLuint[mWorkload];
+ mProgramIds = new GLuint[mWorkload];
+ mTextureUniformHandles = new GLuint[mWorkload];
+ mPositionHandles = new GLuint[mWorkload];
+ mTexCoordHandles = new GLuint[mWorkload];
+ if (mOffscreen) {
+ mFboIds = new GLuint[mWorkload];
+ mRboIds = new GLuint[mWorkload];
+ mCboIds = new GLuint[mWorkload];
+ }
+ for (int i = 0; i < mWorkload; i++) {
+ mContexts[i] = eglCreateContext(mEglDisplay, mGlConfig, EGL_NO_CONTEXT, contextAttribs);
+ if (EGL_NO_CONTEXT == mContexts[i] || EGL_SUCCESS != eglGetError()) {
+ return false;
+ }
+
+ if (!eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mContexts[i])
+ || EGL_SUCCESS != eglGetError()) {
+ return false;
+ }
+
+ if (mOffscreen) {
+ // Setup FBOs.
+ if (!Renderer::createFBO(mFboIds[i], mRboIds[i], mCboIds[i], w, h)) {
+ return false;
+ }
+ }
+
+ // Setup textures.
+ mTextureIds[i] = GLUtils::genRandTex(64, 64);
+ if (mTextureIds[i] == 0) {
+ return false;
+ }
+
+ // Create program.
+ mProgramIds[i] = GLUtils::createProgram(&CS_VERTEX, &CS_FRAGMENT);
+ if (mProgramIds[i] == 0) {
+ return false;
+ }
+ // Bind attributes.
+ mTextureUniformHandles[i] = glGetUniformLocation(mProgramIds[i], "u_Texture");
+ mPositionHandles[i] = glGetAttribLocation(mProgramIds[i], "a_Position");
+ mTexCoordHandles[i] = glGetAttribLocation(mProgramIds[i], "a_TexCoord");
+ }
+
+ GLuint err = glGetError();
+ if (err != GL_NO_ERROR) {
+ ALOGV("GLError %d", err);
+ return false;
+ }
+
+ return true;
+}
+
+bool ContextSwitchRenderer::tearDown() {
+ if (mContexts) {
+ for (int i = 0; i < mWorkload; i++) {
+ eglDestroyContext(mEglDisplay, mContexts[i]);
+ }
+ delete[] mContexts;
+ }
+ if (mOffscreen) {
+ if (mFboIds) {
+ glDeleteFramebuffers(mWorkload, mFboIds);
+ delete[] mFboIds;
+ }
+ if (mRboIds) {
+ glDeleteRenderbuffers(mWorkload, mRboIds);
+ delete[] mRboIds;
+ }
+ if (mCboIds) {
+ glDeleteRenderbuffers(mWorkload, mCboIds);
+ delete[] mCboIds;
+ }
+ }
+ if (mTextureIds) {
+ glDeleteTextures(mWorkload, mTextureIds);
+ delete[] mTextureIds;
+ }
+ if (!Renderer::tearDown()) {
+ return false;
+ }
+ return true;
+}
+
+bool ContextSwitchRenderer::draw() {
+ for (int i = 0; i < mWorkload; i++) {
+ if (!eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mContexts[i])
+ || EGL_SUCCESS != eglGetError()) {
+ return false;
+ }
+
+ if (mOffscreen) {
+ glBindFramebuffer(GL_FRAMEBUFFER, mFboIds[i]);
+ if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
+ return false;
+ }
+ }
+
+ glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+ glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
+
+ glUseProgram(mProgramIds[i]);
+ glActiveTexture(GL_TEXTURE0);
+ // Bind the texture to this unit.
+ glBindTexture(GL_TEXTURE_2D, mTextureIds[i]);
+
+ // Tell the texture uniform sampler to use this texture in the shader by binding to texture
+ // unit 0.
+ glUniform1i(mTextureUniformHandles[i], 0);
+
+ glEnableVertexAttribArray(mPositionHandles[i]);
+ glEnableVertexAttribArray(mTexCoordHandles[i]);
+ glVertexAttribPointer(mPositionHandles[i], 3, GL_FLOAT, false, 0, CS_VERTICES);
+ glVertexAttribPointer(mTexCoordHandles[i], 2, GL_FLOAT, false, 0, CS_TEX_COORDS);
+
+ glDrawArrays(GL_TRIANGLES, 0, CS_NUM_VERTICES);
+ glFinish();
+ }
+
+ GLuint err = glGetError();
+ if (err != GL_NO_ERROR) {
+ ALOGV("GLError %d", err);
+ return false;
+ }
+
+ return (mOffscreen) ? true : eglSwapBuffers(mEglDisplay, mEglSurface);
+}
diff --git a/suite/pts/deviceTests/opengl/jni/contextswitch/ContextSwitchRenderer.h b/suite/pts/deviceTests/opengl/jni/contextswitch/ContextSwitchRenderer.h
new file mode 100644
index 0000000..3dfe9f3
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/contextswitch/ContextSwitchRenderer.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+#ifndef CONTEXTSWITCHRENDERER_H
+#define CONTEXTSWITCHRENDERER_H
+
+#include <Renderer.h>
+
+class ContextSwitchRenderer: public Renderer {
+public:
+ ContextSwitchRenderer(ANativeWindow* window, bool offscreen, int workload);
+ virtual ~ContextSwitchRenderer() {};
+ bool setUp();
+ bool tearDown();
+ bool draw();
+private:
+ GLuint mTextureUniformHandle;
+ GLuint mPositionHandle;
+ GLuint mTexCoordHandle;
+ EGLContext* mContexts;
+ GLuint* mTextureIds;
+ GLuint* mFboIds;
+ GLuint* mRboIds;
+ GLuint* mCboIds;
+ GLuint* mProgramIds;
+ GLuint* mTextureUniformHandles;
+ GLuint* mPositionHandles;
+ GLuint* mTexCoordHandles;
+};
+
+#endif
diff --git a/suite/pts/deviceTests/opengl/jni/fullpipeline/FullPipelineMesh.cpp b/suite/pts/deviceTests/opengl/jni/fullpipeline/FullPipelineMesh.cpp
new file mode 100644
index 0000000..c0e5250
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/fullpipeline/FullPipelineMesh.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+#include "FullPipelineMesh.h"
+#include "FullPipelineProgram.h"
+
+FullPipelineMesh::FullPipelineMesh(const Mesh* mesh) :
+ MeshNode(mesh) {
+}
+
+void FullPipelineMesh::before(Program& program, Matrix& model, Matrix& view, Matrix& projection) {
+ FullPipelineProgram& prog = (FullPipelineProgram&) program;
+
+ glActiveTexture(GL_TEXTURE0);
+ // Bind the texture to this unit.
+ glBindTexture(GL_TEXTURE_2D, mMesh->mTextureId);
+ // Tell the texture uniform sampler to use this texture in the shader by binding to texture
+ // unit 0.
+ glUniform1i(prog.mTextureUniformHandle, 0);
+
+ glEnableVertexAttribArray(prog.mPositionHandle);
+ glEnableVertexAttribArray(prog.mNormalHandle);
+ glEnableVertexAttribArray(prog.mTexCoordHandle);
+ glVertexAttribPointer(prog.mPositionHandle, 3, GL_FLOAT, false, 0, mMesh->mVertices);
+ glVertexAttribPointer(prog.mNormalHandle, 3, GL_FLOAT, false, 0, mMesh->mNormals);
+ glVertexAttribPointer(prog.mTexCoordHandle, 2, GL_FLOAT, false, 0, mMesh->mTexCoords);
+
+ // This multiplies the view matrix by the model matrix, and stores the result in the MVP
+ // matrix (which currently contains model * view).
+ prog.mMVMatrix.multiply(view, model);
+
+ // Pass in the modelview matrix.
+ glUniformMatrix4fv(prog.mMVMatrixHandle, 1, false, prog.mMVMatrix.mData);
+
+ // This multiplies the modelview matrix by the projection matrix, and stores the result in
+ // the MVP matrix (which now contains model * view * projection).
+ prog.mMVPMatrix.multiply(projection, prog.mMVMatrix);
+
+ // Pass in the combined matrix.
+ glUniformMatrix4fv(prog.mMVPMatrixHandle, 1, false, prog.mMVPMatrix.mData);
+
+ // Pass in the light position in eye space.
+ glUniform3f(prog.mLightPosHandle, prog.mLightPosInEyeSpace[0], prog.mLightPosInEyeSpace[1],
+ prog.mLightPosInEyeSpace[2]);
+
+ glDrawArrays(GL_TRIANGLES, 0, mMesh->mNumVertices);
+}
+
+void FullPipelineMesh::after(Program& program, Matrix& model, Matrix& view, Matrix& projection) {
+}
diff --git a/suite/pts/deviceTests/opengl/jni/fullpipeline/FullPipelineMesh.h b/suite/pts/deviceTests/opengl/jni/fullpipeline/FullPipelineMesh.h
new file mode 100644
index 0000000..3fcb8ae
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/fullpipeline/FullPipelineMesh.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+#ifndef FULLPIPELINEMESH_H
+#define FULLPIPELINEMESH_H
+
+#include <graphics/Matrix.h>
+#include <graphics/Mesh.h>
+#include <graphics/MeshNode.h>
+#include <graphics/Program.h>
+
+class FullPipelineMesh: public MeshNode {
+public:
+ FullPipelineMesh(const Mesh* mesh);
+ virtual ~FullPipelineMesh() {};
+protected:
+ virtual void before(Program& program, Matrix& model, Matrix& view,
+ Matrix& projection);
+ virtual void after(Program& program, Matrix& model, Matrix& view,
+ Matrix& projection);
+};
+
+#endif
diff --git a/suite/pts/deviceTests/opengl/jni/fullpipeline/FullPipelineProgram.cpp b/suite/pts/deviceTests/opengl/jni/fullpipeline/FullPipelineProgram.cpp
new file mode 100644
index 0000000..e3abc2c
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/fullpipeline/FullPipelineProgram.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+#include "FullPipelineProgram.h"
+
+#include <EGL/egl.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+FullPipelineProgram::FullPipelineProgram(GLuint programId) :
+ Program(programId) {
+ mLightPosInModelSpace[0] = 0.0f;
+ mLightPosInModelSpace[1] = 2.0f;
+ mLightPosInModelSpace[2] = 2.0f;
+ mLightPosInModelSpace[3] = 1.0f;
+ mMVMatrixHandle = glGetUniformLocation(programId, "u_MVMatrix");
+ mMVPMatrixHandle = glGetUniformLocation(programId, "u_MVPMatrix");
+ mLightPosHandle = glGetUniformLocation(programId, "u_LightPos");
+ mTextureUniformHandle = glGetUniformLocation(programId, "u_Texture");
+ mPositionHandle = glGetAttribLocation(programId, "a_Position");
+ mNormalHandle = glGetAttribLocation(programId, "a_Normal");
+ mTexCoordHandle = glGetAttribLocation(programId, "a_TexCoordinate");
+}
+
+void FullPipelineProgram::before(Matrix& model, Matrix& view, Matrix& projection) {
+ Program::before(model, view, projection);
+ mLightModelMatrix.identity();
+
+ Matrix::multiplyVector(mLightPosInWorldSpace, mLightModelMatrix, mLightPosInModelSpace);
+ Matrix::multiplyVector(mLightPosInEyeSpace, view, mLightPosInWorldSpace);
+}
diff --git a/suite/pts/deviceTests/opengl/jni/fullpipeline/FullPipelineProgram.h b/suite/pts/deviceTests/opengl/jni/fullpipeline/FullPipelineProgram.h
new file mode 100644
index 0000000..3eab1c4
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/fullpipeline/FullPipelineProgram.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+#ifndef FULLPIPELINEPROGRAM_H
+#define FULLPIPELINEPROGRAM_H
+
+#include <graphics/Matrix.h>
+#include <graphics/Program.h>
+
+#include <EGL/egl.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+class FullPipelineProgram: public Program {
+public:
+ FullPipelineProgram(GLuint programId);
+ virtual ~FullPipelineProgram() {};
+ Matrix mMVMatrix;
+ Matrix mMVPMatrix;
+ Matrix mLightModelMatrix;
+ float mLightPosInModelSpace[4];
+ float mLightPosInWorldSpace[4];
+ float mLightPosInEyeSpace[4];
+
+ int mMVMatrixHandle;
+ int mMVPMatrixHandle;
+ int mLightPosHandle;
+ int mTexCoordHandle;
+ int mPositionHandle;
+ int mNormalHandle;
+ int mTextureUniformHandle;
+ virtual void before(Matrix& model, Matrix& view, Matrix& projection);
+};
+
+#endif
diff --git a/suite/pts/deviceTests/opengl/jni/fullpipeline/FullPipelineRenderer.cpp b/suite/pts/deviceTests/opengl/jni/fullpipeline/FullPipelineRenderer.cpp
new file mode 100644
index 0000000..c2ffecc
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/fullpipeline/FullPipelineRenderer.cpp
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+#include <math.h>
+#include <EGL/egl.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+#include "FullPipelineMesh.h"
+#include "FullPipelineProgram.h"
+#include "FullPipelineRenderer.h"
+#include <graphics/Mesh.h>
+#include <graphics/TransformationNode.h>
+#include <GLUtils.h>
+
+#define LOG_TAG "PTS_OPENGL"
+#define LOG_NDEBUG 0
+#include "utils/Log.h"
+
+static const int FP_NUM_VERTICES = 6;
+
+static const float FP_VERTICES[FP_NUM_VERTICES * 3] = {
+ 1.0f, 1.0f, 0.0f,
+ 0.0f, 1.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f,
+ 1.0f, 0.0f, 0.0f,
+ 1.0f, 1.0f, 0.0f };
+
+static const float FP_NORMALS[FP_NUM_VERTICES * 3] = {
+ 0.0f, 0.0f, 1.0f,
+ 0.0f, 0.0f, 1.0f,
+ 0.0f, 0.0f, 1.0f,
+ 0.0f, 0.0f, 1.0f,
+ 0.0f, 0.0f, 1.0f,
+ 0.0f, 0.0f, 1.0f };
+
+static const float FP_TEX_COORDS[FP_NUM_VERTICES * 2] = {
+ 1.0f, 1.0f,
+ 0.0f, 1.0f,
+ 0.0f, 0.0f,
+ 0.0f, 0.0f,
+ 1.0f, 0.0f,
+ 1.0f, 1.0f };
+
+static const char* FP_VERTEX =
+ "uniform mat4 u_MVPMatrix;"
+ "uniform mat4 u_MVMatrix;"
+ "attribute vec4 a_Position;"
+ "attribute vec3 a_Normal;"
+ "attribute vec2 a_TexCoordinate;"
+ "varying vec3 v_Position;"
+ "varying vec3 v_Normal;"
+ "varying vec2 v_TexCoordinate;"
+ "void main() {\n"
+ " // Transform the vertex into eye space.\n"
+ " v_Position = vec3(u_MVMatrix * a_Position);\n"
+ " // Pass through the texture coordinate.\n"
+ " v_TexCoordinate = a_TexCoordinate;\n"
+ " // Transform the normal\'s orientation into eye space.\n"
+ " v_Normal = vec3(u_MVMatrix * vec4(a_Normal, 0.0));\n"
+ " // Multiply to get the final point in normalized screen coordinates.\n"
+ " gl_Position = u_MVPMatrix * a_Position;\n"
+ "}";
+
+static const char* FP_FRAGMENT =
+ "precision mediump float;"
+ "uniform vec3 u_LightPos;"
+ "uniform sampler2D u_Texture;"
+ "varying vec3 v_Position;"
+ "varying vec3 v_Normal;"
+ "varying vec2 v_TexCoordinate;"
+ "void main() {\n"
+ " // Will be used for attenuation.\n"
+ " float distance = length(u_LightPos - v_Position);\n"
+ " // Get a lighting direction vector from the light to the vertex.\n"
+ " vec3 lightVector = normalize(u_LightPos - v_Position);\n"
+ " // Calculate the dot product of the light vector and vertex normal.\n"
+ " float diffuse = max(dot(v_Normal, lightVector), 0.0);\n"
+ " // Add attenuation.\n"
+ " diffuse = diffuse * (1.0 / (1.0 + (0.01 * distance)));\n"
+ " // Add ambient lighting\n"
+ " diffuse = diffuse + 0.25;\n"
+ " // Multiply the diffuse illumination and texture to get final output color.\n"
+ " gl_FragColor = (diffuse * texture2D(u_Texture, v_TexCoordinate));\n"
+ "}";
+
+FullPipelineRenderer::FullPipelineRenderer(ANativeWindow* window, bool offscreen, int workload) :
+ Renderer(window, offscreen, workload), mProgram(NULL), mSceneGraph(NULL),
+ mModelMatrix(NULL), mViewMatrix(NULL), mProjectionMatrix(NULL), mMesh(NULL) {
+}
+
+bool FullPipelineRenderer::setUp() {
+ if (!Renderer::setUp()) {
+ return false;
+ }
+
+ mProgramId = GLUtils::createProgram(&FP_VERTEX, &FP_FRAGMENT);
+ if (mProgramId == 0)
+ return false;
+ mProgram = new FullPipelineProgram(mProgramId);
+
+ mModelMatrix = new Matrix();
+
+ // Position the eye in front of the origin.
+ float eyeX = 0.0f;
+ float eyeY = 0.0f;
+ float eyeZ = 2.0f;
+
+ // We are looking at the origin
+ float centerX = 0.0f;
+ float centerY = 0.0f;
+ float centerZ = 0.0f;
+
+ // Set our up vector. This is where our head would be pointing were we holding the camera.
+ float upX = 0.0f;
+ float upY = 1.0f;
+ float upZ = 0.0f;
+
+ // Set the view matrix. This matrix can be said to represent the camera position.
+ mViewMatrix = Matrix::newLookAt(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ);
+
+ // Create a new perspective projection matrix. The height will stay the same
+ // while the width will vary as per aspect ratio.
+ float ratio = (float) width / height;
+ float left = -ratio;
+ float right = ratio;
+ float bottom = -1.0f;
+ float top = 1.0f;
+ float near = 1.0f;
+ float far = 3.0f;
+
+ mProjectionMatrix = Matrix::newFrustum(left, right, bottom, top, near, far);
+
+ // Setup texture.
+ mTextureId = GLUtils::genRandTex(width, height);
+ if (mTextureId == 0) {
+ return false;
+ }
+
+ float count = pow(2, mWorkload - 1);
+ float middle = count / 2.0f;
+ float scale = 1.0f / count;
+
+ mMesh = new Mesh(FP_VERTICES, FP_NORMALS, FP_TEX_COORDS, FP_NUM_VERTICES, mTextureId);
+ mSceneGraph = new ProgramNode();
+
+ for (int i = 0; i < count; i++) {
+ for (int j = 0; j < count; j++) {
+ Matrix* transformMatrix = Matrix::newScale(scale, scale, scale);
+ transformMatrix->translate(i - middle, j - middle, 0.0f);
+ TransformationNode* transformNode = new TransformationNode(transformMatrix);
+ mSceneGraph->addChild(transformNode);
+ FullPipelineMesh* meshNode = new FullPipelineMesh(mMesh);
+ transformNode->addChild(meshNode);
+ }
+ }
+ return true;
+}
+
+bool FullPipelineRenderer::tearDown() {
+ if (mTextureId != 0) {
+ glDeleteTextures(1, &mTextureId);
+ mTextureId = 0;
+ }
+ if (!Renderer::tearDown()) {
+ return false;
+ }
+ delete mModelMatrix;
+ mModelMatrix = NULL;
+ delete mViewMatrix;
+ mViewMatrix = NULL;
+ delete mProjectionMatrix;
+ mProjectionMatrix = NULL;
+ delete mProgram;
+ mProgram = NULL;
+ delete mSceneGraph;
+ mSceneGraph = NULL;
+ delete mMesh;
+ mMesh = NULL;
+ return true;
+}
+
+bool FullPipelineRenderer::draw() {
+ if (mOffscreen) {
+ glBindFramebuffer(GL_FRAMEBUFFER, mFboId);
+ }
+ // Set the background clear color to black.
+ glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+ // Use culling to remove back faces.
+ glEnable(GL_CULL_FACE);
+ // Use depth testing.
+ glEnable(GL_DEPTH_TEST);
+ glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
+ mModelMatrix->identity();
+ mSceneGraph->draw(*mProgram, *mModelMatrix, *mViewMatrix, *mProjectionMatrix);
+
+ GLuint err = glGetError();
+ if (err != GL_NO_ERROR) {
+ ALOGV("GLError %d", err);
+ return false;
+ }
+
+ if (mOffscreen) {
+ glFinish();
+ return true;
+ } else {
+ return eglSwapBuffers(mEglDisplay, mEglSurface);
+ }
+}
diff --git a/suite/pts/deviceTests/opengl/jni/fullpipeline/FullPipelineRenderer.h b/suite/pts/deviceTests/opengl/jni/fullpipeline/FullPipelineRenderer.h
new file mode 100644
index 0000000..0c5acae
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/fullpipeline/FullPipelineRenderer.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+#ifndef FULLPIPELINERENDERER_H
+#define FULLPIPELINERENDERER_H
+
+#include "FullPipelineProgram.h"
+
+#include <Renderer.h>
+#include <graphics/Matrix.h>
+#include <graphics/Mesh.h>
+#include <graphics/ProgramNode.h>
+
+class FullPipelineRenderer: public Renderer {
+public:
+ FullPipelineRenderer(ANativeWindow* window, bool offscreen, int workload);
+ virtual ~FullPipelineRenderer() {};
+ bool setUp();
+ bool tearDown();
+ bool draw();
+private:
+ FullPipelineProgram* mProgram;
+ ProgramNode* mSceneGraph;
+ Matrix* mModelMatrix;
+ Matrix* mViewMatrix;
+ Matrix* mProjectionMatrix;
+ Mesh* mMesh;
+ GLuint mTextureId;
+};
+#endif
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/Matrix.cpp b/suite/pts/deviceTests/opengl/jni/graphics/Matrix.cpp
new file mode 100644
index 0000000..83591e8
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/graphics/Matrix.cpp
@@ -0,0 +1,329 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+#include "Matrix.h"
+#include <string.h>
+#include <math.h>
+
+#define LOG_TAG "PTS_OPENGL"
+#define LOG_NDEBUG 0
+#include "utils/Log.h"
+
+Matrix::Matrix() {
+ identity();
+}
+
+Matrix::Matrix(const Matrix& src) {
+ loadWith(src);
+}
+
+void Matrix::print(const char* label) {
+ ALOGI("%c", *label);
+ for (int i = 0; i < 4; i++) {
+ const float* d = &(mData[i * 4]);
+ ALOGI("%f %f %f %f\n", d[0], d[1], d[2], d[3]);
+ }
+}
+
+bool Matrix::equals(const Matrix& src) {
+ bool equals = true;
+ const float* d = src.mData;
+ for (int i = 0; i < MATRIX_SIZE && equals; i++) {
+ if (mData[i] != d[i]) {
+ equals = false;
+ }
+ }
+ return equals;
+}
+
+void Matrix::loadWith(const Matrix& src) {
+ memcpy(mData, src.mData, MATRIX_SIZE * sizeof(float));
+}
+
+void Matrix::identity() {
+ mData[0] = 1.0f;
+ mData[1] = 0.0f;
+ mData[2] = 0.0f;
+ mData[3] = 0.0f;
+
+ mData[4] = 0.0f;
+ mData[5] = 1.0f;
+ mData[6] = 0.0f;
+ mData[7] = 0.0f;
+
+ mData[8] = 0.0f;
+ mData[9] = 0.0f;
+ mData[10] = 1.0f;
+ mData[11] = 0.0f;
+
+ mData[12] = 0.0f;
+ mData[13] = 0.0f;
+ mData[14] = 0.0f;
+ mData[15] = 1.0f;
+}
+
+void Matrix::translate(float x, float y, float z) {
+ Matrix* m = newTranslate(x, y, z);
+ Matrix* temp = new Matrix(*this);
+ if (m != NULL && temp != NULL) {
+ multiply(*temp, *m);
+ }
+ delete m;
+ delete temp;
+}
+
+void Matrix::scale(float x, float y, float z) {
+ Matrix* m = newScale(x, y, z);
+ Matrix* temp = new Matrix(*this);
+ if (m != NULL && temp != NULL) {
+ multiply(*temp, *m);
+ }
+ delete m;
+ delete temp;
+}
+
+void Matrix::rotate(float degrees, float x, float y, float z) {
+ Matrix* m = newRotate(degrees, x, y, z);
+ Matrix* temp = new Matrix(*this);
+ if (m != NULL && temp != NULL) {
+ multiply(*temp, *m);
+ }
+ delete m;
+ delete temp;
+}
+
+void Matrix::multiply(const Matrix& l, const Matrix& r) {
+ float const* const lhs = l.mData;
+ float const* const rhs = r.mData;
+ for (int i = 0; i < 4; i++) {
+ const int i4 = i * 4;
+ float x = 0;
+ float y = 0;
+ float z = 0;
+ float w = 0;
+
+ for (int j = 0; j < 4; j++) {
+ const int j4 = j * 4;
+ const float e = rhs[i4 + j];
+ x += lhs[j4 + 0] * e;
+ y += lhs[j4 + 1] * e;
+ z += lhs[j4 + 2] * e;
+ w += lhs[j4 + 3] * e;
+ }
+
+ mData[i4 + 0] = x;
+ mData[i4 + 1] = y;
+ mData[i4 + 2] = z;
+ mData[i4 + 3] = w;
+ }
+}
+
+Matrix* Matrix::newLookAt(float eyeX, float eyeY, float eyeZ, float centerX,
+ float centerY, float centerZ, float upX, float upY, float upZ) {
+ Matrix* m = new Matrix();
+ if (m != NULL) {
+ // See the OpenGL GLUT documentation for gluLookAt for a description
+ // of the algorithm. We implement it in a straightforward way:
+
+ float fx = centerX - eyeX;
+ float fy = centerY - eyeY;
+ float fz = centerZ - eyeZ;
+
+ // Normalize f
+ float rlf = 1.0f / (float) sqrt(fx * fx + fy * fy + fz * fz);
+ fx *= rlf;
+ fy *= rlf;
+ fz *= rlf;
+
+ // compute s = f x up (x means "cross product")
+ float sx = fy * upZ - fz * upY;
+ float sy = fz * upX - fx * upZ;
+ float sz = fx * upY - fy * upX;
+
+ // and normalize s
+ float rls = 1.0f / (float) sqrt(sx * sx + sy * sy + sz * sz);
+ sx *= rls;
+ sy *= rls;
+ sz *= rls;
+
+ // compute u = s x f
+ float ux = sy * fz - sz * fy;
+ float uy = sz * fx - sx * fz;
+ float uz = sx * fy - sy * fx;
+
+ float* d = m->mData;
+ d[0] = sx;
+ d[1] = ux;
+ d[2] = -fx;
+ d[3] = 0.0f;
+
+ d[4] = sy;
+ d[5] = uy;
+ d[6] = -fy;
+ d[7] = 0.0f;
+
+ d[8] = sz;
+ d[9] = uz;
+ d[10] = -fz;
+ d[11] = 0.0f;
+
+ d[12] = 0.0f;
+ d[13] = 0.0f;
+ d[14] = 0.0f;
+ d[15] = 1.0f;
+
+ m->translate(-eyeX, -eyeY, -eyeZ);
+ }
+ return m;
+}
+
+Matrix* Matrix::newFrustum(float left, float right, float bottom, float top,
+ float near, float far) {
+ const float r_width = 1.0f / (right - left);
+ const float r_height = 1.0f / (top - bottom);
+ const float r_depth = 1.0f / (near - far);
+ const float x = 2.0f * (near * r_width);
+ const float y = 2.0f * (near * r_height);
+ const float A = (right + left) * r_width;
+ const float B = (top + bottom) * r_height;
+ const float C = (far + near) * r_depth;
+ const float D = 2.0f * (far * near * r_depth);
+ Matrix* m = new Matrix();
+ if (m != NULL) {
+ float* d = m->mData;
+ d[0] = x;
+ d[5] = y;
+ d[8] = A;
+ d[9] = B;
+ d[10] = C;
+ d[14] = D;
+ d[11] = -1.0f;
+ d[1] = 0.0f;
+ d[2] = 0.0f;
+ d[3] = 0.0f;
+ d[4] = 0.0f;
+ d[6] = 0.0f;
+ d[7] = 0.0f;
+ d[12] = 0.0f;
+ d[13] = 0.0f;
+ d[15] = 0.0f;
+ }
+ return m;
+}
+
+Matrix* Matrix::newTranslate(float x, float y, float z) {
+ Matrix* m = new Matrix();
+ if (m != NULL) {
+ float* d = m->mData;
+ d[12] = x;
+ d[13] = y;
+ d[14] = z;
+ }
+ return m;
+}
+Matrix* Matrix::newScale(float x, float y, float z) {
+ Matrix* m = new Matrix();
+ if (m != NULL) {
+ float* d = m->mData;
+ d[0] = x;
+ d[5] = y;
+ d[10] = z;
+ }
+ return m;
+}
+Matrix* Matrix::newRotate(float degrees, float x, float y, float z) {
+ Matrix* m = new Matrix();
+ if (m != NULL) {
+ float* d = m->mData;
+ d[3] = 0;
+ d[7] = 0;
+ d[11] = 0;
+ d[12] = 0;
+ d[13] = 0;
+ d[14] = 0;
+ d[15] = 1;
+ float radians = degrees * (M_PI / 180.0f);
+ float s = (float) sinf(radians);
+ float c = (float) cosf(radians);
+ if (1.0f == x && 0.0f == y && 0.0f == z) {
+ d[5] = c;
+ d[10] = c;
+ d[6] = s;
+ d[9] = -s;
+ d[1] = 0;
+ d[2] = 0;
+ d[4] = 0;
+ d[8] = 0;
+ d[0] = 1;
+ } else if (0.0f == x && 1.0f == y && 0.0f == z) {
+ d[0] = c;
+ d[10] = c;
+ d[8] = s;
+ d[2] = -s;
+ d[1] = 0;
+ d[4] = 0;
+ d[6] = 0;
+ d[9] = 0;
+ d[5] = 1;
+ } else if (0.0f == x && 0.0f == y && 1.0f == z) {
+ d[0] = c;
+ d[5] = c;
+ d[1] = s;
+ d[4] = -s;
+ d[2] = 0;
+ d[6] = 0;
+ d[8] = 0;
+ d[9] = 0;
+ d[10] = 1;
+ } else {
+ float len = sqrt((x * x) + (y * y) + (z * z));
+ if (1.0f != len) {
+ float recipLen = 1.0f / len;
+ x *= recipLen;
+ y *= recipLen;
+ z *= recipLen;
+ }
+ float nc = 1.0f - c;
+ float xy = x * y;
+ float yz = y * z;
+ float zx = z * x;
+ float xs = x * s;
+ float ys = y * s;
+ float zs = z * s;
+ d[0] = x * x * nc + c;
+ d[4] = xy * nc - zs;
+ d[8] = zx * nc + ys;
+ d[1] = xy * nc + zs;
+ d[5] = y * y * nc + c;
+ d[9] = yz * nc - xs;
+ d[2] = zx * nc - ys;
+ d[6] = yz * nc + xs;
+ d[10] = z * z * nc + c;
+ }
+ }
+ return m;
+}
+
+void Matrix::multiplyVector(float* result, const Matrix& lhs,
+ const float* rhs) {
+ const float* d = lhs.mData;
+ const float x = rhs[0];
+ const float y = rhs[1];
+ const float z = rhs[2];
+ const float w = rhs[3];
+ for (int i = 0; i < 4; i++) {
+ const int j = i * 4;
+ result[i] = d[j + 0] * x + d[j + 1] * y + d[j + 2] * z + d[j + 3] * w;
+ }
+}
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/Matrix.h b/suite/pts/deviceTests/opengl/jni/graphics/Matrix.h
new file mode 100644
index 0000000..3484266
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/graphics/Matrix.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+#ifndef MATRIX_H
+#define MATRIX_H
+
+class Matrix {
+public:
+ static const int MATRIX_SIZE = 16;
+ float mData[MATRIX_SIZE];
+ Matrix();
+ Matrix(const Matrix& src);
+ // Returns true if the two matrices have the same values.
+ bool equals(const Matrix& src);
+ // Loads this matrix with the identity matrix.
+ void identity();
+ // Loads this matrix with the data from src.
+ void loadWith(const Matrix& src);
+ // Translates this matrix by the given amounts.
+ void translate(float x, float y, float z);
+ // Scales this matrix by the given amounts.
+ void scale(float x, float y, float z);
+ // Rotates this matrix the given angle.
+ void rotate(float degrees, float x, float y, float z);
+ // Sets this matrix to be the result of multiplying the given matrices.
+ void multiply(const Matrix& l, const Matrix& r);
+
+ void print(const char* label);
+
+ // Returns a new matrix representing the camera.
+ static Matrix* newLookAt(float eyeX, float eyeY, float eyeZ, float centerX,
+ float centerY, float centerZ, float upX, float upY, float upZ);
+ // Returns a new matrix representing the perspective matrix.
+ static Matrix* newFrustum(float left, float right, float bottom, float top,
+ float near, float far);
+ // Returns a new matrix representing the translation.
+ static Matrix* newTranslate(float x, float y, float z);
+ // Returns a new matrix representing the scaling.
+ static Matrix* newScale(float x, float y, float z);
+ // Returns a new matrix representing the rotation.
+ static Matrix* newRotate(float degrees, float x, float y, float z);
+
+ // Sets the given matrix to be the result of multiplying the given matrix by the given vector.
+ static void multiplyVector(float* result, const Matrix& lhs,
+ const float* rhs);
+};
+
+#endif
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/Mesh.cpp b/suite/pts/deviceTests/opengl/jni/graphics/Mesh.cpp
new file mode 100644
index 0000000..b92551c
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/graphics/Mesh.cpp
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+#include "Mesh.h"
+
+Mesh::Mesh(const float* vertices, const float* normals, const float* texCoords,
+ const int numVertices, const GLuint textureId)
+ : mVertices(vertices),
+ mNormals(normals),
+ mTexCoords(texCoords),
+ mNumVertices(numVertices),
+ mTextureId(textureId) {
+
+}
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/Mesh.h b/suite/pts/deviceTests/opengl/jni/graphics/Mesh.h
new file mode 100644
index 0000000..5f5ac7d
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/graphics/Mesh.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+#ifndef MESH_H
+#define MESH_H
+
+#include <GLES2/gl2.h>
+
+// Meshes act as holders for geometry data etc. This is accessed by the MeshNodes to render them.
+// This allows a mesh to appear multiple times in a SceneGraph without duplication.
+class Mesh {
+public:
+ Mesh(const float* vertices, const float* normals, const float* texCoords,
+ const int numVertices, const GLuint textureId);
+ virtual ~Mesh() {};
+ const float* mVertices;
+ const float* mNormals;
+ const float* mTexCoords;
+ const int mNumVertices;
+ const GLuint mTextureId;
+};
+
+#endif
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/MeshNode.cpp b/suite/pts/deviceTests/opengl/jni/graphics/MeshNode.cpp
new file mode 100644
index 0000000..8ea5f76
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/graphics/MeshNode.cpp
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+#include "MeshNode.h"
+
+MeshNode::MeshNode(const Mesh* mesh) :
+ mMesh(mesh) {
+
+}
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/MeshNode.h b/suite/pts/deviceTests/opengl/jni/graphics/MeshNode.h
new file mode 100644
index 0000000..a02b6bd
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/graphics/MeshNode.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+#ifndef MESHNODE_H
+#define MESHNODE_H
+
+#include "Matrix.h"
+#include "Mesh.h"
+#include "Program.h"
+#include "SceneGraphNode.h"
+
+class MeshNode: public SceneGraphNode {
+public:
+ MeshNode(const Mesh* mesh);
+ virtual ~MeshNode() {};
+protected:
+ virtual void before(Program& program, Matrix& model, Matrix& view, Matrix& projection) = 0;
+ virtual void after(Program& program, Matrix& model, Matrix& view, Matrix& projection) = 0;
+ const Mesh* mMesh;
+};
+
+#endif
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/Program.cpp b/suite/pts/deviceTests/opengl/jni/graphics/Program.cpp
new file mode 100644
index 0000000..b951a4b
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/graphics/Program.cpp
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+#include "ProgramNode.h"
+
+Program::Program(GLuint programId) :
+ mProgramId(programId) {
+}
+
+void Program::before(Matrix& model, Matrix& view, Matrix& projection) {
+ glUseProgram(mProgramId);
+}
+
+void Program::after(Matrix& model, Matrix& view, Matrix& projection) {
+}
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/Program.h b/suite/pts/deviceTests/opengl/jni/graphics/Program.h
new file mode 100644
index 0000000..f351b58
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/graphics/Program.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+#ifndef PROGRAM_H
+#define PROGRAM_H
+
+#include "Matrix.h"
+
+#include <GLES2/gl2.h>
+
+class Program {
+public:
+ Program(GLuint programId);
+ virtual ~Program() {};
+ virtual void before(Matrix& model, Matrix& view, Matrix& projection);
+ virtual void after(Matrix& model, Matrix& view, Matrix& projection);
+private:
+ GLuint mProgramId;
+};
+
+#endif
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/ProgramNode.cpp b/suite/pts/deviceTests/opengl/jni/graphics/ProgramNode.cpp
new file mode 100644
index 0000000..bc4eb90
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/graphics/ProgramNode.cpp
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+#include "ProgramNode.h"
+
+void ProgramNode::before(Program& program, Matrix& model, Matrix& view,
+ Matrix& projection) {
+ program.before(model, view, projection);
+}
+
+void ProgramNode::after(Program& program, Matrix& model, Matrix& view,
+ Matrix& projection) {
+ program.after(model, view, projection);
+}
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/ProgramNode.h b/suite/pts/deviceTests/opengl/jni/graphics/ProgramNode.h
new file mode 100644
index 0000000..54ac92a
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/graphics/ProgramNode.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+#ifndef PROGRAMNODE_H
+#define PROGRAMNODE_H
+
+#include "Matrix.h"
+#include "Program.h"
+#include "SceneGraphNode.h"
+
+class ProgramNode: public SceneGraphNode {
+public:
+ ProgramNode() {};
+ virtual ~ProgramNode() {};
+protected:
+ virtual void before(Program& program, Matrix& model, Matrix& view,
+ Matrix& projection);
+ virtual void after(Program& program, Matrix& model, Matrix& view,
+ Matrix& projection);
+};
+
+#endif
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/SceneGraphNode.cpp b/suite/pts/deviceTests/opengl/jni/graphics/SceneGraphNode.cpp
new file mode 100644
index 0000000..473c846
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/graphics/SceneGraphNode.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+#include "SceneGraphNode.h"
+
+SceneGraphNode::~SceneGraphNode() {
+ for (size_t i = 0; i < mChildren.size(); i++) {
+ delete mChildren[i];
+ }
+}
+
+void SceneGraphNode::addChild(SceneGraphNode* child) {
+ mChildren.add(child);
+}
+
+void SceneGraphNode::draw(Program& program, Matrix& model, Matrix& view,
+ Matrix& projection) {
+ before(program, model, view, projection);
+ for (size_t i = 0; i < mChildren.size(); i++) {
+ mChildren[i]->draw(program, model, view, projection);
+ }
+ after(program, model, view, projection);
+}
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/SceneGraphNode.h b/suite/pts/deviceTests/opengl/jni/graphics/SceneGraphNode.h
new file mode 100644
index 0000000..55a20f1
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/graphics/SceneGraphNode.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+#ifndef SCENEGRAPHNODE_H
+#define SCENEGRAPHNODE_H
+
+#include <utils/Vector.h>
+#include "Matrix.h"
+#include "Program.h"
+
+class SceneGraphNode {
+public:
+ SceneGraphNode() {};
+ virtual ~SceneGraphNode();
+ void addChild(SceneGraphNode* child);
+ void draw(Program& program, Matrix& model, Matrix& view,
+ Matrix& projection);
+protected:
+ virtual void before(Program& program, Matrix& model, Matrix& view,
+ Matrix& projection) = 0;
+ virtual void after(Program& program, Matrix& model, Matrix& view,
+ Matrix& projection) = 0;
+private:
+ android::Vector<SceneGraphNode*> mChildren;
+};
+
+#endif
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/TransformationNode.cpp b/suite/pts/deviceTests/opengl/jni/graphics/TransformationNode.cpp
new file mode 100644
index 0000000..3cfa55b
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/graphics/TransformationNode.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+#include "TransformationNode.h"
+
+TransformationNode::TransformationNode(Matrix* matrix) :
+ mMatrix(matrix) {
+}
+
+TransformationNode::~TransformationNode() {
+ delete mMatrix;
+}
+
+void TransformationNode::before(Program& program, Matrix& model, Matrix& view,
+ Matrix& projection) {
+ // Save the current Matrix.
+ mSavedModelMatrix.loadWith(model);
+ // Apply transformation.
+ model.multiply(*mMatrix, mSavedModelMatrix);
+}
+
+void TransformationNode::after(Program& program, Matrix& model, Matrix& view,
+ Matrix& projection) {
+ // Restore original Matrix.
+ model.loadWith(mSavedModelMatrix);
+}
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/TransformationNode.h b/suite/pts/deviceTests/opengl/jni/graphics/TransformationNode.h
new file mode 100644
index 0000000..04ba5c2
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/graphics/TransformationNode.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+#ifndef TRANSFORMATIONNODE_H
+#define TRANSFORMATIONNODE_H
+
+#include "Matrix.h"
+#include "Program.h"
+#include "SceneGraphNode.h"
+
+class TransformationNode: public SceneGraphNode {
+public:
+ TransformationNode(Matrix* matrix);
+ virtual ~TransformationNode();
+protected:
+ virtual void before(Program& program, Matrix& model, Matrix& view,
+ Matrix& projection);
+ virtual void after(Program& program, Matrix& model, Matrix& view,
+ Matrix& projection);
+private:
+ Matrix mSavedModelMatrix;
+ Matrix* mMatrix;
+};
+
+#endif
diff --git a/suite/pts/deviceTests/opengl/jni/pixeloutput/PixelOutputRenderer.cpp b/suite/pts/deviceTests/opengl/jni/pixeloutput/PixelOutputRenderer.cpp
new file mode 100644
index 0000000..95bf52b
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/pixeloutput/PixelOutputRenderer.cpp
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+#include "PixelOutputRenderer.h"
+#include <GLUtils.h>
+
+#define LOG_TAG "PTS_OPENGL"
+#define LOG_NDEBUG 0
+#include "utils/Log.h"
+
+static const int PO_NUM_VERTICES = 6;
+
+static const float PO_VERTICES[PO_NUM_VERTICES * 3] = {
+ 1.0f, 1.0f, -1.0f,
+ -1.0f, 1.0f, -1.0f,
+ -1.0f, -1.0f, -1.0f,
+ -1.0f, -1.0f, -1.0f,
+ 1.0f, -1.0f, -1.0f,
+ 1.0f, 1.0f, -1.0f };
+static const float PO_TEX_COORDS[PO_NUM_VERTICES * 2] = {
+ 1.0f, 1.0f,
+ 0.0f, 1.0f,
+ 0.0f, 0.0f,
+ 0.0f, 0.0f,
+ 1.0f, 0.0f,
+ 1.0f, 1.0f };
+
+static const char* PO_VERTEX =
+ "attribute vec4 a_Position;"
+ "attribute vec2 a_TexCoord;"
+ "varying vec2 v_TexCoord;"
+ "void main() {"
+ " v_TexCoord = a_TexCoord;"
+ " gl_Position = a_Position;"
+ "}";
+
+static const char* PO_FRAGMENT =
+ "precision mediump float;"
+ "uniform sampler2D u_Texture;"
+ "varying vec2 v_TexCoord;"
+ "void main() {"
+ " gl_FragColor = texture2D(u_Texture, v_TexCoord);"
+ "}";
+
+PixelOutputRenderer::PixelOutputRenderer(ANativeWindow* window, bool offscreen, int workload) :
+ Renderer(window, offscreen, workload) {
+}
+
+bool PixelOutputRenderer::setUp() {
+ if (!Renderer::setUp()) {
+ return false;
+ }
+
+ // Create program.
+ mProgramId = GLUtils::createProgram(&PO_VERTEX, &PO_FRAGMENT);
+ if (mProgramId == 0)
+ return false;
+ // Bind attributes.
+ mTextureUniformHandle = glGetUniformLocation(mProgramId, "u_Texture");
+ mPositionHandle = glGetAttribLocation(mProgramId, "a_Position");
+ mTexCoordHandle = glGetAttribLocation(mProgramId, "a_TexCoord");
+
+ // Setup texture.
+ mTextureId = GLUtils::genRandTex(width, height);
+ if (mTextureId == 0) {
+ return false;
+ }
+ return true;
+}
+
+bool PixelOutputRenderer::tearDown() {
+ if (mTextureId != 0) {
+ glDeleteTextures(1, &mTextureId);
+ mTextureId = 0;
+ }
+ if (!Renderer::tearDown()) {
+ return false;
+ }
+ return true;
+}
+
+bool PixelOutputRenderer::draw() {
+ if (mOffscreen) {
+ glBindFramebuffer(GL_FRAMEBUFFER, mFboId);
+ }
+ glUseProgram(mProgramId);
+ // Set the background clear color to black.
+ glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+ glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
+
+ // No culling of back faces
+ glDisable(GL_CULL_FACE);
+
+ // No depth testing
+ glDisable(GL_DEPTH_TEST);
+
+ // Enable blending
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_ONE, GL_ONE);
+
+ glActiveTexture(GL_TEXTURE0);
+ // Bind the texture to this unit.
+ glBindTexture(GL_TEXTURE_2D, mTextureId);
+ // Tell the texture uniform sampler to use this texture in the shader by binding to texture
+ // unit 0.
+ glUniform1i(mTextureUniformHandle, 0);
+
+ glEnableVertexAttribArray(mPositionHandle);
+ glEnableVertexAttribArray(mTexCoordHandle);
+ glVertexAttribPointer(mPositionHandle, 3, GL_FLOAT, false, 0, PO_VERTICES);
+ glVertexAttribPointer(mTexCoordHandle, 2, GL_FLOAT, false, 0, PO_TEX_COORDS);
+
+ for (int i = 0; i < mWorkload; i++) {
+ glDrawArrays(GL_TRIANGLES, 0, PO_NUM_VERTICES);
+ }
+
+ GLuint err = glGetError();
+ if (err != GL_NO_ERROR) {
+ ALOGV("GLError %d", err);
+ return false;
+ }
+
+ if (mOffscreen) {
+ glFinish();
+ return true;
+ } else {
+ return eglSwapBuffers(mEglDisplay, mEglSurface);
+ }
+}
diff --git a/suite/pts/deviceTests/opengl/jni/pixeloutput/PixelOutputRenderer.h b/suite/pts/deviceTests/opengl/jni/pixeloutput/PixelOutputRenderer.h
new file mode 100644
index 0000000..6422517
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/pixeloutput/PixelOutputRenderer.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+#ifndef PIXELOUTPUTRENDERER_H
+#define PIXELOUTPUTRENDERER_H
+
+#include <Renderer.h>
+
+class PixelOutputRenderer: public Renderer {
+public:
+ PixelOutputRenderer(ANativeWindow* window, bool offscreen, int workload);
+ virtual ~PixelOutputRenderer() {};
+ bool setUp();
+ bool tearDown();
+ bool draw();
+private:
+ GLuint mTextureId;
+ GLuint mTextureUniformHandle;
+ GLuint mPositionHandle;
+ GLuint mTexCoordHandle;
+};
+
+#endif
diff --git a/suite/pts/deviceTests/opengl/jni/shaderperf/ShaderPerfRenderer.cpp b/suite/pts/deviceTests/opengl/jni/shaderperf/ShaderPerfRenderer.cpp
new file mode 100644
index 0000000..a3fea22
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/shaderperf/ShaderPerfRenderer.cpp
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+#include "ShaderPerfRenderer.h"
+#include <GLUtils.h>
+
+#define LOG_TAG "PTS_OPENGL"
+#define LOG_NDEBUG 0
+#include "utils/Log.h"
+
+static const int SP_NUM_VERTICES = 6;
+
+static const float SP_VERTICES[SP_NUM_VERTICES * 3] = {
+ 1.0f, 1.0f, -1.0f,
+ -1.0f, 1.0f, -1.0f,
+ -1.0f, -1.0f, -1.0f,
+ -1.0f, -1.0f, -1.0f,
+ 1.0f, -1.0f, -1.0f,
+ 1.0f, 1.0f, -1.0f };
+
+static const char* SP_VERTEX = "attribute vec4 a_Position;"
+ "varying vec4 v_Position;"
+ "void main() {"
+ " v_Position = a_Position;"
+ " gl_Position = a_Position;"
+ "}";
+
+// TODO At the moment this a very simple shader. Later on this will get more complex.
+static const char* SP_FRAGMENT = "precision mediump float;"
+ "varying vec4 v_Position;"
+ "void main() {"
+ " gl_FragColor = v_Position;"
+ "}";
+
+ShaderPerfRenderer::ShaderPerfRenderer(ANativeWindow* window, bool offscreen, int workload) :
+ Renderer(window, offscreen, workload) {
+}
+
+bool ShaderPerfRenderer::setUp() {
+ if (!Renderer::setUp()) {
+ return false;
+ }
+ // Create program.
+ mProgramId = GLUtils::createProgram(&SP_VERTEX, &SP_FRAGMENT);
+ if (mProgramId == 0)
+ return false;
+ // Bind attributes.
+ mPositionHandle = glGetAttribLocation(mProgramId, "a_Position");
+ return true;
+}
+
+bool ShaderPerfRenderer::draw() {
+ if (mOffscreen) {
+ glBindFramebuffer(GL_FRAMEBUFFER, mFboId);
+ }
+ glUseProgram(mProgramId);
+ // Set the background clear color to black.
+ glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+ glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
+
+ // No culling of back faces
+ glDisable(GL_CULL_FACE);
+
+ glEnableVertexAttribArray(mPositionHandle);
+ glVertexAttribPointer(mPositionHandle, 3, GL_FLOAT, false, 0, SP_VERTICES);
+
+ glDrawArrays(GL_TRIANGLES, 0, SP_NUM_VERTICES);
+
+ GLuint err = glGetError();
+ if (err != GL_NO_ERROR) {
+ ALOGV("GLError %d", err);
+ return false;
+ }
+
+ if (mOffscreen) {
+ glFinish();
+ return true;
+ } else {
+ return eglSwapBuffers(mEglDisplay, mEglSurface);
+ }
+}
diff --git a/suite/pts/deviceTests/opengl/jni/shaderperf/ShaderPerfRenderer.h b/suite/pts/deviceTests/opengl/jni/shaderperf/ShaderPerfRenderer.h
new file mode 100644
index 0000000..22e0420
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/shaderperf/ShaderPerfRenderer.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+#ifndef SHADERPERFRENDERER_H
+#define SHADERPERFRENDERER_H
+
+#include <Renderer.h>
+
+class ShaderPerfRenderer: public Renderer {
+public:
+ ShaderPerfRenderer(ANativeWindow* window, bool offscreen, int workload);
+ virtual ~ShaderPerfRenderer() {};
+ bool setUp();
+ bool draw();
+private:
+ GLuint mPositionHandle;
+};
+
+#endif
diff --git a/suite/pts/deviceTests/opengl/src/com/android/pts/opengl/primitive/Benchmark.java b/suite/pts/deviceTests/opengl/src/com/android/pts/opengl/primitive/Benchmark.java
new file mode 100644
index 0000000..d61f45d
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/src/com/android/pts/opengl/primitive/Benchmark.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.android.pts.opengl.primitive;
+
+/**
+ * Represents the different primitive benchmarks.
+ */
+public enum Benchmark {
+ FullPipeline,
+ PixelOutput,
+ ShaderPerf,
+ ContextSwitch;
+}
diff --git a/suite/pts/deviceTests/opengl/src/com/android/pts/opengl/primitive/GLActivity.java b/suite/pts/deviceTests/opengl/src/com/android/pts/opengl/primitive/GLActivity.java
new file mode 100644
index 0000000..df0698a
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/src/com/android/pts/opengl/primitive/GLActivity.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.android.pts.opengl.primitive;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.cts.util.WatchDog;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.Surface;
+
+import com.android.pts.util.ResultType;
+import com.android.pts.util.ResultUnit;
+
+import java.util.ArrayList;
+import java.util.concurrent.Semaphore;
+
+public class GLActivity extends Activity {
+
+ public final static String TAG = "GLActivity";
+ /**
+ * Holds the name of the benchmark to run.
+ */
+ public final static String INTENT_EXTRA_BENCHMARK_NAME = "benchmark_name";
+ /**
+ * Holds whether or not the benchmark is to be run offscreen.
+ */
+ public final static String INTENT_EXTRA_OFFSCREEN = "offscreen";
+ /**
+ * The number of frames to render for each workload.
+ */
+ public final static String INTENT_EXTRA_NUM_FRAMES = "num_frames";
+ /**
+ * The number of iterations to run, the workload increases with each iteration.
+ */
+ public final static String INTENT_EXTRA_NUM_ITERATIONS = "num_iterations";
+ /**
+ * The number of milliseconds to wait before timing out.
+ */
+ public final static String INTENT_EXTRA_TIMEOUT = "timeout";
+
+ private Worker runner;
+ private volatile Exception mException;
+ private volatile Surface mSurface;
+ private Semaphore mSemaphore = new Semaphore(0);
+
+ private Benchmark mBenchmark;
+ private boolean mOffscreen;
+ private int mNumFrames;
+ private int mNumIterations;
+ private int mTimeout;
+ public double[] mFpsValues;
+
+ @Override
+ public void onCreate(Bundle data) {
+ super.onCreate(data);
+ System.loadLibrary("ptsopengl_jni");
+ Intent intent = getIntent();
+ mBenchmark = Benchmark.valueOf(intent.getStringExtra(INTENT_EXTRA_BENCHMARK_NAME));
+ mOffscreen = intent.getBooleanExtra(INTENT_EXTRA_OFFSCREEN, false);
+ mNumFrames = intent.getIntExtra(INTENT_EXTRA_NUM_FRAMES, 0);
+ mNumIterations = intent.getIntExtra(INTENT_EXTRA_NUM_ITERATIONS, 0);
+ mTimeout = intent.getIntExtra(INTENT_EXTRA_TIMEOUT, 0);
+ mFpsValues = new double[mNumIterations];
+
+ Log.i(TAG, "Benchmark: " + mBenchmark);
+ Log.i(TAG, "Offscreen: " + mOffscreen);
+ Log.i(TAG, "Num Frames: " + mNumFrames);
+ Log.i(TAG, "Num Iterations: " + mNumIterations);
+ Log.i(TAG, "Time Out: " + mTimeout);
+
+ SurfaceView surfaceView = new SurfaceView(this);
+ surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+ mSurface = holder.getSurface();
+ }
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {}
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {}
+ });
+ setContentView(surfaceView);
+ }
+
+ public void spawnAndWaitForCompletion() throws Exception {
+ // Spawns a worker to run the benchmark.
+ runner = new Worker();
+ runner.start();
+ // Wait for semiphore.
+ mSemaphore.acquire();
+ if (mException != null) {
+ throw mException;
+ }
+ }
+
+ private void complete() {
+ // Release semiphore.
+ mSemaphore.release();
+ }
+
+ private synchronized void setException(Exception e) {
+ if (mException == null) {
+ mException = e;
+ }
+ }
+
+ private static native void setupFullPipelineBenchmark(
+ Surface surface, boolean offscreen, int workload);
+
+ private static native void setupPixelOutputBenchmark(
+ Surface surface, boolean offscreen, int workload);
+
+ private static native void setupShaderPerfBenchmark(
+ Surface surface, boolean offscreen, int workload);
+
+ private static native void setupContextSwitchBenchmark(
+ Surface surface, boolean offscreen, int workload);
+
+ private static native boolean startBenchmark(int numFrames, double[] frameTimes);
+
+ /**
+ * This thread runs the benchmarks, freeing the UI thread.
+ */
+ private class Worker extends Thread implements WatchDog.TimeoutCallback {
+
+ private WatchDog watchDog;
+ private volatile boolean success = true;
+
+ @Override
+ public void run() {
+ // Creates a watchdog to ensure a iteration doesn't exceed the timeout.
+ watchDog = new WatchDog(mTimeout, this);
+ // Used to record the start and end time of the iteration.
+ double[] times = new double[2];
+ for (int i = 0; i < mNumIterations && success; i++) {
+ // The workload to use for this iteration.
+ int workload = i + 1;
+ // Setup the benchmark.
+ switch (mBenchmark) {
+ case FullPipeline:
+ setupFullPipelineBenchmark(mSurface, mOffscreen, workload);
+ break;
+ case PixelOutput:
+ setupPixelOutputBenchmark(mSurface, mOffscreen, workload);
+ break;
+ case ShaderPerf:
+ setupShaderPerfBenchmark(mSurface, mOffscreen, workload);
+ break;
+ case ContextSwitch:
+ setupContextSwitchBenchmark(mSurface, mOffscreen, workload);
+ break;
+ }
+ watchDog.start();
+ // Start benchmark.
+ success = startBenchmark(mNumFrames, times);
+ watchDog.stop();
+
+ if (!success) {
+ setException(new Exception("Benchmark failed to run"));
+ } else {
+ // Calculate FPS.
+ mFpsValues[i] = mNumFrames * 1000.0f / (times[1] - times[0]);
+ }
+ }
+ complete();
+ }
+
+ public void onTimeout() {
+ setException(new Exception("Benchmark timed out"));
+ complete();
+ }
+
+ }
+}
diff --git a/suite/pts/deviceTests/opengl/src/com/android/pts/opengl/primitive/GLBenchmark.java b/suite/pts/deviceTests/opengl/src/com/android/pts/opengl/primitive/GLBenchmark.java
new file mode 100644
index 0000000..b1c9112
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/src/com/android/pts/opengl/primitive/GLBenchmark.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.android.pts.opengl.primitive;
+
+import com.android.pts.util.PtsActivityInstrumentationTestCase2;
+import com.android.pts.util.ResultType;
+import com.android.pts.util.ResultUnit;
+
+import android.content.Intent;
+import android.cts.util.TimeoutReq;
+import android.opengl.Matrix;
+import android.util.Log;
+
+import java.util.Arrays;
+
+/**
+ * Runs the Primitive OpenGL ES 2.0 Benchmarks.
+ */
+public class GLBenchmark extends PtsActivityInstrumentationTestCase2<GLActivity> {
+
+ public GLBenchmark() {
+ super(GLActivity.class);
+ }
+
+ /**
+ * Runs the full OpenGL ES 2.0 pipeline test offscreen.
+ */
+ @TimeoutReq(minutes = 20)
+ public void testFullPipelineOffscreen() throws Exception {
+ runBenchmark(Benchmark.FullPipeline, true, 500, 8, 1000000);
+ }
+
+ /**
+ * Runs the full OpenGL ES 2.0 pipeline test onscreen.
+ */
+ @TimeoutReq(minutes = 20)
+ public void testFullPipelineOnscreen() throws Exception {
+ runBenchmark(Benchmark.FullPipeline, false, 500, 8, 1000000);
+ }
+
+ /**
+ * Runs the pixel output test offscreen.
+ */
+ public void testPixelOutputOffscreen() throws Exception {
+ runBenchmark(Benchmark.PixelOutput, true, 500, 8, 1000000);
+ }
+
+ /**
+ * Runs the pixel output test onscreen.
+ */
+ public void testPixelOutputOnscreen() throws Exception {
+ runBenchmark(Benchmark.PixelOutput, false, 500, 8, 1000000);
+ }
+
+ /**
+ * Runs the shader performance test offscreen.
+ */
+ public void testShaderPerfOffscreen() throws Exception {
+ // TODO(stuartscott): Not yet implemented
+ // runBenchmark(Benchmark.ShaderPerf, true, 500, 8, 1000000);
+ }
+
+ /**
+ * Runs the shader performance test onscreen.
+ */
+ public void testShaderPerfOnscreen() throws Exception {
+ // TODO(stuartscott): Not yet implemented
+ // runBenchmark(Benchmark.ShaderPerf, false, 500, 8, 1000000);
+ }
+
+ /**
+ * Runs the context switch overhead test offscreen.
+ */
+ public void testContextSwitchOffscreen() throws Exception {
+ runBenchmark(Benchmark.ContextSwitch, true, 500, 8, 1000000);
+ }
+
+ /**
+ * Runs the context switch overhead test onscreen.
+ */
+ public void testContextSwitchOnscreen() throws Exception {
+ runBenchmark(Benchmark.ContextSwitch, false, 500, 8, 1000000);
+ }
+
+ /**
+ * Runs the specified test.
+ *
+ * @param benchmark An enum representing the benchmark to run.
+ * @param offscreen Whether to render to an offscreen framebuffer rather than the screen.
+ * @param numFrames The number of frames to render.
+ * @param numIterations The number of iterations to run, each iteration has a bigger workload.
+ * @param timeout The milliseconds to wait for an iteration of the benchmark before timing out.
+ * @throws Exception If the benchmark could not be run.
+ */
+ private void runBenchmark(
+ Benchmark benchmark, boolean offscreen, int numFrames, int numIterations, int timeout)
+ throws Exception {
+ String benchmarkName = benchmark.toString();
+ Intent intent = new Intent();
+ intent.putExtra(GLActivity.INTENT_EXTRA_BENCHMARK_NAME, benchmarkName);
+ intent.putExtra(GLActivity.INTENT_EXTRA_OFFSCREEN, offscreen);
+ intent.putExtra(GLActivity.INTENT_EXTRA_NUM_FRAMES, numFrames);
+ intent.putExtra(GLActivity.INTENT_EXTRA_NUM_ITERATIONS, numIterations);
+ intent.putExtra(GLActivity.INTENT_EXTRA_TIMEOUT, timeout);
+
+ GLActivity activity = null;
+ setActivityIntent(intent);
+ try {
+ activity = getActivity();
+ activity.spawnAndWaitForCompletion();
+ } finally {
+ if (activity != null) {
+ double[] fpsValues = activity.mFpsValues;
+ double score = 0;
+ for (double d : fpsValues) {
+ score += d;
+ }
+ getReportLog().printArray(
+ "Fps Values", fpsValues, ResultType.HIGHER_BETTER, ResultUnit.FPS);
+ getReportLog()
+ .printSummary("Score", score, ResultType.HIGHER_BETTER, ResultUnit.SCORE);
+ activity.finish();
+ }
+ }
+ }
+}
diff --git a/suite/pts/deviceTests/opengl/test/Android.mk b/suite/pts/deviceTests/opengl/test/Android.mk
new file mode 100644
index 0000000..1939b2d
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/test/Android.mk
@@ -0,0 +1,34 @@
+#
+# Copyright (C) 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# build only for linux
+ifeq ($(HOST_OS),linux)
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+LOCAL_SRC_FILES := $(patsubst ./%,%, $(shell cd $(LOCAL_PATH); \
+ find . -name "*.cpp" -and -not -name ".*"))
+LOCAL_SRC_FILES += ../jni/graphics/Matrix.cpp
+
+#$(info $(LOCAL_SRC_FILES))
+LOCAL_C_INCLUDES += external/gtest/include $(LOCAL_PATH)/../jni/graphics/
+LOCAL_STATIC_LIBRARIES := libutils libcutils libgtest_host libgtest_main_host
+LOCAL_LDFLAGS:= -g -lrt -ldl -lstdc++ -lm -fno-exceptions
+LOCAL_MODULE:= pts_device_opengl_test
+include $(BUILD_HOST_EXECUTABLE)
+
+endif # linux
\ No newline at end of file
diff --git a/suite/pts/deviceTests/opengl/test/MatrixTest.cpp b/suite/pts/deviceTests/opengl/test/MatrixTest.cpp
new file mode 100644
index 0000000..d78dfc4
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/test/MatrixTest.cpp
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+#include <gtest/gtest.h>
+#include <math.h>
+
+#include "Matrix.h"
+
+class MatrixTest: public testing::Test {
+public:
+
+};
+
+void checkValues(const float* arr1, const float* arr2, const int size) {
+ for (int i = 0; i < size; i++) {
+ ASSERT_FLOAT_EQ(arr1[i], arr2[i]);
+ }
+}
+
+TEST(MatrixTest, matrixEqualityTest) {
+ // Create two identity matrixes.
+ Matrix m1;
+ Matrix m2;
+ // Change some random values.
+ m1.mData[4] = 9;
+ m2.mData[4] = 9;
+ // Check they are the same.
+ ASSERT_TRUE(m1.equals(m2));
+ Matrix* clone = new Matrix(m1);
+ ASSERT_TRUE(clone != NULL);
+ ASSERT_TRUE(m1.equals(*clone));
+ delete clone;
+}
+
+TEST(MatrixTest, matrixIdentityTest) {
+ // Create an identity matrix.
+ Matrix m;
+ float expected[] = {
+ 1.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 1.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 1.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f};
+ // Check values
+ checkValues(m.mData, expected, Matrix::MATRIX_SIZE);
+}
+
+TEST(MatrixTest, matrixLoadWithTest) {
+ // Create a matrix.
+ Matrix m1;
+ float* d1 = m1.mData;
+ float data[Matrix::MATRIX_SIZE];
+
+ // Fill with rubbish
+ for (int i = 0; i < Matrix::MATRIX_SIZE; i++) {
+ d1[i] = i;
+ data[i] = i;
+ }
+
+ // Create another matrix
+ Matrix m2;
+
+ // Load second matrix with first
+ m2.loadWith(m1);
+
+ // Check values
+ checkValues(m2.mData, data, Matrix::MATRIX_SIZE);
+}
+
+TEST(MatrixTest, matrixTranslateTest) {
+ Matrix m1;
+ m1.translate(10, 5, 6);
+ Matrix* m2 = Matrix::newTranslate(10, 5, 6);
+ ASSERT_TRUE(m2 != NULL);
+ ASSERT_TRUE(m1.equals(*m2));
+ delete m2;
+}
+
+TEST(MatrixTest, matrixScaleTest) {
+ Matrix m1;
+ m1.scale(10, 5, 6);
+ Matrix* m2 = Matrix::newScale(10, 5, 6);
+ ASSERT_TRUE(m2 != NULL);
+ ASSERT_TRUE(m1.equals(*m2));
+ delete m2;
+}
+
+TEST(MatrixTest, matrixRotateTest) {
+ Matrix m1;
+ m1.rotate(180, 1, 0, 1);
+ Matrix* m2 = Matrix::newRotate(180, 1, 0, 1);
+ ASSERT_TRUE(m2 != NULL);
+ ASSERT_TRUE(m1.equals(*m2));
+ delete m2;
+}
+
+TEST(MatrixTest, matrixMultiplyTest) {
+ // Create three identity matrixes.
+ Matrix m1;
+ Matrix m2;
+ Matrix m3;
+ float* d1 = m1.mData;
+ float* d2 = m2.mData;
+
+ m3.multiply(m1, m2);
+ // Multiplication of identity matrixes should give identity
+ ASSERT_TRUE(m3.equals(m1));
+
+ // Fill with ascending numbers
+ for (int i = 0; i < Matrix::MATRIX_SIZE; i++) {
+ d1[i] = i;
+ d2[i] = i;
+ }
+ m3.multiply(m1, m2);
+
+ // Check against expected
+ float expected[] = {
+ 56, 62, 68, 74,
+ 152, 174, 196, 218,
+ 248, 286, 324, 362,
+ 344, 398, 452, 506};
+ checkValues(m3.mData, expected, Matrix::MATRIX_SIZE);
+}
+
+TEST(MatrixTest, matrixNewLookAtTest) {
+ // Position the eye in front of the origin.
+ float eyeX = 0.0f;
+ float eyeY = 0.0f;
+ float eyeZ = 6.0f;
+
+ // We are looking at the origin
+ float centerX = 0.0f;
+ float centerY = 0.0f;
+ float centerZ = 0.0f;
+
+ // Set our up vector. This is where our head would be pointing were we holding the camera.
+ float upX = 0.0f;
+ float upY = 1.0f;
+ float upZ = 0.0f;
+
+ // Set the view matrix. This matrix can be said to represent the camera position.
+ Matrix* m = Matrix::newLookAt(eyeX, eyeY, eyeZ, centerX, centerY, centerZ,
+ upX, upY, upZ);
+ ASSERT_TRUE(m != NULL);
+ float expected[] = {
+ 1.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 1.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 1.0f, 0.0f,
+ 0.0f, 0.0f, -6.0f, 1.0f};
+ // Check values
+ checkValues(m->mData, expected, Matrix::MATRIX_SIZE);
+ delete m;
+}
+
+TEST(MatrixTest, matrixNewFrustumTest) {
+ float ratio = (float) 800 / 600;
+ float left = -ratio;
+ float right = ratio;
+ float bottom = -1.0f;
+ float top = 1.0f;
+ float near = 1.0f;
+ float far = 8.0f;
+
+ Matrix* m = Matrix::newFrustum(left, right, bottom, top, near, far);
+ ASSERT_TRUE(m != NULL);
+ float expected[] = {
+ 0.75f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 1.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 9.0f / -7.0f, -1.0f,
+ 0.0f, 0.0f, 16.0f / -7.0f, 0.0f};
+ // Check values
+ checkValues(m->mData, expected, Matrix::MATRIX_SIZE);
+ delete m;
+}
+
+TEST(MatrixTest, matrixNewTranslateTest) {
+ Matrix* m = Matrix::newTranslate(5, 6, 8);
+ ASSERT_TRUE(m != NULL);
+ float expected[] = {
+ 1.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 1.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 1.0f, 0.0f,
+ 5.0f, 6.0f, 8.0f, 1.0f};
+ // Check values
+ checkValues(m->mData, expected, Matrix::MATRIX_SIZE);
+ delete m;
+}
+
+TEST(MatrixTest, matrixNewScaleTest) {
+ Matrix* m = Matrix::newScale(3, 7, 2);
+ ASSERT_TRUE(m != NULL);
+ float expected[] = {
+ 3.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 7.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 2.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f};
+ // Check values
+ checkValues(m->mData, expected, Matrix::MATRIX_SIZE);
+ delete m;
+}
+
+TEST(MatrixTest, matrixNewRotateTest) {
+ Matrix* m = Matrix::newRotate(45.0f, 0.0f, 1.0f, 0.0f);
+ ASSERT_TRUE(m != NULL);
+ float radians = 45.0f * (M_PI / 180.0f);
+ float sin = sinf(radians);
+ float cos = cosf(radians);
+ float expected[] = {
+ cos, 0.0f, -sin, 0.0f,
+ 0.0f, 1.0f, 0.0f, 0.0f,
+ sin, 0.0f, cos, 0.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f};
+ // Check values
+ checkValues(m->mData, expected, Matrix::MATRIX_SIZE);
+ delete m;
+}
+
+TEST(MatrixTest, matrixMultiplyVectorTest) {
+ float in[] = {2, 4, 6, 8};
+ float out[4];
+ Matrix m;
+ float* d = m.mData;
+ // Fill with rubbish
+ for (int i = 0; i < Matrix::MATRIX_SIZE; i++) {
+ d[i] = i;
+ }
+ float expected[] = {40, 120, 200, 280};
+ Matrix::multiplyVector(out, m, in);
+ checkValues(out, expected, 4);
+}
diff --git a/suite/pts/hostTests/browser/browserlauncher/Android.mk b/suite/pts/deviceTests/simplecpu/Android.mk
similarity index 83%
copy from suite/pts/hostTests/browser/browserlauncher/Android.mk
copy to suite/pts/deviceTests/simplecpu/Android.mk
index 0592017..69db471 100644
--- a/suite/pts/hostTests/browser/browserlauncher/Android.mk
+++ b/suite/pts/deviceTests/simplecpu/Android.mk
@@ -20,14 +20,16 @@
LOCAL_JAVA_LIBRARIES := android.test.runner
-LOCAL_STATIC_JAVA_LIBRARIES := ptsutil ctsutil ctstestrunner ctstestserver
+LOCAL_STATIC_JAVA_LIBRARIES := ptsutil ctsutil ctstestrunner
+
+LOCAL_JNI_SHARED_LIBRARIES := libptscpu_jni
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_PACKAGE_NAME := PtsDeviceBrowserLauncher
+LOCAL_PACKAGE_NAME := PtsDeviceSimpleCpu
LOCAL_SDK_VERSION := 16
-include $(BUILD_PTS_PACKAGE)
+include $(BUILD_CTS_PACKAGE)
include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/suite/pts/deviceTests/simplecpu/AndroidManifest.xml b/suite/pts/deviceTests/simplecpu/AndroidManifest.xml
new file mode 100644
index 0000000..930bcbd
--- /dev/null
+++ b/suite/pts/deviceTests/simplecpu/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.pts.simplecpu">
+
+ <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+ <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ android:targetPackage="com.android.pts.simplecpu"
+ android:label="Vesy simple CPU benchmarking" />
+</manifest>
diff --git a/suite/pts/hostTests/ptshostutil/Android.mk b/suite/pts/deviceTests/simplecpu/jni/Android.mk
similarity index 76%
rename from suite/pts/hostTests/ptshostutil/Android.mk
rename to suite/pts/deviceTests/simplecpu/jni/Android.mk
index 14e786a..9c34e0e 100644
--- a/suite/pts/hostTests/ptshostutil/Android.mk
+++ b/suite/pts/deviceTests/simplecpu/jni/Android.mk
@@ -11,19 +11,21 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-
+#
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_SRC_FILES := \
- $(call all-java-files-under, src)
-
-LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed-prebuilt ddmlib-prebuilt junit
+LOCAL_MODULE := libptscpu_jni
LOCAL_MODULE_TAGS := optional
-LOCAL_MODULE := ptshostutil
+LOCAL_SRC_FILES := CpuNativeJni.cpp
-include $(BUILD_HOST_JAVA_LIBRARY)
+LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
+LOCAL_SHARED_LIBRARIES := libnativehelper
+
+LOCAL_SDK_VERSION := 14
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/suite/pts/deviceTests/simplecpu/jni/CpuNativeJni.cpp b/suite/pts/deviceTests/simplecpu/jni/CpuNativeJni.cpp
new file mode 100644
index 0000000..851f35e
--- /dev/null
+++ b/suite/pts/deviceTests/simplecpu/jni/CpuNativeJni.cpp
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <jni.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+
+/* Code from now to qsort_local all copied from bionic source.
+ * The code is duplicated here to remove dependency on optimized bionic
+ */
+static __inline char *med3(char *, char *, char *, int (*)(const void *, const void *));
+static __inline void swapfunc(char *, char *, int, int);
+
+#define min(a, b) (a) < (b) ? a : b
+
+/*
+ * Qsort routine from Bentley & McIlroy's "Engineering a Sort Function".
+ */
+#define swapcode(TYPE, parmi, parmj, n) { \
+ long i = (n) / sizeof (TYPE); \
+ TYPE *pi = (TYPE *) (parmi); \
+ TYPE *pj = (TYPE *) (parmj); \
+ do { \
+ TYPE t = *pi; \
+ *pi++ = *pj; \
+ *pj++ = t; \
+ } while (--i > 0); \
+}
+
+#define SWAPINIT(a, es) swaptype = ((char *)a - (char *)0) % sizeof(long) || \
+ es % sizeof(long) ? 2 : es == sizeof(long)? 0 : 1;
+
+static __inline void
+swapfunc(char *a, char *b, int n, int swaptype)
+{
+ if (swaptype <= 1)
+ swapcode(long, a, b, n)
+ else
+ swapcode(char, a, b, n)
+}
+
+#define swap(a, b) \
+ if (swaptype == 0) { \
+ long t = *(long *)(a); \
+ *(long *)(a) = *(long *)(b); \
+ *(long *)(b) = t; \
+ } else \
+ swapfunc(a, b, es, swaptype)
+
+#define vecswap(a, b, n) if ((n) > 0) swapfunc(a, b, n, swaptype)
+
+static __inline char *
+med3(char *a, char *b, char *c, int (*cmp)(const void *, const void *))
+{
+ return cmp(a, b) < 0 ?
+ (cmp(b, c) < 0 ? b : (cmp(a, c) < 0 ? c : a ))
+ :(cmp(b, c) > 0 ? b : (cmp(a, c) < 0 ? a : c ));
+}
+
+void
+qsort_local(void *aa, size_t n, size_t es, int (*cmp)(const void *, const void *))
+{
+ char *pa, *pb, *pc, *pd, *pl, *pm, *pn;
+ int d, r, swaptype, swap_cnt;
+ char *a = (char*)aa;
+
+loop: SWAPINIT(a, es);
+ swap_cnt = 0;
+ if (n < 7) {
+ for (pm = (char *)a + es; pm < (char *) a + n * es; pm += es)
+ for (pl = pm; pl > (char *) a && cmp(pl - es, pl) > 0;
+ pl -= es)
+ swap(pl, pl - es);
+ return;
+ }
+ pm = (char *)a + (n / 2) * es;
+ if (n > 7) {
+ pl = (char *)a;
+ pn = (char *)a + (n - 1) * es;
+ if (n > 40) {
+ d = (n / 8) * es;
+ pl = med3(pl, pl + d, pl + 2 * d, cmp);
+ pm = med3(pm - d, pm, pm + d, cmp);
+ pn = med3(pn - 2 * d, pn - d, pn, cmp);
+ }
+ pm = med3(pl, pm, pn, cmp);
+ }
+ swap(a, pm);
+ pa = pb = (char *)a + es;
+
+ pc = pd = (char *)a + (n - 1) * es;
+ for (;;) {
+ while (pb <= pc && (r = cmp(pb, a)) <= 0) {
+ if (r == 0) {
+ swap_cnt = 1;
+ swap(pa, pb);
+ pa += es;
+ }
+ pb += es;
+ }
+ while (pb <= pc && (r = cmp(pc, a)) >= 0) {
+ if (r == 0) {
+ swap_cnt = 1;
+ swap(pc, pd);
+ pd -= es;
+ }
+ pc -= es;
+ }
+ if (pb > pc)
+ break;
+ swap(pb, pc);
+ swap_cnt = 1;
+ pb += es;
+ pc -= es;
+ }
+ if (swap_cnt == 0) { /* Switch to insertion sort */
+ for (pm = (char *) a + es; pm < (char *) a + n * es; pm += es)
+ for (pl = pm; pl > (char *) a && cmp(pl - es, pl) > 0;
+ pl -= es)
+ swap(pl, pl - es);
+ return;
+ }
+
+ pn = (char *)a + n * es;
+ r = min(pa - (char *)a, pb - pa);
+ vecswap(a, pb - r, r);
+ r = min(pd - pc, pn - pd - (int)es);
+ vecswap(pb, pn - r, r);
+ if ((r = pb - pa) > (int)es)
+ qsort_local(a, r / es, es, cmp);
+ if ((r = pd - pc) > (int)es) {
+ /* Iterate rather than recurse to save stack space */
+ a = pn - r;
+ n = r / es;
+ goto loop;
+ }
+ /* qsort(pn - r, r / es, es, cmp); */
+}
+
+/* code duplication ends here */
+
+/**
+ * Util for getting time stamp
+ */
+double currentTimeMillis()
+{
+ struct timeval tv;
+ gettimeofday(&tv, (struct timezone *) NULL);
+ return tv.tv_sec * 1000.0 + tv.tv_usec / 1000.0;
+}
+
+/**
+ * Initialize given array randomly for the given seed
+ */
+template <typename T> void randomInitArray(T* array, int len, unsigned int seed)
+{
+ srand(seed);
+ for (int i = 0; i < len; i++) {
+ array[i] = (T) rand();
+ }
+}
+
+/**
+ * comparison function for int, for qsort
+ */
+int cmpint(const void* p1, const void* p2)
+{
+ return *(int*)p1 - *(int*)p2;
+}
+
+extern "C" JNIEXPORT jdouble JNICALL Java_com_android_pts_simplecpu_CpuNative_runSort(JNIEnv* env,
+ jclass clazz, jint numberElements, jint repetition)
+{
+ int* data = new int[numberElements];
+ if (data == NULL) {
+ env->ThrowNew(env->FindClass("java/lang/OutOfMemoryError"), "No memory");
+ return -1;
+ }
+ double totalTime = 0;
+ for (int i = 0; i < repetition; i++) {
+ randomInitArray<int>(data, numberElements, 0);
+ double start = currentTimeMillis();
+ qsort_local(data, numberElements, sizeof(int), cmpint);
+ double end = currentTimeMillis();
+ totalTime += (end - start);
+ }
+ delete[] data;
+ return totalTime;
+}
+
+
+/**
+ * Do matrix multiplication, C = A x B with all matrices having dimension of n x n
+ * The implementation is not in the most efficient, but it is good enough for benchmarking purpose.
+ * @param n should be multiple of 8
+ */
+void doMatrixMultiplication(float* A, float* B, float* C, int n)
+{
+ // batch size
+ const int M = 8;
+ for (int i = 0; i < n; i++) {
+ for (int j = 0; j < n; j += M) {
+ float sum[M];
+ for (int k = 0; k < M; k++) {
+ sum[k] = 0;
+ }
+ // re-use the whole cache line for accessing B.
+ // otherwise, the whole line will be read and only one value will be used.
+
+ for (int k = 0; k < n; k++) {
+ float a = A[i * n + k];
+ sum[0] += a * B[k * n + j];
+ sum[1] += a * B[k * n + j + 1];
+ sum[2] += a * B[k * n + j + 2];
+ sum[3] += a * B[k * n + j + 3];
+ sum[4] += a * B[k * n + j + 4];
+ sum[5] += a * B[k * n + j + 5];
+ sum[6] += a * B[k * n + j + 6];
+ sum[7] += a * B[k * n + j + 7];
+ }
+ for (int k = 0; k < M; k++) {
+ C[i * n + j + k] = sum[k];
+ }
+ }
+ }
+}
+
+extern "C" JNIEXPORT jdouble JNICALL Java_com_android_pts_simplecpu_CpuNative_runMatrixMultiplication(
+ JNIEnv* env, jclass clazz, jint n, jint repetition)
+{
+ // C = A x B
+ float* A = new float[n * n];
+ float* B = new float[n * n];
+ float* C = new float[n * n];
+ if ((A == NULL) || (B == NULL) || (C == NULL)) {
+ delete[] A;
+ delete[] B;
+ delete[] C;
+ env->ThrowNew(env->FindClass("java/lang/OutOfMemoryError"), "No memory");
+ return -1;
+ }
+ double totalTime = 0;
+ for (int i = 0; i < repetition; i++) {
+ randomInitArray<float>(A, n * n, 0);
+ randomInitArray<float>(B, n * n, 1);
+ double start = currentTimeMillis();
+ doMatrixMultiplication(A, B, C, n);
+ double end = currentTimeMillis();
+ totalTime += (end - start);
+ }
+ delete[] A;
+ delete[] B;
+ delete[] C;
+ return totalTime;
+}
+
diff --git a/suite/pts/deviceTests/simplecpu/src/com/android/pts/simplecpu/CpuNative.java b/suite/pts/deviceTests/simplecpu/src/com/android/pts/simplecpu/CpuNative.java
new file mode 100644
index 0000000..43c4b50
--- /dev/null
+++ b/suite/pts/deviceTests/simplecpu/src/com/android/pts/simplecpu/CpuNative.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.pts.simplecpu;
+
+public class CpuNative {
+ static {
+ System.loadLibrary("ptscpu_jni");
+ }
+ /**
+ * run qsort for given number of repetition
+ * with each having the size of bufferSize.
+ * @param numberElements
+ * @param repeatition
+ * @return time taken for computation, added for all repetition in ms
+ */
+ public static native double runSort(int numberElements, int repetition);
+
+ /**
+ * run matrix multiplication of (n x n) x (n x n)
+ *
+ * @param n dimension, should be multiple of 8
+ * @param repetition
+ * @return time taken for computation, added for all repetition in ms
+ */
+ public static native double runMatrixMultiplication(int n, int repetition);
+}
diff --git a/suite/pts/deviceTests/simplecpu/src/com/android/pts/simplecpu/SimpleCpuTest.java b/suite/pts/deviceTests/simplecpu/src/com/android/pts/simplecpu/SimpleCpuTest.java
new file mode 100644
index 0000000..8b17171
--- /dev/null
+++ b/suite/pts/deviceTests/simplecpu/src/com/android/pts/simplecpu/SimpleCpuTest.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.pts.simplecpu;
+
+import android.cts.util.TimeoutReq;
+
+import com.android.pts.util.ResultType;
+import com.android.pts.util.ResultUnit;
+import com.android.pts.util.PtsAndroidTestCase;
+import com.android.pts.util.ReportLog;
+import com.android.pts.util.Stat;
+
+/**
+ * Very simple CPU benchmarking to check the basic capability of CPU.
+ * Cases include
+ * qsort
+ * matrix multiplication (for floating point performance)
+ */
+public class SimpleCpuTest extends PtsAndroidTestCase {
+ private static final String TAG = "BandwidthTest";
+ private static final int KB = 1024;
+ private static final int MB = 1024 * 1024;
+ private static final int NUMBER_REPEAT = 20;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ warmUpCpu();
+ }
+
+ public void testSort004KB() {
+ doTestSort(NUMBER_REPEAT, 4 * KB);
+ }
+
+ public void testSort128KB() {
+ doTestSort(NUMBER_REPEAT, 128 * KB);
+ }
+
+ public void testSort001MB() {
+ doTestSort(NUMBER_REPEAT, 1 * MB);
+ }
+
+ // will fit into L1
+ public void testMatrixMultiplication032() {
+ doMatrixMultiplication(NUMBER_REPEAT, 32);
+ }
+
+ // mostly fit into L2
+ public void testMatrixMultiplication128() {
+ doMatrixMultiplication(NUMBER_REPEAT, 128);
+ }
+
+ // may fit into L2
+ public void testMatrixMultiplication200() {
+ doMatrixMultiplication(NUMBER_REPEAT, 200);
+ }
+
+ public void testMatrixMultiplication400() {
+ doMatrixMultiplication(NUMBER_REPEAT, 400);
+ }
+
+ // will exceed L2
+ @TimeoutReq(minutes = 20)
+ public void testMatrixMultiplication600() {
+ doMatrixMultiplication(NUMBER_REPEAT, 600);
+ }
+
+ /**
+ * run some code to force full CPU freq.
+ */
+ private void warmUpCpu() {
+ CpuNative.runSort(1 * MB, 10);
+ }
+
+ /**
+ * qsort test
+ * @param numberRepeat
+ * @param arrayLength
+ */
+ private void doTestSort(int numberRepeat, int arrayLength) {
+ final int numberRepeatInEachCall = 10;
+ double[] result = new double[numberRepeat];
+ for (int i = 0; i < numberRepeat; i++) {
+ result[i] = CpuNative.runSort(arrayLength, numberRepeatInEachCall);
+ }
+ getReportLog().printArray("sorting time", result, ResultType.LOWER_BETTER,
+ ResultUnit.MS);
+ Stat.StatResult stat = Stat.getStat(result);
+ getReportLog().printSummary("sorting time", stat.mAverage, ResultType.LOWER_BETTER,
+ ResultUnit.MS);
+ }
+
+ /**
+ * Matrix multiplication test, nxn matrix multiplication
+ * @param numberRepeat
+ * @param n should be multiple of 8
+ */
+ private void doMatrixMultiplication(int numberRepeat, int n) {
+ assertTrue(n % 8 == 0);
+ final int numberRepeatInEachCall = 10;
+ double[] result = new double[numberRepeat];
+ for (int i = 0; i < numberRepeat; i++) {
+ result[i] = CpuNative.runMatrixMultiplication(n, numberRepeatInEachCall);
+ }
+ getReportLog().printArray("matrix mutiplication time", result, ResultType.LOWER_BETTER,
+ ResultUnit.MS);
+ Stat.StatResult stat = Stat.getStat(result);
+ getReportLog().printSummary("matrix mutiplication time", stat.mAverage,
+ ResultType.LOWER_BETTER, ResultUnit.MS);
+ }
+
+}
diff --git a/suite/pts/deviceTests/ui/Android.mk b/suite/pts/deviceTests/ui/Android.mk
index 28db8a1..3790342 100644
--- a/suite/pts/deviceTests/ui/Android.mk
+++ b/suite/pts/deviceTests/ui/Android.mk
@@ -24,12 +24,18 @@
LOCAL_STATIC_JAVA_LIBRARIES := ptsutil ctsutil ctstestrunner
+LOCAL_JNI_SHARED_LIBRARIES := libctsopenglperf_jni
+
LOCAL_SRC_FILES := $(call all-java-files-under, src)
+# re-use existing openglperf code for CTS. Do not include any test from that dir.
+LOCAL_SRC_FILES += $(filter-out %Test.java,$(call all-java-files-under, ../../../../tests/tests/openglperf/src))
+# do not use its own assets for this package.
+LOCAL_ASSET_DIR := $(LOCAL_PATH)/../../../../tests/tests/openglperf/assets
LOCAL_PACKAGE_NAME := PtsDeviceUi
LOCAL_SDK_VERSION := 16
-include $(BUILD_PTS_PACKAGE)
+include $(BUILD_CTS_PACKAGE)
diff --git a/suite/pts/deviceTests/ui/AndroidManifest.xml b/suite/pts/deviceTests/ui/AndroidManifest.xml
index b5e4937b..6925b5d 100644
--- a/suite/pts/deviceTests/ui/AndroidManifest.xml
+++ b/suite/pts/deviceTests/ui/AndroidManifest.xml
@@ -31,6 +31,7 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
+ <activity android:name="android.openglperf.cts.GlPlanetsActivity" />
</application>
<instrumentation android:name="android.test.InstrumentationCtsTestRunner"
android:targetPackage="com.android.pts.ui"
diff --git a/suite/pts/deviceTests/ui/src/com/android/pts/ui/FpsTest.java b/suite/pts/deviceTests/ui/src/com/android/pts/ui/FpsTest.java
new file mode 100644
index 0000000..a2fd8d9
--- /dev/null
+++ b/suite/pts/deviceTests/ui/src/com/android/pts/ui/FpsTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.pts.ui;
+
+import android.content.Context;
+import android.content.Intent;
+import android.openglperf.cts.GlPlanetsActivity;
+import android.util.Log;
+import android.view.Display;
+import android.view.WindowManager;
+
+import com.android.pts.util.ResultType;
+import com.android.pts.util.ResultUnit;
+import com.android.pts.util.PtsActivityInstrumentationTestCase2;
+import com.android.pts.util.ReportLog;
+import com.android.pts.util.Stat;
+
+/**
+ * measure time taken to render n frames with OpenGL.
+ * This will measure the jankiness of the Gl rendering.
+ * If some frames are delayed, total time will take longer than n x refresh_rate
+ */
+public class FpsTest extends PtsActivityInstrumentationTestCase2<GlPlanetsActivity> {
+ private static final String TAG = "FpsTest";
+ private static final int NUM_FRAMES_TO_RENDER = 60 * 60;
+ private static final long RENDERING_TIMEOUT = NUM_FRAMES_TO_RENDER / 10;
+ // error of this much in ms in refresh interval is not considered as jankiness
+ // note that this margin is set to be little big to consider that there can be additional delay
+ // measurement on some devices showed many jankiness around 2 to 3 ms,
+ // which should not be counted
+ private static final double REFRESH_INTERVAL_THRESHHOLD_IN_MS = 4.0;
+ private GlPlanetsActivity mActivity;
+
+ public FpsTest() {
+ super(GlPlanetsActivity.class);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ mActivity = null;
+ super.tearDown();
+ }
+
+ public void testFrameIntervals() throws Exception {
+ Intent intent = new Intent();
+ intent.putExtra(GlPlanetsActivity.INTENT_EXTRA_NUM_FRAMES,
+ NUM_FRAMES_TO_RENDER);
+ intent.putExtra(GlPlanetsActivity.INTENT_EXTRA_NUM_PLANETS, 0);
+ intent.putExtra(GlPlanetsActivity.INTENT_EXTRA_USE_VBO_VERTICES, true);
+ intent.putExtra(GlPlanetsActivity.INTENT_EXTRA_USE_VBO_INDICES, true);
+ intent.putExtra(GlPlanetsActivity.INTENT_EXTRA_NUM_INDEX_BUFFERS, 10);
+
+ setActivityIntent(intent);
+ mActivity = getActivity();
+ boolean waitResult = mActivity.waitForGlPlanetsCompletionWithTimeout(RENDERING_TIMEOUT);
+ assertTrue("timeout while waiting for rendering completion", waitResult);
+
+ int[] frameInterval = mActivity.getFrameInterval();
+ assertTrue(frameInterval.length == NUM_FRAMES_TO_RENDER);
+ double fpsMeasured = mActivity.getAverageFps();
+ WindowManager wm = (WindowManager)mActivity.getSystemService(Context.WINDOW_SERVICE);
+ Display dpy = wm.getDefaultDisplay();
+ double fpsNominal = dpy.getRefreshRate();
+ double frameIntervalNominalInMs = 1000.0 / fpsNominal;
+ int jankNumber = 0;
+ // only count positive ( = real delay)
+ double maxDelay = 0;
+ // first one not valid, and should be thrown away
+ double[] intervals = new double[NUM_FRAMES_TO_RENDER - 1];
+ double[] jankiness = new double[NUM_FRAMES_TO_RENDER - 1];
+ double deltaAccumulated = 0;
+ for (int i = 0; i < NUM_FRAMES_TO_RENDER - 1; i++) {
+ intervals[i] = frameInterval[i + 1];
+ double delay = (double)intervals[i] - frameIntervalNominalInMs;
+ if (Math.abs(delay) > REFRESH_INTERVAL_THRESHHOLD_IN_MS) {
+ // Falling here does not necessarily mean jank as it may be catching up
+ // the previous delay. Basically count the first delay as jank, but subsequent
+ // variation should be checked with accumulated delay
+ double delayAdjusted = 0;
+ if (deltaAccumulated == 0) { // This is the first delay. always consider as total
+ jankNumber++;
+ delayAdjusted = delay;
+ } else { // delay already happened
+ double deltaFromLastRefresh = deltaAccumulated - Math.floor(deltaAccumulated /
+ frameIntervalNominalInMs) * frameIntervalNominalInMs;
+ if (deltaAccumulated < 0) {
+ // adjust as the above operation makes delay positive
+ deltaFromLastRefresh -= frameIntervalNominalInMs;
+ }
+ delayAdjusted = delay + deltaFromLastRefresh;
+ if (Math.abs(delayAdjusted) > REFRESH_INTERVAL_THRESHHOLD_IN_MS) {
+ jankNumber++;
+ } else { // caught up
+ delayAdjusted = 0;
+ deltaAccumulated = 0;
+ }
+ }
+ deltaAccumulated += delay;
+ if (delayAdjusted > maxDelay) {
+ maxDelay = delayAdjusted;
+ }
+ jankiness[i] = delayAdjusted;
+ } else {
+ jankiness[i] = 0;
+ deltaAccumulated = 0;
+ }
+ }
+ Log.i(TAG, " fps nominal " + fpsNominal + " fps measured " + fpsMeasured);
+ getReportLog().printArray("intervals", intervals, ResultType.NEUTRAL,
+ ResultUnit.MS);
+ getReportLog().printArray("jankiness", jankiness, ResultType.LOWER_BETTER,
+ ResultUnit.MS);
+ getReportLog().printValue("number of jank", jankNumber, ResultType.LOWER_BETTER,
+ ResultUnit.COUNT);
+ getReportLog().printSummary("max jank delay", maxDelay, ResultType.LOWER_BETTER,
+ ResultUnit.MS);
+ }
+}
diff --git a/suite/pts/deviceTests/ui/src/com/android/pts/ui/ScrollingActivity.java b/suite/pts/deviceTests/ui/src/com/android/pts/ui/ScrollingActivity.java
index 7bdfff9..fbec65d 100644
--- a/suite/pts/deviceTests/ui/src/com/android/pts/ui/ScrollingActivity.java
+++ b/suite/pts/deviceTests/ui/src/com/android/pts/ui/ScrollingActivity.java
@@ -26,8 +26,10 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
-public class ScrollingActivity extends ListActivity implements OnScrollListener
-{
+/**
+ * Activity for measuring scrolling time of long list.
+ */
+public class ScrollingActivity extends ListActivity implements OnScrollListener {
static final String TAG = "ScrollingActivity";
private static final int NUMBER_ELEMENTS = 10000;
private static final int SCROLL_TIME_IN_MS = 1;
@@ -36,8 +38,7 @@
private CountDownLatch mLatchStop = null;
private int mTargetLoc;
- public void onCreate(Bundle icicle)
- {
+ public void onCreate(Bundle icicle) {
super.onCreate(icicle);
for (int i = 0; i < NUMBER_ELEMENTS; i++) {
mItems[i] = Integer.toString(i);
@@ -46,7 +47,6 @@
android.R.layout.simple_list_item_1, mItems));
ListView view = getListView();
view.setOnScrollListener(this);
- //view.setVelocityScale(100.0f);
}
public boolean scrollToTop() {
@@ -75,15 +75,6 @@
mLatchStop = null;
return result;
}
- public void onStop()
- {
- super.onStop();
- }
-
- public void onResume()
- {
- super.onResume();
- }
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
diff --git a/suite/pts/deviceTests/ui/src/com/android/pts/ui/ScrollingTest.java b/suite/pts/deviceTests/ui/src/com/android/pts/ui/ScrollingTest.java
index 8e4bbed..9fd43de 100644
--- a/suite/pts/deviceTests/ui/src/com/android/pts/ui/ScrollingTest.java
+++ b/suite/pts/deviceTests/ui/src/com/android/pts/ui/ScrollingTest.java
@@ -18,6 +18,8 @@
import android.cts.util.TimeoutReq;
import com.android.pts.util.MeasureRun;
import com.android.pts.util.MeasureTime;
+import com.android.pts.util.ResultType;
+import com.android.pts.util.ResultUnit;
import com.android.pts.util.PtsActivityInstrumentationTestCase2;
import com.android.pts.util.ReportLog;
import com.android.pts.util.Stat;
@@ -65,8 +67,10 @@
assertTrue(activity.scrollToTop());
}
});
- getReportLog().printArray("ms", results, false);
+ getReportLog().printArray("scrolling time", results, ResultType.LOWER_BETTER,
+ ResultUnit.MS);
Stat.StatResult stat = Stat.getStat(results);
- getReportLog().printSummary("Time ms", stat.mAverage, stat.mStddev);
+ getReportLog().printSummary("scrolling time", stat.mAverage, ResultType.LOWER_BETTER,
+ ResultUnit.MS);
}
}
diff --git a/suite/pts/hostTests/browser/browserlauncher/Android.mk b/suite/pts/deviceTests/videoperf/Android.mk
similarity index 75%
copy from suite/pts/hostTests/browser/browserlauncher/Android.mk
copy to suite/pts/deviceTests/videoperf/Android.mk
index 0592017..055c9f7 100644
--- a/suite/pts/hostTests/browser/browserlauncher/Android.mk
+++ b/suite/pts/deviceTests/videoperf/Android.mk
@@ -1,4 +1,4 @@
-# Copyright (C) 2012 The Android Open Source Project
+# Copyright (C) 2013 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -20,14 +20,13 @@
LOCAL_JAVA_LIBRARIES := android.test.runner
-LOCAL_STATIC_JAVA_LIBRARIES := ptsutil ctsutil ctstestrunner ctstestserver
+LOCAL_STATIC_JAVA_LIBRARIES := ptsutil ctsutil ctstestrunner
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_PACKAGE_NAME := PtsDeviceBrowserLauncher
+LOCAL_PACKAGE_NAME := PtsDeviceVideoPerf
LOCAL_SDK_VERSION := 16
-include $(BUILD_PTS_PACKAGE)
+include $(BUILD_CTS_PACKAGE)
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/suite/pts/deviceTests/videoperf/AndroidManifest.xml b/suite/pts/deviceTests/videoperf/AndroidManifest.xml
new file mode 100644
index 0000000..fdef1ef
--- /dev/null
+++ b/suite/pts/deviceTests/videoperf/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.pts.videoperf">
+
+ <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+ <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ android:targetPackage="com.android.pts.videoperf"
+ android:label="UI Latency measurement" />
+</manifest>
diff --git a/suite/pts/deviceTests/videoperf/src/com/android/pts/videoperf/CodecInfo.java b/suite/pts/deviceTests/videoperf/src/com/android/pts/videoperf/CodecInfo.java
new file mode 100644
index 0000000..a94395a
--- /dev/null
+++ b/suite/pts/deviceTests/videoperf/src/com/android/pts/videoperf/CodecInfo.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.pts.videoperf;
+
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaCodecInfo.CodecProfileLevel;
+import android.media.MediaCodecList;
+import android.util.Log;
+
+
+/**
+ * Utility class for getting codec information like bit rate, fps, and etc.
+ * Uses public member variables instead of methods as this code is only for video benchmarking.
+ */
+public class CodecInfo {
+ /** bit rate in bps */
+ public int mBitRate = 0;
+ /** Frame rate */
+ public int mFps = 0;
+ /** if codec is supporting YUV semiplanar format */
+ public boolean mSupportSemiPlanar = false;
+ /** if codec is supporting YUV planar format */
+ public boolean mSupportPlanar = false;
+
+ private static final String TAG = "CodecInfo";
+ private static final String VIDEO_AVC = "video/avc";
+ /**
+ * Check if given codec with given (w,h) is supported.
+ * @param mimeType codec type in mime format like "video/avc"
+ * @param w video width
+ * @param h video height
+ * @param isEncoder whether the codec is encoder or decoder
+ * @return null if the configuration is not supported.
+ */
+ public static CodecInfo getSupportedFormatInfo(String mimeType, int w, int h,
+ boolean isEncoder) {
+ CodecCapabilities cap = getCodecCapability(mimeType, isEncoder);
+ if (cap == null) { // not supported
+ return null;
+ }
+ CodecInfo info = new CodecInfo();
+ for (int color : cap.colorFormats) {
+ if (color == CodecCapabilities.COLOR_FormatYUV420SemiPlanar) {
+ info.mSupportSemiPlanar = true;
+ }
+ if (color == CodecCapabilities.COLOR_FormatYUV420Planar) {
+ info.mSupportPlanar = true;
+ }
+ }
+ printIntArray("supported colors", cap.colorFormats);
+ // either YUV420 planar or semiplanar should be supported
+ if (!info.mSupportPlanar && !info.mSupportSemiPlanar) {
+ Log.i(TAG, "no supported color format");
+ return null;
+ }
+
+ if (mimeType.equals(VIDEO_AVC)) {
+ int highestLevel = 0;
+ for (CodecProfileLevel lvl : cap.profileLevels) {
+ if (lvl.level > highestLevel) {
+ highestLevel = lvl.level;
+ }
+ }
+ Log.i(TAG, "Avc highest level " + Integer.toHexString(highestLevel));
+ int maxW = 0;
+ int maxH = 0;
+ int bitRate = 0;
+ double fps = 0; // frame rate for the max resolution
+ switch(highestLevel) {
+ // Do not support Level 1 to 2.
+ case CodecProfileLevel.AVCLevel1:
+ case CodecProfileLevel.AVCLevel11:
+ case CodecProfileLevel.AVCLevel12:
+ case CodecProfileLevel.AVCLevel13:
+ case CodecProfileLevel.AVCLevel1b:
+ case CodecProfileLevel.AVCLevel2:
+ return null;
+ case CodecProfileLevel.AVCLevel21:
+ maxW = 352;
+ maxH = 576;
+ bitRate = 4000000;
+ fps = 25;
+ break;
+ case CodecProfileLevel.AVCLevel22:
+ maxW = 720;
+ maxH = 480;
+ bitRate = 4000000;
+ fps = 15;
+ break;
+ case CodecProfileLevel.AVCLevel3:
+ maxW = 720;
+ maxH = 480;
+ bitRate = 10000000;
+ fps = 30;
+ break;
+ case CodecProfileLevel.AVCLevel31:
+ maxW = 1280;
+ maxH = 720;
+ bitRate = 14000000;
+ fps = 30;
+ break;
+ case CodecProfileLevel.AVCLevel32:
+ maxW = 1280;
+ maxH = 720;
+ bitRate = 20000000;
+ fps = 60;
+ break;
+ case CodecProfileLevel.AVCLevel4:
+ maxW = 1920;
+ maxH = 1080;
+ bitRate = 20000000;
+ fps = 30.1;
+ break;
+ case CodecProfileLevel.AVCLevel41:
+ maxW = 1920;
+ maxH = 1080;
+ bitRate = 50000000;
+ fps = 30.1;
+ break;
+ case CodecProfileLevel.AVCLevel42:
+ maxW = 2048;
+ maxH = 1080;
+ bitRate = 50000000;
+ fps = 60;
+ break;
+ case CodecProfileLevel.AVCLevel5:
+ maxW = 3672;
+ maxH = 1536;
+ bitRate = 135000000;
+ fps = 26.7;
+ break;
+ case CodecProfileLevel.AVCLevel51:
+ maxW = 4096;
+ maxH = 2304;
+ bitRate = 240000000;
+ fps = 26.7;
+ break;
+ default:
+ maxW = 4096;
+ maxH = 2304;
+ bitRate = 240000000;
+ fps = 26.7;
+ break;
+ }
+ if ((w > maxW) || (h > maxH)) {
+ Log.i(TAG, "Requested resolution (" + w + "," + h + ") exceeds (" +
+ maxW + "," + maxH + ")");
+ return null;
+ }
+ info.mFps = (int)(fps * maxW * maxH / (w * h));
+ info.mBitRate = bitRate;
+ Log.i(TAG, "AVC Level " + Integer.toHexString(highestLevel) + " bit rate " + bitRate +
+ " fps " + info.mFps);
+ }
+ return info;
+ }
+
+ /**
+ * Search for given codecName and returns CodecCapabilities if found
+ * @param codecName
+ * @param isEncoder true for encoder, false for decoder
+ * @return null if the codec is not supported
+ */
+ private static CodecCapabilities getCodecCapability(
+ String codecName, boolean isEncoder) {
+ int codecCount = MediaCodecList.getCodecCount();
+ for (int i = 0; i < codecCount; ++i) {
+ MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i);
+ String[] types = info.getSupportedTypes();
+ if (isEncoder != info.isEncoder()) {
+ continue;
+ }
+ for (int j = 0; j < types.length; ++j) {
+ if (types[j].compareTo(codecName) == 0) {
+ CodecCapabilities cap = info.getCapabilitiesForType(types[j]);
+ Log.i(TAG, "Use codec " + info.getName());
+ return cap;
+ }
+ }
+ }
+ return null;
+ }
+
+ // for debugging
+ private static void printIntArray(String msg, int[] data) {
+ StringBuilder builder = new StringBuilder();
+ builder.append(msg);
+ builder.append(":");
+ for (int e : data) {
+ builder.append(Integer.toHexString(e));
+ builder.append(",");
+ }
+ builder.deleteCharAt(builder.length() - 1);
+ Log.i(TAG, builder.toString());
+ }
+}
diff --git a/suite/pts/deviceTests/videoperf/src/com/android/pts/videoperf/VideoEncoderDecoderTest.java b/suite/pts/deviceTests/videoperf/src/com/android/pts/videoperf/VideoEncoderDecoderTest.java
new file mode 100644
index 0000000..0a03ff3
--- /dev/null
+++ b/suite/pts/deviceTests/videoperf/src/com/android/pts/videoperf/VideoEncoderDecoderTest.java
@@ -0,0 +1,607 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.pts.videoperf;
+
+import android.graphics.Point;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaCodecInfo.CodecProfileLevel;
+import android.media.MediaCodecList;
+import android.media.MediaFormat;
+import android.util.Log;
+
+import com.android.pts.util.PtsAndroidTestCase;
+import com.android.pts.util.ResultType;
+import com.android.pts.util.ResultUnit;
+import com.android.pts.util.Stat;
+
+import java.nio.ByteBuffer;
+import java.lang.System;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ * This tries to test video encoder / decoder performance by running encoding / decoding
+ * without displaying the raw data. To make things simpler, encoder is used to encode synthetic
+ * data and decoder is used to decode the encoded video. This approach does not work where
+ * there is only decoder. Performance index is total time taken for encoding and decoding
+ * the whole frames.
+ * To prevent sacrificing quality for faster encoding / decoding, randomly selected pixels are
+ * compared with the original image. As the pixel comparison can slow down the decoding process,
+ * only some randomly selected pixels are compared. As there can be only one performance index,
+ * error above certain threshold in pixel value will be treated as an error.
+ */
+public class VideoEncoderDecoderTest extends PtsAndroidTestCase {
+ private static final String TAG = "VideoEncoderDecoderTest";
+ // this wait time affects fps as too big value will work as a blocker if device fps
+ // is not very high.
+ private static final long VIDEO_CODEC_WAIT_TIME_US = 5000;
+ private static final boolean VERBOSE = false;
+ private static final String VIDEO_AVC = "video/avc";
+ private static final int TOTAL_FRAMES = 300;
+ private static final int NUMBER_OF_REPEAT = 10;
+ // i frame interval for encoder
+ private static final int KEY_I_FRAME_INTERVAL = 5;
+
+ private static final int Y_CLAMP_MIN = 16;
+ private static final int Y_CLAMP_MAX = 235;
+ private static final int YUV_PLANE_ADDITIONAL_LENGTH = 200;
+ private ByteBuffer mYBuffer;
+ private ByteBuffer mUVBuffer;
+ // if input raw data is semi-planar
+ private boolean mSrcSemiPlanar;
+ // if output raw data is semi-planar
+ private boolean mDstSemiPlanar;
+ private int mBufferWidth;
+ private int mBufferHeight;
+ private int mVideoWidth;
+ private int mVideoHeight;
+
+ private Vector<ByteBuffer> mEncodedOutputBuffer;
+ // check this many pixels per each decoded frame
+ // checking too many points decreases decoder frame rates a lot.
+ private static final int PIXEL_CHECK_PER_FRAME = 1000;
+ // RMS error in pixel values above this will be treated as error.
+ private static final double PIXEL_RMS_ERROR_MARGAIN = 20.0;
+ private Random mRandom;
+
+ @Override
+ protected void setUp() throws Exception {
+ mEncodedOutputBuffer = new Vector<ByteBuffer>(TOTAL_FRAMES * 2);
+ // Use time as a seed, hoping to prevent checking pixels in the same pattern
+ long now = System.currentTimeMillis();
+ mRandom = new Random(now);
+ super.setUp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ mEncodedOutputBuffer.clear();
+ mEncodedOutputBuffer = null;
+ mYBuffer = null;
+ mUVBuffer = null;
+ mRandom = null;
+ super.tearDown();
+ }
+
+ public void testAvc0176x0144() throws Exception {
+ doTest(VIDEO_AVC, 176, 144, NUMBER_OF_REPEAT);
+ }
+
+ public void testAvc0352x0288() throws Exception {
+ doTest(VIDEO_AVC, 352, 288, NUMBER_OF_REPEAT);
+ }
+
+ public void testAvc0720x0480() throws Exception {
+ doTest(VIDEO_AVC, 720, 480, NUMBER_OF_REPEAT);
+ }
+
+ public void testAvc1280x0720() throws Exception {
+ doTest(VIDEO_AVC, 1280, 720, NUMBER_OF_REPEAT);
+ }
+
+ /**
+ * resolution intentionally set to 1072 not 1080
+ * as 1080 is not multiple of 16, and it requires additional setting like stride
+ * which is not specified in API documentation.
+ */
+ public void testAvc1920x1072() throws Exception {
+ doTest(VIDEO_AVC, 1920, 1072, NUMBER_OF_REPEAT);
+ }
+
+ /**
+ * Run encoding / decoding test for given mimeType of codec
+ * @param mimeType like video/avc
+ * @param w video width
+ * @param h video height
+ * @param numberRepeat how many times to repeat the encoding / decoding process
+ */
+ private void doTest(String mimeType, int w, int h, int numberRepeat) throws Exception {
+ CodecInfo infoEnc = CodecInfo.getSupportedFormatInfo(mimeType, w, h, true);
+ if (infoEnc == null) {
+ Log.i(TAG, "Codec " + mimeType + "with " + w + "," + h + " not supported");
+ return;
+ }
+ CodecInfo infoDec = CodecInfo.getSupportedFormatInfo(mimeType, w, h, false);
+ assertNotNull(infoDec);
+ mVideoWidth = w;
+ mVideoHeight = h;
+ initYUVPlane(w + YUV_PLANE_ADDITIONAL_LENGTH, h + YUV_PLANE_ADDITIONAL_LENGTH,
+ infoEnc.mSupportSemiPlanar, infoDec.mSupportSemiPlanar);
+ double[] encoderFpsResults = new double[numberRepeat];
+ double[] decoderFpsResults = new double[numberRepeat];
+ double[] totalFpsResults = new double[numberRepeat];
+ double[] decoderRmsErrorResults = new double[numberRepeat];
+ for (int i = 0; i < numberRepeat; i++) {
+ MediaFormat format = new MediaFormat();
+ format.setString(MediaFormat.KEY_MIME, mimeType);
+ format.setInteger(MediaFormat.KEY_BIT_RATE, infoEnc.mBitRate);
+ format.setInteger(MediaFormat.KEY_WIDTH, w);
+ format.setInteger(MediaFormat.KEY_HEIGHT, h);
+ format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+ infoEnc.mSupportSemiPlanar ? CodecCapabilities.COLOR_FormatYUV420SemiPlanar :
+ CodecCapabilities.COLOR_FormatYUV420Planar);
+ format.setInteger(MediaFormat.KEY_FRAME_RATE, infoEnc.mFps);
+ format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, KEY_I_FRAME_INTERVAL);
+ double encodingTime = runEncoder(VIDEO_AVC, format, TOTAL_FRAMES);
+ // re-initialize format for decoder
+ format = new MediaFormat();
+ format.setString(MediaFormat.KEY_MIME, mimeType);
+ format.setInteger(MediaFormat.KEY_WIDTH, w);
+ format.setInteger(MediaFormat.KEY_HEIGHT, h);
+ format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+ infoDec.mSupportSemiPlanar ? CodecCapabilities.COLOR_FormatYUV420SemiPlanar :
+ CodecCapabilities.COLOR_FormatYUV420Planar);
+ double[] decoderResult = runDecoder(VIDEO_AVC, format);
+ if (decoderResult == null) {
+ // color change in the middle. give up
+ return;
+ }
+ double decodingTime = decoderResult[0];
+ decoderRmsErrorResults[i] = decoderResult[1];
+ encoderFpsResults[i] = (double)TOTAL_FRAMES / encodingTime * 1000.0;
+ decoderFpsResults[i] = (double)TOTAL_FRAMES / decodingTime * 1000.0;
+ totalFpsResults[i] = (double)TOTAL_FRAMES / (encodingTime + decodingTime) * 1000.0;
+
+ // clear things for re-start
+ mEncodedOutputBuffer.clear();
+ // it will be good to clean everything to make every run the same.
+ System.gc();
+ }
+ getReportLog().printArray("encoder", encoderFpsResults, ResultType.HIGHER_BETTER,
+ ResultUnit.FPS);
+ getReportLog().printArray("rms error", decoderRmsErrorResults, ResultType.LOWER_BETTER,
+ ResultUnit.NONE);
+ getReportLog().printArray("decoder", decoderFpsResults, ResultType.HIGHER_BETTER,
+ ResultUnit.FPS);
+ getReportLog().printArray("encoder decoder", totalFpsResults, ResultType.HIGHER_BETTER,
+ ResultUnit.FPS);
+ getReportLog().printSummary("encoder decoder", Stat.getAverage(totalFpsResults),
+ ResultType.HIGHER_BETTER, ResultUnit.FPS);
+ // make sure that rms error is not too big.
+ for (int i = 0; i < numberRepeat; i++) {
+ assertTrue(decoderRmsErrorResults[i] < PIXEL_RMS_ERROR_MARGAIN);
+ }
+ }
+
+ /**
+ * run encoder benchmarking
+ * @param mimeType encoder type like video/avc
+ * @param format format of media to encode
+ * @param totalFrames total number of frames to encode
+ * @return time taken in ms to encode the frames. This does not include initialization time.
+ */
+ private double runEncoder(String mimeType, MediaFormat format, int totalFrames) {
+ MediaCodec codec = MediaCodec.createEncoderByType(mimeType);
+ try {
+ codec.configure(
+ format,
+ null /* surface */,
+ null /* crypto */,
+ MediaCodec.CONFIGURE_FLAG_ENCODE);
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "codec '" + mimeType + "' failed configuration.");
+ assertTrue("codec '" + mimeType + "' failed configuration.", false);
+ }
+ codec.start();
+ ByteBuffer[] codecInputBuffers = codec.getInputBuffers();
+ ByteBuffer[] codecOutputBuffers = codec.getOutputBuffers();
+
+ int numBytesSubmitted = 0;
+ int numBytesDequeued = 0;
+ int inFramesCount = 0;
+ long start = System.currentTimeMillis();
+ while (true) {
+ int index;
+
+ if (inFramesCount < totalFrames) {
+ index = codec.dequeueInputBuffer(VIDEO_CODEC_WAIT_TIME_US /* timeoutUs */);
+ if (index != MediaCodec.INFO_TRY_AGAIN_LATER) {
+ int size = queueInputBufferEncoder(
+ codec, codecInputBuffers, index, inFramesCount,
+ (inFramesCount == (totalFrames - 1)) ?
+ MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
+ inFramesCount++;
+ numBytesSubmitted += size;
+ if (VERBOSE) {
+ Log.d(TAG, "queued " + size + " bytes of input data, frame " +
+ (inFramesCount - 1));
+ }
+
+ }
+ }
+ MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+ index = codec.dequeueOutputBuffer(info, VIDEO_CODEC_WAIT_TIME_US /* timeoutUs */);
+ if (index == MediaCodec.INFO_TRY_AGAIN_LATER) {
+ } else if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+ } else if (index == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+ codecOutputBuffers = codec.getOutputBuffers();
+ } else if (index >= 0) {
+ dequeueOutputBufferEncoder(codec, codecOutputBuffers, index, info);
+ numBytesDequeued += info.size;
+ if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+ if (VERBOSE) {
+ Log.d(TAG, "dequeued output EOS.");
+ }
+ break;
+ }
+ if (VERBOSE) {
+ Log.d(TAG, "dequeued " + info.size + " bytes of output data.");
+ }
+ }
+ }
+ long finish = System.currentTimeMillis();
+ if (VERBOSE) {
+ Log.d(TAG, "queued a total of " + numBytesSubmitted + "bytes, "
+ + "dequeued " + numBytesDequeued + " bytes.");
+ }
+ codec.stop();
+ codec.release();
+ codec = null;
+ return (double)(finish - start);
+ }
+
+ /**
+ * Fills input buffer for encoder from YUV buffers.
+ * @return size of enqueued data.
+ */
+ private int queueInputBufferEncoder(
+ MediaCodec codec, ByteBuffer[] inputBuffers, int index, int frameCount, int flags) {
+ ByteBuffer buffer = inputBuffers[index];
+ buffer.clear();
+
+ Point origin = getOrigin(frameCount);
+ // Y color first
+ int srcOffsetY = origin.x + origin.y * mBufferWidth;
+ final byte[] yBuffer = mYBuffer.array();
+ for (int i = 0; i < mVideoHeight; i++) {
+ buffer.put(yBuffer, srcOffsetY, mVideoWidth);
+ srcOffsetY += mBufferWidth;
+ }
+ if (mSrcSemiPlanar) {
+ int srcOffsetU = origin.y / 2 * mBufferWidth + origin.x / 2 * 2;
+ final byte[] uvBuffer = mUVBuffer.array();
+ for (int i = 0; i < mVideoHeight / 2; i++) {
+ buffer.put(uvBuffer, srcOffsetU, mVideoWidth);
+ srcOffsetU += mBufferWidth;
+ }
+ } else {
+ int srcOffsetU = origin.y / 2 * mBufferWidth / 2 + origin.x / 2;
+ int srcOffsetV = srcOffsetU + mBufferWidth / 2 * mBufferHeight / 2;
+ final byte[] uvBuffer = mUVBuffer.array();
+ for (int i = 0; i < mVideoHeight /2; i++) { //U only
+ buffer.put(uvBuffer, srcOffsetU, mVideoWidth / 2);
+ srcOffsetU += mBufferWidth / 2;
+ }
+ for (int i = 0; i < mVideoHeight /2; i++) { //V only
+ buffer.put(uvBuffer, srcOffsetV, mVideoWidth / 2);
+ srcOffsetV += mBufferWidth / 2;
+ }
+ }
+ int size = mVideoHeight * mVideoWidth * 3 /2;
+
+ codec.queueInputBuffer(index, 0 /* offset */, size, 0 /* timeUs */, flags);
+ if (VERBOSE && (frameCount == 0)) {
+ printByteArray("Y ", mYBuffer.array(), 0, 20);
+ printByteArray("UV ", mUVBuffer.array(), 0, 20);
+ printByteArray("UV ", mUVBuffer.array(), mBufferWidth * 60, 20);
+ }
+ return size;
+ }
+
+ /**
+ * Dequeue encoded data from output buffer and store for later usage.
+ */
+ private void dequeueOutputBufferEncoder(
+ MediaCodec codec, ByteBuffer[] outputBuffers,
+ int index, MediaCodec.BufferInfo info) {
+ ByteBuffer output = outputBuffers[index];
+ output.clear();
+ int l = info.size;
+ ByteBuffer copied = ByteBuffer.allocate(l);
+ output.get(copied.array(), 0, l);
+ mEncodedOutputBuffer.add(copied);
+ codec.releaseOutputBuffer(index, false /* render */);
+ }
+
+ /**
+ * run encoder benchmarking with encoded stream stored from encoding phase
+ * @param mimeType encoder type like video/avc
+ * @param format format of media to decode
+ * @return returns length-2 array with 0: time for decoding, 1 : rms error of pixels
+ */
+ private double[] runDecoder(String mimeType, MediaFormat format) {
+ MediaCodec codec = MediaCodec.createDecoderByType(mimeType);
+ codec.configure(format, null /* surface */, null /* crypto */, 0 /* flags */);
+ codec.start();
+ ByteBuffer[] codecInputBuffers = codec.getInputBuffers();
+ ByteBuffer[] codecOutputBuffers = codec.getOutputBuffers();
+
+ double totalErrorSquared = 0;
+
+ MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+ boolean sawOutputEOS = false;
+ int inputLeft = mEncodedOutputBuffer.size();
+ int inputBufferCount = 0;
+ int outFrameCount = 0;
+ YUVValue expected = new YUVValue();
+ YUVValue decoded = new YUVValue();
+ long start = System.currentTimeMillis();
+ while (!sawOutputEOS) {
+ if (inputLeft > 0) {
+ int inputBufIndex = codec.dequeueInputBuffer(VIDEO_CODEC_WAIT_TIME_US);
+
+ if (inputBufIndex >= 0) {
+ ByteBuffer dstBuf = codecInputBuffers[inputBufIndex];
+ dstBuf.clear();
+ ByteBuffer src = mEncodedOutputBuffer.get(inputBufferCount);
+ int writeSize = src.capacity();
+ dstBuf.put(src.array(), 0, writeSize);
+ codec.queueInputBuffer(
+ inputBufIndex,
+ 0 /* offset */,
+ writeSize,
+ 0,
+ (inputLeft == 1) ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
+ inputLeft --;
+ inputBufferCount ++;
+ }
+ }
+
+ int res = codec.dequeueOutputBuffer(info, VIDEO_CODEC_WAIT_TIME_US);
+ if (res >= 0) {
+ int outputBufIndex = res;
+ ByteBuffer buf = codecOutputBuffers[outputBufIndex];
+ if (VERBOSE && (outFrameCount == 0)) {
+ printByteBuffer("Y ", buf, 0, 20);
+ printByteBuffer("UV ", buf, mVideoWidth * mVideoHeight, 20);
+ printByteBuffer("UV ", buf, mVideoWidth * mVideoHeight + mVideoWidth * 60, 20);
+ }
+ Point origin = getOrigin(outFrameCount);
+ for (int i = 0; i < PIXEL_CHECK_PER_FRAME; i++) {
+ int w = mRandom.nextInt(mVideoWidth);
+ int h = mRandom.nextInt(mVideoHeight);
+ getPixelValuesFromYUVBuffers(origin.x, origin.y, w, h, expected);
+ getPixelValuesFromOutputBuffer(buf, w, h, decoded);
+ if (VERBOSE) {
+ Log.i(TAG, outFrameCount + "-" + i + "- th round expcted " + expected.mY +
+ "," + expected.mU + "," + expected.mV + " decoded " + decoded.mY +
+ "," + decoded.mU + "," + decoded.mV);
+ }
+ totalErrorSquared += expected.calcErrorSquared(decoded);
+ }
+ codec.releaseOutputBuffer(outputBufIndex, false /* render */);
+ if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+ Log.d(TAG, "saw output EOS.");
+ sawOutputEOS = true;
+ }
+ outFrameCount++;
+ } else if (res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+ codecOutputBuffers = codec.getOutputBuffers();
+ Log.d(TAG, "output buffers have changed.");
+ } else if (res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+ MediaFormat oformat = codec.getOutputFormat();
+ Log.d(TAG, "output format has changed to " + oformat);
+ int colorFormat = oformat.getInteger(MediaFormat.KEY_COLOR_FORMAT);
+ if (colorFormat == CodecCapabilities.COLOR_FormatYUV420SemiPlanar ) {
+ mDstSemiPlanar = true;
+ } else if (colorFormat == CodecCapabilities.COLOR_FormatYUV420Planar ) {
+ mDstSemiPlanar = false;
+ } else {
+ Log.w(TAG, "output format changed to unsupported one " +
+ Integer.toHexString(colorFormat));
+ // give up and return as nothing can be done
+ codec.release();
+ return null;
+ }
+ }
+ }
+ long finish = System.currentTimeMillis();
+ codec.stop();
+ codec.release();
+ codec = null;
+ assertTrue(outFrameCount >= TOTAL_FRAMES);
+ // divide by 3 as sum is done for Y, U, V.
+ double errorRms = Math.sqrt(totalErrorSquared / PIXEL_CHECK_PER_FRAME / outFrameCount / 3);
+ double[] result = { (double) finish - start, errorRms };
+ return result;
+ }
+
+ /**
+ * returns origin in the absolute frame for given frame count.
+ * The video scene is moving by moving origin per each frame.
+ */
+ private Point getOrigin(int frameCount) {
+ if (frameCount < 100) {
+ return new Point(2 * frameCount, 0);
+ } else if (frameCount < 200) {
+ return new Point(200, (frameCount - 100) * 2);
+ } else {
+ if (frameCount > 300) { // for safety
+ frameCount = 300;
+ }
+ return new Point(600 - frameCount * 2, 600 - frameCount * 2);
+ }
+ }
+
+ /**
+ * initialize reference YUV plane
+ * @param w This should be YUV_PLANE_ADDITIONAL_LENGTH pixels bigger than video resolution
+ * to allow movements
+ * @param h This should be YUV_PLANE_ADDITIONAL_LENGTH pixels bigger than video resolution
+ * to allow movements
+ * @param semiPlanarEnc
+ * @param semiPlanarDec
+ */
+ private void initYUVPlane(int w, int h, boolean semiPlanarEnc, boolean semiPlanarDec) {
+ int bufferSizeY = w * h;
+ mYBuffer = ByteBuffer.allocate(bufferSizeY);
+ mUVBuffer = ByteBuffer.allocate(bufferSizeY / 2);
+ mSrcSemiPlanar = semiPlanarEnc;
+ mDstSemiPlanar = semiPlanarDec;
+ mBufferWidth = w;
+ mBufferHeight = h;
+ final byte[] yArray = mYBuffer.array();
+ final byte[] uvArray = mUVBuffer.array();
+ for (int i = 0; i < h; i++) {
+ for (int j = 0; j < w; j++) {
+ yArray[i * w + j] = clampY((i + j) & 0xff);
+ }
+ }
+ if (semiPlanarEnc) {
+ for (int i = 0; i < h/2; i++) {
+ for (int j = 0; j < w/2; j++) {
+ uvArray[i * w + 2 * j] = (byte) (i & 0xff);
+ uvArray[i * w + 2 * j + 1] = (byte) (j & 0xff);
+ }
+ }
+ } else { // planar, U first, then V
+ int vOffset = bufferSizeY / 4;
+ for (int i = 0; i < h/2; i++) {
+ for (int j = 0; j < w/2; j++) {
+ uvArray[i * w/2 + j] = (byte) (i & 0xff);
+ uvArray[i * w/2 + vOffset + j] = (byte) (j & 0xff);
+ }
+ }
+ }
+ }
+
+ /**
+ * class to store pixel values in YUV
+ *
+ */
+ public class YUVValue {
+ public byte mY;
+ public byte mU;
+ public byte mV;
+ public YUVValue() {
+ }
+
+ public boolean equalTo(YUVValue other) {
+ return (mY == other.mY) && (mU == other.mU) && (mV == other.mV);
+ }
+
+ public double calcErrorSquared(YUVValue other) {
+ double yDelta = mY - other.mY;
+ double uDelta = mU - other.mU;
+ double vDelta = mV - other.mV;
+ return yDelta * yDelta + uDelta * uDelta + vDelta * vDelta;
+ }
+ }
+
+ /**
+ * Read YUV values from given position (x,y) for given origin (originX, originY)
+ * The whole data is already available from YBuffer and UVBuffer.
+ * @param result pass the result via this. This is for avoiding creating / destroying too many
+ * instances
+ */
+ private void getPixelValuesFromYUVBuffers(int originX, int originY, int x, int y,
+ YUVValue result) {
+ result.mY = mYBuffer.get((originY + y) * mBufferWidth + (originX + x));
+ if (mSrcSemiPlanar) {
+ int index = (originY + y) / 2 * mBufferWidth + (originX + x) / 2 * 2;
+ //Log.d(TAG, "YUV " + originX + "," + originY + "," + x + "," + y + "," + index);
+ result.mU = mUVBuffer.get(index);
+ result.mV = mUVBuffer.get(index + 1);
+ } else {
+ int vOffset = mBufferWidth * mBufferHeight / 4;
+ int index = (originY + y) / 2 * mBufferWidth / 2 + (originX + x) / 2;
+ result.mU = mUVBuffer.get(index);
+ result.mV = mUVBuffer.get(vOffset + index);
+ }
+ }
+
+ /**
+ * Read YUV pixels from decoded output buffer for give (x, y) position
+ * Output buffer is composed of Y parts followed by U/V
+ * @param result pass the result via this. This is for avoiding creating / destroying too many
+ * instances
+ */
+ private void getPixelValuesFromOutputBuffer(ByteBuffer buffer, int x, int y, YUVValue result) {
+ result.mY = buffer.get(y * mVideoWidth + x);
+ if (mDstSemiPlanar) {
+ int index = mVideoWidth * mVideoHeight + y / 2 * mVideoWidth + x / 2 * 2;
+ //Log.d(TAG, "Decoded " + x + "," + y + "," + index);
+ result.mU = buffer.get(index);
+ result.mV = buffer.get(index + 1);
+ } else {
+ int vOffset = mVideoWidth * mVideoHeight / 4;
+ int index = mVideoWidth * mVideoHeight + y / 2 * mVideoWidth / 2 + x / 2;
+ result.mU = buffer.get(index);
+ result.mV = buffer.get(index + vOffset);
+ }
+ }
+
+ /**
+ * Y cannot have full range. clamp it to prevent invalid value.
+ */
+ private byte clampY(int y) {
+ if (y < Y_CLAMP_MIN) {
+ y = Y_CLAMP_MIN;
+ } else if (y > Y_CLAMP_MAX) {
+ y = Y_CLAMP_MAX;
+ }
+ return (byte) (y & 0xff);
+ }
+
+ // for debugging
+ private void printByteArray(String msg, byte[] data, int offset, int len) {
+ StringBuilder builder = new StringBuilder();
+ builder.append(msg);
+ builder.append(":");
+ for (int i = offset; i < offset + len; i++) {
+ builder.append(Integer.toHexString(data[i]));
+ builder.append(",");
+ }
+ builder.deleteCharAt(builder.length() - 1);
+ Log.i(TAG, builder.toString());
+ }
+
+ // for debugging
+ private void printByteBuffer(String msg, ByteBuffer data, int offset, int len) {
+ StringBuilder builder = new StringBuilder();
+ builder.append(msg);
+ builder.append(":");
+ for (int i = offset; i < offset + len; i++) {
+ builder.append(Integer.toHexString(data.get(i)));
+ builder.append(",");
+ }
+ builder.deleteCharAt(builder.length() - 1);
+ Log.i(TAG, builder.toString());
+ }
+}
diff --git a/suite/pts/expectations/knownfailures.txt b/suite/pts/expectations/knownfailures.txt
deleted file mode 100644
index 0d4f101..0000000
--- a/suite/pts/expectations/knownfailures.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-[
-]
diff --git a/suite/pts/hostTests/bootup/Android.mk b/suite/pts/hostTests/bootup/Android.mk
index 4d779a0..1872284 100644
--- a/suite/pts/hostTests/bootup/Android.mk
+++ b/suite/pts/hostTests/bootup/Android.mk
@@ -24,7 +24,7 @@
LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed-prebuilt ddmlib-prebuilt junit ptscommonutilhost
-LOCAL_PTS_TEST_PACKAGE := com.android.pts.bootup
+LOCAL_CTS_TEST_PACKAGE := com.android.pts.bootup
-include $(BUILD_PTS_HOST_JAVA_LIBRARY)
+include $(BUILD_CTS_HOST_JAVA_LIBRARY)
diff --git a/suite/pts/hostTests/bootup/src/com/android/pts/bootup/BootupTimeTest.java b/suite/pts/hostTests/bootup/src/com/android/pts/bootup/BootupTimeTest.java
index e78289b..dd7b834 100644
--- a/suite/pts/hostTests/bootup/src/com/android/pts/bootup/BootupTimeTest.java
+++ b/suite/pts/hostTests/bootup/src/com/android/pts/bootup/BootupTimeTest.java
@@ -19,6 +19,8 @@
import android.cts.util.TimeoutReq;
import com.android.pts.util.MeasureRun;
import com.android.pts.util.MeasureTime;
+import com.android.pts.util.ResultType;
+import com.android.pts.util.ResultUnit;
import com.android.pts.util.ReportLog;
import com.android.pts.util.Stat;
import com.android.pts.util.Stat.StatResult;
@@ -65,9 +67,11 @@
rebootDevice();
}
});
- mReport.printArray("time in ms", result, false);
+ mReport.printArray("bootup time", result, ResultType.LOWER_BETTER,
+ ResultUnit.MS);
StatResult stat = Stat.getStat(result);
- mReport.printSummary("time in ms", stat.mAverage, stat.mStddev);
+ mReport.printSummary("bootup time", stat.mAverage, ResultType.LOWER_BETTER,
+ ResultUnit.MS);
}
private void rebootDevice() throws DeviceNotAvailableException {
diff --git a/suite/pts/hostTests/browser/Android.mk b/suite/pts/hostTests/browser/Android.mk
deleted file mode 100644
index 9e45477..0000000
--- a/suite/pts/hostTests/browser/Android.mk
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright (C) 2012 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_MODULE := PtsHostBrowser
-
-LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed-prebuilt ddmlib-prebuilt junit ptscommonutilhost ptshostutil
-
-LOCAL_PTS_TEST_PACKAGE := com.android.pts.browser
-
-include $(BUILD_PTS_HOST_JAVA_LIBRARY)
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/suite/pts/hostTests/browser/browserlauncher/src/com/android/pts/browser/LaunchBrowserTest.java b/suite/pts/hostTests/browser/browserlauncher/src/com/android/pts/browser/LaunchBrowserTest.java
deleted file mode 100644
index a9e48f3..0000000
--- a/suite/pts/hostTests/browser/browserlauncher/src/com/android/pts/browser/LaunchBrowserTest.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.pts.browser;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.Uri;
-import android.webkit.cts.CtsTestServer;
-import com.android.pts.util.PtsAndroidTestCase;
-
-import java.util.concurrent.Semaphore;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Device side code to run browser benchmarking.
- * It launches an activity with URL and wait for broadcast sent from host.
- * It is host's responsibility to send broadcast after parsing browser's result.
- */
-public class LaunchBrowserTest extends PtsAndroidTestCase {
-
- private static final String OCTANE_START_FILE = "octane/index.html";
- private static final String HOST_COMPLETION_BROADCAST = "com.android.pts.browser.completion";
- private static long BROWSER_COMPLETION_TIMEOUT = 10 * 60;
- private CtsTestServer mWebServer;
- private HostBroadcastReceiver mReceiver;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mWebServer = new CtsTestServer(getContext());
- mReceiver = new HostBroadcastReceiver();
- mReceiver.register(getContext(), HOST_COMPLETION_BROADCAST);
- }
-
- @Override
- protected void tearDown() throws Exception {
- mReceiver.unregister(getContext());
- mWebServer.shutdown();
- mWebServer = null;
- super.tearDown();
- }
-
- public void testOctane() throws InterruptedException {
- String url = mWebServer.getAssetUrl(OCTANE_START_FILE) + "?auto=1";
- Uri uri = Uri.parse(url);
- Intent intent = new Intent(Intent.ACTION_VIEW, uri);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- getContext().startActivity(intent);
- mReceiver.waitForBroadcast(BROWSER_COMPLETION_TIMEOUT);
- }
-
- class HostBroadcastReceiver extends BroadcastReceiver {
- private final Semaphore mSemaphore = new Semaphore(0);
-
- public void register(Context context, String actionName) {
- IntentFilter filter = new IntentFilter();
- filter.addAction(actionName);
- context.registerReceiver(this, filter);
- }
-
- public void unregister(Context context) {
- context.unregisterReceiver(this);
- }
-
- public boolean waitForBroadcast(long timeoutInSec) throws InterruptedException {
- return mSemaphore.tryAcquire(timeoutInSec, TimeUnit.SECONDS);
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- mSemaphore.release();
- }
- }
-}
diff --git a/suite/pts/hostTests/browser/src/com/android/pts/browser/BrowserTest.java b/suite/pts/hostTests/browser/src/com/android/pts/browser/BrowserTest.java
deleted file mode 100644
index b5400ab..0000000
--- a/suite/pts/hostTests/browser/src/com/android/pts/browser/BrowserTest.java
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.pts.browser;
-
-import android.cts.util.TimeoutReq;
-
-import com.android.cts.tradefed.build.CtsBuildHelper;
-import com.android.ddmlib.Log;
-import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
-import com.android.pts.ptsutil.LogcatLineReceiver;
-import com.android.pts.util.ReportLog;
-import com.android.pts.util.Stat;
-import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.result.CollectingTestListener;
-import com.android.tradefed.result.TestRunResult;
-import com.android.tradefed.testtype.DeviceTestCase;
-import com.android.tradefed.testtype.IBuildReceiver;
-
-import java.io.File;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Run browser benchmarking.
- * Benchmarking result is printed via logcat (JavaScript console.log method).
- * Corresponding device side test should be running to make browser benchmarking working,
- * And the device test expects HOST_COMPLETION_BROADCAST broadcast from host as device
- * test cannot detect the completion from browser side.
- */
-public class BrowserTest extends DeviceTestCase implements IBuildReceiver {
- private static final String BROADCAST_CMD = "am broadcast -a com.android.pts.browser.completion";
- private static final String TAG = "BrowserTest";
- private static final String CTS_RUNNER = "android.test.InstrumentationCtsTestRunner";
- private static final String PACKAGE = "com.android.pts.browser";
- private static final String APK = "PtsDeviceBrowserLauncher.apk";
- private static final long LOGCAT_TIMEOUT_IN_SEC = 10 * 60L;
- private static final String LOGCAT_FILTER = " browser:D chromium:D *:S";
- private static final long REBOOT_WAIT_TIME_IN_MS = 2 * 60 * 1000L;
-
- private CtsBuildHelper mBuild;
- private ITestDevice mDevice;
- private LogcatLineReceiver mReceiver;
- private ReportLog mReport;
-
- private volatile boolean mIgnoreLine = true;
- private double mResult;
-
- @Override
- public void setBuild(IBuildInfo buildInfo) {
- mBuild = CtsBuildHelper.createBuildHelper(buildInfo);
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mReport = new ReportLog();
- mDevice = getDevice();
- mDevice.uninstallPackage(PACKAGE);
- File app = mBuild.getTestApp(APK);
- mDevice.installPackage(app, false);
- }
-
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
- mDevice.uninstallPackage(PACKAGE);
- mReport.throwReportToHost();
- }
-
- @TimeoutReq(minutes = 40)
- public void testOctane() throws Exception {
- String resultPattern = "([A-Z][\\d\\w]+): ([\\d]+)";
- String summaryPattern = "(Octane Score.*): ([\\d]+)";
- int numberRepeat = 5;
- double[] results = new double[numberRepeat];
- for (int i = 0; i < numberRepeat; i++) {
- Log.i(TAG, i + "-th round");
- // browser will not refresh if the page is already loaded.
- mDevice.reboot();
- Thread.sleep(REBOOT_WAIT_TIME_IN_MS);
- results[i] = runBenchmarking("testOctane", resultPattern, summaryPattern);
- }
- mReport.printArray("scores", results, true);
- Stat.StatResult stat = Stat.getStat(results);
- mReport.printSummary("Score", stat.mAverage, stat.mStddev);
- }
-
- private double runBenchmarking(String testMethodName, String resultPattern,
- String summaryPattern) throws DeviceNotAvailableException, InterruptedException {
- RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(PACKAGE, CTS_RUNNER,
- mDevice.getIDevice());
- testRunner.setMethodName("com.android.pts.browser.LaunchBrowserTest", testMethodName);
- CollectingTestListener listener = new CollectingTestListener();
- mIgnoreLine = true;
- startLogMonitoring(resultPattern, summaryPattern);
- // hack to ignore already captured logcat as the monitor will get it again.
- // Checking time and skipping may be a better logic, but simply throwing away also works.
- Thread.sleep(5000);
- mIgnoreLine = false;
- Log.i(TAG, "start to run test in device");
- mDevice.runInstrumentationTests(testRunner, listener);
- TestRunResult result = listener.getCurrentRunResults();
- if (result.isRunFailure()) {
- fail(result.getRunFailureMessage());
- }
- if (result.getNumPassedTests() == 0) {
- fail("maybe timeout");
- }
- stopLogMonitoring();
- return mResult;
- }
-
- void startLogMonitoring(String resultPattern, String summaryPattern)
- throws InterruptedException, DeviceNotAvailableException {
- final Pattern result = Pattern.compile(resultPattern);
- final Pattern summary = Pattern.compile(summaryPattern);
- mReceiver = new LogcatLineReceiver(mDevice, LOGCAT_FILTER, LOGCAT_TIMEOUT_IN_SEC) {
- @Override
- public void processALine(String line) throws DeviceNotAvailableException {
- Log.i(TAG, "processALine " + line + " ignore " + mIgnoreLine);
- if (mIgnoreLine) {
- return;
- }
- Matcher matchResult = result.matcher(line);
- if (matchResult.find()) {
- mReport.printValue(matchResult.group(1),
- Double.parseDouble(matchResult.group(2)));
- }
- Matcher matchSummary = summary.matcher(line);
- if (matchSummary.find()) {
- mResult = Double.parseDouble(matchSummary.group(2));
- mReport.printValue(matchSummary.group(1), mResult);
- mDevice.executeShellCommand(BROADCAST_CMD);
- }
- }
- };
- mReceiver.start();
- }
-
- void stopLogMonitoring() {
- mReceiver.stop();
- }
-}
diff --git a/suite/pts/hostTests/uihost/Android.mk b/suite/pts/hostTests/uihost/Android.mk
index 77673a2..65742e2 100644
--- a/suite/pts/hostTests/uihost/Android.mk
+++ b/suite/pts/hostTests/uihost/Android.mk
@@ -24,8 +24,8 @@
LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed-prebuilt ddmlib-prebuilt junit ptscommonutilhost
-LOCAL_PTS_TEST_PACKAGE := com.android.pts.uihost
+LOCAL_CTS_TEST_PACKAGE := com.android.pts.uihost
-include $(BUILD_PTS_HOST_JAVA_LIBRARY)
+include $(BUILD_CTS_HOST_JAVA_LIBRARY)
include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/suite/pts/hostTests/uihost/appA/Android.mk b/suite/pts/hostTests/uihost/appA/Android.mk
index 7dce44c..4d31cb4 100644
--- a/suite/pts/hostTests/uihost/appA/Android.mk
+++ b/suite/pts/hostTests/uihost/appA/Android.mk
@@ -30,6 +30,6 @@
LOCAL_SDK_VERSION := 16
-include $(BUILD_PTS_PACKAGE)
+include $(BUILD_CTS_PACKAGE)
diff --git a/suite/pts/hostTests/uihost/appB/Android.mk b/suite/pts/hostTests/uihost/appB/Android.mk
index caa1090..bb1b5d7 100644
--- a/suite/pts/hostTests/uihost/appB/Android.mk
+++ b/suite/pts/hostTests/uihost/appB/Android.mk
@@ -30,6 +30,6 @@
LOCAL_SDK_VERSION := 16
-include $(BUILD_PTS_PACKAGE)
+include $(BUILD_CTS_PACKAGE)
diff --git a/suite/pts/hostTests/uihost/control/Android.mk b/suite/pts/hostTests/uihost/control/Android.mk
index 86ceb7c..dec5ad1 100644
--- a/suite/pts/hostTests/uihost/control/Android.mk
+++ b/suite/pts/hostTests/uihost/control/Android.mk
@@ -30,4 +30,4 @@
LOCAL_SDK_VERSION := 16
-include $(BUILD_PTS_PACKAGE)
+include $(BUILD_CTS_PACKAGE)
diff --git a/suite/pts/hostTests/uihost/control/src/com/android/pts/taskswitching/control/TaskswitchingDeviceTest.java b/suite/pts/hostTests/uihost/control/src/com/android/pts/taskswitching/control/TaskswitchingDeviceTest.java
index fc5bc04..5147eaf 100644
--- a/suite/pts/hostTests/uihost/control/src/com/android/pts/taskswitching/control/TaskswitchingDeviceTest.java
+++ b/suite/pts/hostTests/uihost/control/src/com/android/pts/taskswitching/control/TaskswitchingDeviceTest.java
@@ -27,6 +27,8 @@
import com.android.pts.util.MeasureRun;
import com.android.pts.util.MeasureTime;
+import com.android.pts.util.ResultType;
+import com.android.pts.util.ResultUnit;
import com.android.pts.util.PtsAndroidTestCase;
import com.android.pts.util.Stat;
@@ -81,9 +83,11 @@
}
}
});
- getReportLog().printArray("ms", results, false);
+ getReportLog().printArray("taskswitching time", results, ResultType.LOWER_BETTER,
+ ResultUnit.MS);
Stat.StatResult stat = Stat.getStat(results);
- getReportLog().printSummary("Time ms", stat.mAverage, stat.mStddev);
+ getReportLog().printSummary("taskswitching time", stat.mAverage,
+ ResultType.LOWER_BETTER, ResultUnit.MS);
}
private void startActivity(String packageName, String activityName) {
diff --git a/suite/pts/hostTests/uihost/src/com/android/pts/uihost/InstallTimeTest.java b/suite/pts/hostTests/uihost/src/com/android/pts/uihost/InstallTimeTest.java
index 4bfcde1..056e0f4 100644
--- a/suite/pts/hostTests/uihost/src/com/android/pts/uihost/InstallTimeTest.java
+++ b/suite/pts/hostTests/uihost/src/com/android/pts/uihost/InstallTimeTest.java
@@ -16,37 +16,26 @@
package com.android.pts.uihost;
-import android.cts.util.TimeoutReq;
-
import com.android.cts.tradefed.build.CtsBuildHelper;
-import com.android.ddmlib.Log;
-import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
-import com.android.ddmlib.testrunner.TestIdentifier;
import com.android.pts.util.MeasureRun;
import com.android.pts.util.MeasureTime;
-import com.android.pts.util.PtsException;
+import com.android.pts.util.ResultType;
+import com.android.pts.util.ResultUnit;
import com.android.pts.util.ReportLog;
import com.android.pts.util.Stat;
import com.android.pts.util.Stat.StatResult;
import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.device.TestDeviceOptions;
-import com.android.tradefed.result.CollectingTestListener;
-import com.android.tradefed.result.TestResult;
-import com.android.tradefed.result.TestRunResult;
import com.android.tradefed.testtype.DeviceTestCase;
import com.android.tradefed.testtype.IBuildReceiver;
import java.io.File;
-import java.util.Map;
+
/**
* Test to measure installation time of a APK.
*/
public class InstallTimeTest extends DeviceTestCase implements IBuildReceiver {
- private static final String TAG = "InstallTimeTest";
- private final static String CTS_RUNNER = "android.test.InstrumentationCtsTestRunner";
private CtsBuildHelper mBuild;
private ITestDevice mDevice;
private ReportLog mReport = null;
@@ -89,9 +78,11 @@
device.installPackage(app, false);
}
});
- mReport.printArray("time in ms", result, false);
+ mReport.printArray("install time", result, ResultType.LOWER_BETTER,
+ ResultUnit.MS);
StatResult stat = Stat.getStat(result);
- mReport.printSummary("time in ms", stat.mAverage, stat.mStddev);
+ mReport.printSummary("install time", stat.mAverage, ResultType.LOWER_BETTER,
+ ResultUnit.MS);
}
}
diff --git a/suite/pts/lib/commonutil/Android.mk b/suite/pts/lib/commonutil/Android.mk
index 27e0ba7..152f862 100644
--- a/suite/pts/lib/commonutil/Android.mk
+++ b/suite/pts/lib/commonutil/Android.mk
@@ -35,6 +35,8 @@
LOCAL_MODULE_TAGS := optional
+LOCAL_JAVA_LIBRARIES := junit
+
LOCAL_MODULE := ptscommonutilhost
include $(BUILD_HOST_JAVA_LIBRARY)
\ No newline at end of file
diff --git a/suite/pts/lib/commonutil/src/com/android/pts/util/ReportLog.java b/suite/pts/lib/commonutil/src/com/android/pts/util/ReportLog.java
index 0d69c2f..8a5e624 100644
--- a/suite/pts/lib/commonutil/src/com/android/pts/util/ReportLog.java
+++ b/suite/pts/lib/commonutil/src/com/android/pts/util/ReportLog.java
@@ -19,17 +19,17 @@
import java.util.LinkedList;
import java.util.List;
+import junit.framework.Assert;
+
/**
* Utility class to print performance measurement result back to host.
* For now, throws know exception with message.
*
* Format:
- * LOG_SEPARATOR : separates each log
- * Message = log [LOG_SEPARATOR log]*
- * log for single value = classMethodName:line_number|header|d|value
- * log for array = classMethodName:line_number|header|da|values|
- * average average_value min|max value stddev value
+ * Message = summary log SUMMARY_SEPARATOR [LOG_SEPARATOR log]*
+ * summary = message|target|unit|type|value, target can be " " if there is no target set.
+ * log for array = classMethodName:line_number|message|unit|type|space seSummaryparated values
*/
public class ReportLog {
private static final String LOG_SEPARATOR = "+++";
@@ -39,42 +39,80 @@
private List<String> mMessages = new LinkedList<String> ();
private String mSummary = null;
protected static int mDepth = 3;
+
/**
- * print given value to the report
- * @param header string to explain the contents. It can be unit for the value.
- * @param val
+ * print array of values to output log
*/
- public void printValue(String header, double val) {
- String message = getClassMethodNames(mDepth, true) + LOG_ELEM_SEPARATOR + header +
- LOG_ELEM_SEPARATOR + "d" + LOG_ELEM_SEPARATOR + val;
- mMessages.add(message);
- printLog(message);
+ public void printArray(String message, double[] values, ResultType type,
+ ResultUnit unit) {
+ doPrintArray(message, values, type, unit);
}
/**
- * array version of printValue
- * @param header
- * @param val
- * @param addMin add minimum to the result. If false, add maximum to the result
+ * Print a value to output log
*/
- public void printArray(String header, double[] val, boolean addMin) {
+ public void printValue(String message, double value, ResultType type,
+ ResultUnit unit) {
+ double[] vals = { value };
+ doPrintArray(message, vals, type, unit);
+ }
+
+ private void doPrintArray(String message, double[] values, ResultType type,
+ ResultUnit unit) {
StringBuilder builder = new StringBuilder();
- builder.append(getClassMethodNames(mDepth, true) + LOG_ELEM_SEPARATOR + header +
- LOG_ELEM_SEPARATOR + "da" + LOG_ELEM_SEPARATOR);
- for (double v : val) {
+ // note mDepth + 1 as this function will be called by printVaue or printArray
+ // and we need caller of printValue / printArray
+ builder.append(getClassMethodNames(mDepth + 1, true) + LOG_ELEM_SEPARATOR + message +
+ LOG_ELEM_SEPARATOR + type.getXmlString() + LOG_ELEM_SEPARATOR +
+ unit.getXmlString() + LOG_ELEM_SEPARATOR);
+ for (double v : values) {
builder.append(v);
builder.append(" ");
}
- Stat.StatResult stat = Stat.getStat(val);
- builder.append(LOG_ELEM_SEPARATOR + "average " + stat.mAverage +
- (addMin ? (" min " + stat.mMin) : (" max " + stat.mMax)) + " stddev " + stat.mStddev);
mMessages.add(builder.toString());
printLog(builder.toString());
}
- public void printSummary(String header, double average, double stddev) {
- mSummary = header + LOG_ELEM_SEPARATOR + "average " + average + LOG_ELEM_SEPARATOR +
- "stddev " + stddev;
+ /**
+ * record the result of benchmarking with performance target.
+ * Depending on the ResultType, the function can fail if the result
+ * does not meet the target. For example, for the type of HIGHER_BETTER,
+ * value of 1.0 with target of 2.0 will fail.
+ *
+ * @param message message to be printed in the final report
+ * @param target target performance for the benchmarking
+ * @param value measured value
+ * @param type
+ * @param unit
+ */
+ public void printSummaryWithTarget(String message, double target, double value,
+ ResultType type, ResultUnit unit) {
+ mSummary = message + LOG_ELEM_SEPARATOR + target + LOG_ELEM_SEPARATOR + type.getXmlString()
+ + LOG_ELEM_SEPARATOR + unit.getXmlString() + LOG_ELEM_SEPARATOR + value;
+ boolean resultOk = true;
+ if (type == ResultType.HIGHER_BETTER) {
+ resultOk = value >= target;
+ } else if (type == ResultType.LOWER_BETTER) {
+ resultOk = value <= target;
+ }
+ if (!resultOk) {
+ Assert.fail("Measured result " + value + " does not meet perf target " + target +
+ " with type " + type.getXmlString());
+ }
+ }
+
+ /**
+ * For standard report summary without target value.
+ * Note that this function will not fail as there is no target.
+ * @param messsage
+ * @param value
+ * @param type type of the value
+ * @param unit unit of the data
+ */
+ public void printSummary(String message, double value, ResultType type,
+ ResultUnit unit) {
+ mSummary = message + LOG_ELEM_SEPARATOR + " " + LOG_ELEM_SEPARATOR + type.getXmlString() +
+ LOG_ELEM_SEPARATOR + unit.getXmlString() + LOG_ELEM_SEPARATOR + value;
}
public void throwReportToHost() throws PtsException {
@@ -114,9 +152,6 @@
/**
* array version of calcRatePerSecArray
- * @param change
- * @param timeInMSec
- * @return
*/
public static double[] calcRatePerSecArray(double change, double[] timeInMSec) {
double[] result = new double[timeInMSec.length];
@@ -134,9 +169,6 @@
/**
* copy array from src to dst with given offset in dst.
* dst should be big enough to hold src
- * @param src
- * @param dst
- * @param dstOffset
*/
public static void copyArray(double[] src, double[] dst, int dstOffset) {
for (int i = 0; i < src.length; i++) {
@@ -146,8 +178,6 @@
/**
* get classname.methodname from call stack of the current thread
- *
- * @return
*/
public static String getClassMethodNames() {
return getClassMethodNames(mDepth, false);
@@ -155,14 +185,13 @@
private static String getClassMethodNames(int depth, boolean addLineNumber) {
StackTraceElement[] elements = Thread.currentThread().getStackTrace();
- String names = elements[depth].getClassName() + "." + elements[depth].getMethodName() +
+ String names = elements[depth].getClassName() + "#" + elements[depth].getMethodName() +
(addLineNumber ? ":" + elements[depth].getLineNumber() : "");
return names;
}
/**
* to be overridden by child to print message to be passed
- * @param msg
*/
protected void printLog(String msg) {
diff --git a/suite/pts/lib/commonutil/src/com/android/pts/util/ResultType.java b/suite/pts/lib/commonutil/src/com/android/pts/util/ResultType.java
new file mode 100644
index 0000000..cdf448b
--- /dev/null
+++ b/suite/pts/lib/commonutil/src/com/android/pts/util/ResultType.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.pts.util;
+
+/**
+ * Enum for distinguishing performance results.
+ */
+public enum ResultType {
+ /** lower score shows better performance */
+ LOWER_BETTER,
+ /** higher score shows better performance */
+ HIGHER_BETTER,
+ /** This value is not directly correlated with performance. */
+ NEUTRAL,
+ /** presence of this type requires some attention although it may not be an error. */
+ WARNING;
+
+ /**
+ * Return string used in CTS XML report
+ */
+ public String getXmlString() {
+ return name().toLowerCase();
+ }
+}
diff --git a/suite/pts/lib/commonutil/src/com/android/pts/util/ResultUnit.java b/suite/pts/lib/commonutil/src/com/android/pts/util/ResultUnit.java
new file mode 100644
index 0000000..b234cb5
--- /dev/null
+++ b/suite/pts/lib/commonutil/src/com/android/pts/util/ResultUnit.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.pts.util;
+
+/**
+ * Enum for representing the unit of performance results.
+ *
+ */
+public enum ResultUnit {
+ /** for value with no unit */
+ NONE,
+ /** milli-seconds */
+ MS,
+ /** frames per second */
+ FPS,
+ /** operations per second */
+ OPS,
+ /** kilo-bytes-per-second, not bits-per-second */
+ KBPS,
+ /** mega-bytes-per-second */
+ MBPS,
+ /** amount of data, bytes */
+ BYTE,
+ /** tell how many times it did happen. */
+ COUNT,
+ /** unit for benchmarking with generic score. */
+ SCORE;
+
+ /**
+ * Return string used in CTS XML report
+ */
+ public String getXmlString() {
+ return name().toLowerCase();
+ }
+}
+
diff --git a/suite/pts/lib/commonutil/src/com/android/pts/util/Stat.java b/suite/pts/lib/commonutil/src/com/android/pts/util/Stat.java
index aae51ec..d56f59e 100644
--- a/suite/pts/lib/commonutil/src/com/android/pts/util/Stat.java
+++ b/suite/pts/lib/commonutil/src/com/android/pts/util/Stat.java
@@ -22,6 +22,9 @@
*/
public class Stat {
+ /**
+ * Collection of statistical propertirs like average, max, min, and stddev
+ */
public static class StatResult {
public double mAverage;
public double mMin;
@@ -35,6 +38,9 @@
}
}
+ /**
+ * Calculate statistics properties likes average, min, max, and stddev for the given array
+ */
public static StatResult getStat(double[] data) {
double average = data[0];
double min = data[0];
@@ -57,6 +63,20 @@
return new StatResult(average, min, max, stddev);
}
+ /**
+ * return the average value of the passed array
+ */
+ public static double getAverage(double[] data) {
+ double sum = data[0];
+ for (int i = 1; i < data.length; i++) {
+ sum += data[i];
+ }
+ return sum / data.length;
+ }
+
+ /**
+ * return the minimum value of the passed array
+ */
public static double getMin(double[] data) {
double min = data[0];
for (int i = 1; i < data.length; i++) {
@@ -67,6 +87,9 @@
return min;
}
+ /**
+ * return the maximum value of the passed array
+ */
public static double getMax(double[] data) {
double max = data[0];
for (int i = 1; i < data.length; i++) {
diff --git a/suite/pts/tools/tradefed/pts-tradefed b/suite/pts/tools/tradefed/pts-tradefed
deleted file mode 100755
index 901ad8c..0000000
--- a/suite/pts/tools/tradefed/pts-tradefed
+++ /dev/null
@@ -1,86 +0,0 @@
-#!/bin/bash
-
-# Copyright (C) 2011 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# launcher script for cts-tradefed harness for PTS
-# can be used from an Android build environment, or a standalone pts zip
-
-checkFile() {
- if [ ! -f "$1" ]; then
- echo "Unable to locate $1"
- exit
- fi;
-}
-
-checkPath() {
- if ! type -P $1 &> /dev/null; then
- echo "Unable to find $1 in path."
- exit
- fi;
-}
-
-checkPath adb
-checkPath java
-
-# check java version
-JAVA_VERSION=$(java -version 2>&1 | head -n 1 | grep '[ "]1\.6[\. "$$]')
-if [ "${JAVA_VERSION}" == "" ]; then
- echo "Wrong java version. 1.6 is required."
- exit
-fi
-
-# check debug flag and set up remote debugging
-if [ -n "${TF_DEBUG}" ]; then
- if [ -z "${TF_DEBUG_PORT}" ]; then
- TF_DEBUG_PORT=10088
- fi
- RDBG_FLAG=-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=${TF_DEBUG_PORT}
-fi
-
-
-# check if in Android build env
-if [ ! -z ${ANDROID_BUILD_TOP} ]; then
- HOST=`uname`
- if [ "$HOST" == "Linux" ]; then
- OS="linux-x86"
- elif [ "$HOST" == "Darwin" ]; then
- OS="darwin-x86"
- else
- echo "Unrecognized OS"
- exit
- fi;
- PTS_ROOT=${ANDROID_BUILD_TOP}/out/host/${OS}/pts
- if [ ! -d ${PTS_ROOT} ]; then
- echo "Could not find $PTS_ROOT in Android build environment. Try 'make pts'"
- exit
- fi;
-fi;
-
-if [ -z ${PTS_ROOT} ]; then
- # assume we're in an extracted pts install
- PTS_ROOT="$(dirname $0)/../.."
-fi;
-
-JAR_DIR=${PTS_ROOT}/android-pts/tools
-JARS="ddmlib-prebuilt.jar tradefed-prebuilt.jar hosttestlib.jar cts-tradefed.jar ptscommonutilhost.jar ptshostutil.jar"
-
-for JAR in $JARS; do
- checkFile ${JAR_DIR}/${JAR}
- JAR_PATH=${JAR_PATH}:${JAR_DIR}/${JAR}
-done
-
-java $RDBG_FLAG \
- -cp ${JAR_PATH} -DCTS_ROOT=${PTS_ROOT} -DPTS=1 com.android.cts.tradefed.command.CtsConsole "$@"
-
diff --git a/suite/pts/utils/get_csv_report.py b/suite/pts/utils/get_csv_report.py
index fe3dd74..4c496d2 100755
--- a/suite/pts/utils/get_csv_report.py
+++ b/suite/pts/utils/get_csv_report.py
@@ -1,20 +1,19 @@
#!/usr/bin/env python
#
-# Copyright (C) 2012 The Android Open Source Project
+# Copyright (C) 2013 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the 'License');
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
-# http://www.apache.org/licenses/LICENSE-2.0
+# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an 'AS IS' BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-#
-import csv
+
import os
import re
import subprocess
@@ -22,173 +21,191 @@
from xml.dom import Node
from xml.dom import minidom
-
def getChildrenWithTag(parent, tagName):
- children = []
- for child in parent.childNodes:
- if (child.nodeType == Node.ELEMENT_NODE) and (child.tagName == tagName):
- #print "parent " + parent.getAttribute("name") + " " + tagName +\
- # " " + child.getAttribute("name")
- children.append(child)
- return children
+ children = []
+ for child in parent.childNodes:
+ if (child.nodeType == Node.ELEMENT_NODE) and (child.tagName == tagName):
+ #print "parent " + parent.getAttribute("name") + " " + tagName +\
+ # " " + child.getAttribute("name")
+ children.append(child)
+ return children
+
+def getText(tag):
+ return str(tag.firstChild.nodeValue)
class TestCase(object):
- def __init__(self, name, average, stddev, passFail):
- self.name = name
- self.average = average
- self.stddev = stddev
- self.passFail = passFail
+ def __init__(self, name, summary, details, result):
+ self.name = name
+ self.summary = summary
+ self.details = details
+ self.result = result
- def getName(self):
- return self.name
+ def getName(self):
+ return self.name
- def getStddev(self):
- return self.stddev
+ def getSummary(self):
+ return self.summary
- def getAverage(self):
- return self.average
+ def getDetails(self):
+ return self.details
- def getPassFail(self):
- return self.passFail
+ def getResult(self):
+ return self.result
def parseSuite(suite, parentName):
- if parentName != "":
- parentName += '.'
- cases = {}
- childSuites = getChildrenWithTag(suite, "TestSuite")
- for child in childSuites:
- cases.update(parseSuite(child, parentName + child.getAttribute("name")))
- childTestCases = getChildrenWithTag(suite, "TestCase")
- for child in childTestCases:
- className = parentName + child.getAttribute("name")
- for test in getChildrenWithTag(child, "Test"):
- methodName = test.getAttribute("name")
- # do not include this
- if methodName == "testAndroidTestCaseSetupProperly":
- continue
- caseName = className + "#" + methodName
- passFail = test.getAttribute("result")
- average = ""
- stddev = ""
- failedScene = getChildrenWithTag(test, "FailedScene")
- if len(failedScene) > 0:
- message = failedScene[0].getAttribute("message")
- #print message
- messages = message.split('|')
- if len(messages) > 2:
- average = messages[1].split()[1]
- stddev = messages[2].split()[1]
- testCase = TestCase(caseName, average, stddev, passFail)
- cases[caseName] = testCase
- return cases
+ if parentName != "":
+ parentName += '.'
+ cases = {}
+ childSuites = getChildrenWithTag(suite, "TestSuite")
+ for child in childSuites:
+ cases.update(parseSuite(child, parentName + child.getAttribute("name")))
+ childTestCases = getChildrenWithTag(suite, "TestCase")
+ for child in childTestCases:
+ className = parentName + child.getAttribute("name")
+ for test in getChildrenWithTag(child, "Test"):
+ methodName = test.getAttribute("name")
+ # do not include this
+ if methodName == "testAndroidTestCaseSetupProperly":
+ continue
+ caseName = str(className + "#" + methodName)
+ result = str(test.getAttribute("result"))
+ summary = {}
+ details = {}
+ if result == "pass":
+ sts = getChildrenWithTag(test, "Summary")
+ dts = getChildrenWithTag(test, "Details")
+ if len(sts) == len(dts) == 1:
+ summary[sts[0].getAttribute("message")] = getText(sts[0])
+ for d in getChildrenWithTag(dts[0], "ValueArray"):
+ values = []
+ for c in getChildrenWithTag(d, "Value"):
+ values.append(getText(c))
+ details[d.getAttribute("message")] = values
+ else:
+ result = "no results"
+ testCase = TestCase(caseName, summary, details, result)
+ cases[caseName] = testCase
+ return cases
class Result(object):
- def __init__(self, reportXml):
- self.results = {}
- self.infoKeys = []
- self.infoValues = []
- doc = minidom.parse(reportXml)
- testResult = doc.getElementsByTagName("TestResult")[0]
- buildInfo = testResult.getElementsByTagName("BuildInfo")[0]
- buildId = buildInfo.getAttribute("buildID")
- deviceId = buildInfo.getAttribute("deviceID")
- deviceName = buildInfo.getAttribute("build_device")
- boardName = buildInfo.getAttribute("build_board")
- partitions = buildInfo.getAttribute("partitions")
- m = re.search(r'.*;/data\s+(\w+)\s+(\w+)\s+(\w+)\s+(\w+);', partitions)
- dataPartitionSize = m.group(1)
- self.addKV("device", deviceName)
- self.addKV("board", boardName)
- self.addKV("serial", deviceId)
- self.addKV("build", buildId)
- self.addKV("data size", dataPartitionSize)
- packages = getChildrenWithTag(testResult, "TestPackage")
- for package in packages:
- casesFromChild = parseSuite(package, "")
- self.results.update(casesFromChild)
- #print self.results.keys()
+ def __init__(self, reportXml):
+ self.results = {}
+ self.infoKeys = []
+ self.infoValues = []
+ doc = minidom.parse(reportXml)
+ testResult = doc.getElementsByTagName("TestResult")[0]
+ buildInfo = testResult.getElementsByTagName("BuildInfo")[0]
+ buildId = buildInfo.getAttribute("buildID")
+ deviceId = buildInfo.getAttribute("deviceID")
+ deviceName = buildInfo.getAttribute("build_device")
+ boardName = buildInfo.getAttribute("build_board")
+ partitions = buildInfo.getAttribute("partitions")
+ m = re.search(r'.*;/data\s+([\w\.]+)\s+([\w\.]+)\s+([\w\.]+)\s+([\w\.]+);', partitions)
+ dataPartitionSize = m.group(1)
+ self.addKV("device", deviceName)
+ self.addKV("board", boardName)
+ self.addKV("serial", deviceId)
+ self.addKV("build", buildId)
+ self.addKV("data size", dataPartitionSize)
+ packages = getChildrenWithTag(testResult, "TestPackage")
+ for package in packages:
+ casesFromChild = parseSuite(package, "")
+ self.results.update(casesFromChild)
+ #print self.results.keys()
- def addKV(self, key, value):
- self.infoKeys.append(key)
- self.infoValues.append(value)
+ def addKV(self, key, value):
+ self.infoKeys.append(key)
+ self.infoValues.append(value)
- def getResults(self):
- return self.results
+ def getResults(self):
+ return self.results
- def getKeys(self):
- return self.infoKeys
+ def getKeys(self):
+ return self.infoKeys
- def getValues(self):
- return self.infoValues
+ def getValues(self):
+ return self.infoValues
def executeWithResult(command):
- p = subprocess.Popen(command.split(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
- out, err = p.communicate()
- return out
+ p = subprocess.Popen(command.split(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+ out, err = p.communicate()
+ return out
+
+def parseReports(path):
+ deviceResults = []
+ xmls = executeWithResult("find " + path + " -name testResult.xml -print")
+ print "xml files found :"
+ print xmls
+ for xml in xmls.splitlines():
+ result = Result(xml)
+ deviceResults.append(result)
+ reportInfo = {}
+ keys = deviceResults[0].getKeys()
+ noDevices = len(deviceResults)
+ for i in xrange(len(keys)):
+ values = []
+ for j in xrange(noDevices):
+ values.append(str(deviceResults[j].getValues()[i]))
+ reportInfo[keys[i]] = values
+ #print reportInfo
+
+ tests = []
+ for deviceResult in deviceResults:
+ for key in deviceResult.getResults().keys():
+ if not key in tests:
+ tests.append(key)
+ tests.sort()
+ #print tests
+
+ reportTests = {}
+ for i in xrange(len(tests)):
+ test = tests[i]
+ reportTests[test] = []
+ for j in xrange(noDevices):
+ values = {}
+ if deviceResults[j].getResults().has_key(test):
+ result = deviceResults[j].getResults()[test]
+ values["result"] = result.getResult()
+ values["summary"] = result.getSummary()
+ values["details"] = result.getDetails()
+ values["device"] = deviceResults[j].getValues()[0]
+ reportTests[test].append(values)
+
+ #print reportTests
+ return (reportInfo, reportTests)
def main(argv):
- if len(argv) < 3:
- print "get_csv_report.py pts_report_dir output_file"
- sys.exit(1)
- reportPath = os.path.abspath(argv[1])
- outputCsv = os.path.abspath(argv[2])
+ if len(argv) < 3:
+ print "get_csv_report.py pts_report_dir output_file"
+ sys.exit(1)
+ reportPath = os.path.abspath(argv[1])
+ outputCsv = os.path.abspath(argv[2])
- deviceResults = []
- xmls = executeWithResult("find " + reportPath + " -name testResult.xml -print")
- print "xml files found :"
- print xmls
- for xml in xmls.splitlines():
- result = Result(xml)
- deviceResults.append(result)
- reportInfo = []
- keys = deviceResults[0].getKeys()
- noDevices = len(deviceResults)
- for i in xrange(len(keys)):
- reportInfo.append([])
- reportInfo[i].append(keys[i])
- # for worst/average
- reportInfo[i].append("")
- for j in xrange(noDevices):
- reportInfo[i].append(deviceResults[j].getValues()[i])
- #print reportInfo
+ (reportInfo, reportTests) = parseReports(reportPath)
- tests = []
- for deviceResult in deviceResults:
- for key in deviceResult.getResults().keys():
- if not key in tests:
- tests.append(key)
- tests.sort()
- #print tests
-
- reportTests = []
- for i in xrange(len(tests)):
- reportTests.append([])
- reportTests.append([])
- reportTests[2 * i].append(tests[i])
- reportTests[2 * i + 1].append(tests[i])
- reportTests[2 * i].append("average")
- reportTests[2 * i + 1].append("stddev")
- for j in xrange(noDevices):
- if deviceResults[j].getResults().has_key(tests[i]):
- result = deviceResults[j].getResults()[tests[i]]
- if result.getPassFail() == "pass":
- reportTests[2 * i].append(result.getAverage())
- reportTests[2 * i + 1].append(result.getStddev())
- else:
- reportTests[2 * i].append("fail")
- reportTests[2 * i + 1].append("fail")
- else:
- reportTests[2 * i].append("")
- reportTests[2 * i + 1].append("")
-
- #print reportTests
-
- with open(outputCsv, 'wb') as f:
- writer = csv.writer(f)
- writer.writerows(reportInfo)
- writer.writerows(reportTests)
-
+ with open(outputCsv, 'w') as f:
+ for key in reportInfo:
+ f.write(key)
+ for value in reportInfo[key]:
+ f.write(',')
+ f.write(value)
+ f.write('\n')
+ for test in reportTests:
+ for report in reportTests[test]:
+ if report.has_key('result'):
+ result = report['result']
+ f.write(test)
+ f.write(',')
+ f.write(result)
+ for key in report['summary']:
+ f.write(',')
+ f.write(report['summary'][key])
+ for key in report['details']:
+ for value in report['details'][key]:
+ f.write(',')
+ f.write(value)
+ f.write('\n')
if __name__ == '__main__':
- main(sys.argv)
+ main(sys.argv)
diff --git a/suite/pts/utils/grapher.py b/suite/pts/utils/grapher.py
new file mode 100755
index 0000000..42b5f11
--- /dev/null
+++ b/suite/pts/utils/grapher.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the 'License');
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an 'AS IS' BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os, sys
+import get_csv_report as psr
+import matplotlib.pyplot as plt
+import matplotlib.mlab as mlab
+import matplotlib.cbook as cbook
+import matplotlib.ticker as ticker
+"""
+A simple script to render the data from the benchmark as a graph.
+This uses MatPlotLib (http://matplotlib.org/) to plot which can be installed on linux with;
+ sudo apt-get install python-matplotlib
+"""
+
+def main(argv):
+ if len(argv) != 2:
+ print "grapher.py pts_report_dir"
+ sys.exit(1)
+
+ (_, tests) = psr.parseReports(os.path.abspath(argv[1]))
+
+ # For each of the benchmarks
+ for benchmark in tests:
+ if benchmark.startswith('com.android.pts.opengl'):
+ results = tests[benchmark]
+ legend = []
+ # Create a new figure
+ fig = plt.figure()
+ # Set the title of the graph
+ plt.title(benchmark)
+ # For each result in the data set
+ for r in results:
+ score = r['result']
+ x = []
+ y = []
+ if score == 'pass':
+ y = r['details']['Fps Values']
+ x = range(1, len(y) + 1)
+ # Get the score, then trim it to 2 decimal places
+ score = r['summary']['Score']
+ score = score[0:score.index('.') + 3]
+ if score != 'no results':
+ # Create a plot
+ ax = fig.add_subplot(111)
+ # Plot the workload vs the values
+ ax.plot(x, y, 'o-', label=r['device'] + ' (%s)'%score)
+ # Add a legend
+ ax.legend(loc='upper right').get_frame().set_fill(False)
+ plt.xlabel('Iteration')
+ plt.ylabel('FPS')
+ fig.autofmt_xdate()
+ # Show the plots
+ plt.show()
+
+if __name__ == '__main__':
+ main(sys.argv)
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index 590ee36..6a07ef8 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -244,6 +244,14 @@
</intent-filter>
</activity>
+ <activity android:name="android.widget.cts.GridLayoutStubActivity"
+ android:label="GridLayoutStubActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
<activity android:name="android.widget.cts.LayoutDirectionStubActivity"
android:label="LayoutDirectionStubActivity">
<intent-filter>
@@ -419,6 +427,14 @@
</intent-filter>
</activity>
+ <activity android:name="android.text.cts.EmojiStubActivity"
+ android:label="AvailableIntentsActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
<activity android:name="android.widget.cts.TableStubActivity"
android:label="TableStubActivity">
<intent-filter>
diff --git a/tests/SignatureTest/Android.mk b/tests/SignatureTest/Android.mk
index cc0d53c..0b51f25 100644
--- a/tests/SignatureTest/Android.mk
+++ b/tests/SignatureTest/Android.mk
@@ -33,7 +33,11 @@
# To be passed in on command line
CTS_API_VERSION ?= current
+ifeq (current,$(CTS_API_VERSION))
+android_api_description := frameworks/base/api/$(CTS_API_VERSION).txt
+else
android_api_description := $(SRC_API_DIR)/$(CTS_API_VERSION).txt
+endif
# Can't call local-intermediates-dir directly here because we have to
# include BUILD_PACAKGE first. Can't include BUILD_PACKAGE first
@@ -41,75 +45,50 @@
# hack.
intermediates.COMMON := $(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),,COMMON)
signature_res_dir := $(intermediates.COMMON)/genres
-LOCAL_RESOURCE_DIR := $(signature_res_dir)
-
-# These shell commands allow us to get around the package.mk check for
-# empty/non-existent resouce dirs (which ours would be). If it finds
-# an empty/non-existent resource dir, R_file_stamp doesn't contain a
-# target and our stuff never gets copied.
-# TODO: fix package.mk so we can avoid this hack
-fake_resource_check := $(signature_res_dir)/raw/fake_resource_check
-
-$(shell \
- if [ ! -f $(fake_resource_check) ]; then \
- mkdir -p $(dir $(fake_resource_check)); \
- touch $(fake_resource_check); \
- fi \
- )
+LOCAL_RESOURCE_DIR := $(signature_res_dir) $(LOCAL_PATH)/res
include $(BUILD_PACKAGE)
-copied_res_stamp := $(intermediates.COMMON)/copyres.stamp
generated_res_stamp := $(intermediates.COMMON)/genres.stamp
-api_ver_path := $(intermediates.COMMON)
-api_ver_file := $(api_ver_path)/api_ver_is_$(CTS_API_VERSION)
+api_ver_file := $(intermediates.COMMON)/api_ver_is_$(CTS_API_VERSION)
# The api_ver_file keeps track of which api version was last built.
# By only ever having one of these magic files in existance and making
# sure the generated resources rule depend on it, we can ensure that
# the proper version of the api resource gets generated.
$(api_ver_file):
- $(hide) rm -f $(api_ver_path)/api_ver_is_*
- $(hide) touch $@
+ $(hide) rm -f $(dir $@)/api_ver_is_* \
+ && mkdir -p $(dir $@) && touch $@
android_api_xml_description := $(intermediates.COMMON)/api.xml
-$(android_api_xml_description): PRIVATE_INPUT_FILE := $(android_api_description)
-$(android_api_xml_description): $(android_api_description) $(APICHECK)
- $(hide) echo "Convert api file to xml: $@"
- $(hide) $(APICHECK_COMMAND) -convert2xml $(PRIVATE_INPUT_FILE) $@
-
-static_res_deps := $(call find-subdir-assets,$(LOCAL_PATH)/res)
-$(copied_res_stamp): PRIVATE_PATH := $(LOCAL_PATH)
-$(copied_res_stamp): PRIVATE_MODULE := $(LOCAL_MODULE)
-$(copied_res_stamp): PRIVATE_RES_DIR := $(signature_res_dir)
-$(copied_res_stamp): FAKE_RESOURCE_DIR := $(dir $(fake_resource_check))
-$(copied_res_stamp): FAKE_RESOURCE_CHECK := $(fake_resource_check)
-$(copied_res_stamp): $(foreach res,$(static_res_deps),$(LOCAL_PATH)/res/${res}) | $(ACP)
- $(hide) echo "Copy resources: $(PRIVATE_MODULE)"
- $(hide) rm -f $@
- $(hide) rm -rf $(PRIVATE_RES_DIR)
- $(hide) mkdir -p $(PRIVATE_RES_DIR)
- $(hide) if [ ! -f $(FAKE_RESOURCE_CHECK) ]; \
- then mkdir -p $(FAKE_RESOURCE_DIR); \
- touch $(FAKE_RESOURCE_CHECK); \
- fi
- $(hide) $(ACP) -rd $(PRIVATE_PATH)/res/* $(PRIVATE_RES_DIR)/
- $(hide) touch $@
+$(android_api_xml_description): $(android_api_description) | $(APICHECK)
+ @ echo "Convert api file to xml: $@"
+ @ mkdir -p $(dir $@)
+ $(hide) $(APICHECK_COMMAND) -convert2xml $< $@
# Split up config/api/1.xml by "package" and save the files as the
# resource files of SignatureTest.
$(generated_res_stamp): PRIVATE_PATH := $(LOCAL_PATH)
$(generated_res_stamp): PRIVATE_MODULE := $(LOCAL_MODULE)
$(generated_res_stamp): PRIVATE_RES_DIR := $(signature_res_dir)
+$(generated_res_stamp): PRIVATE_API_XML_DESC := $(android_api_xml_description)
$(generated_res_stamp): $(api_ver_file)
-$(generated_res_stamp): $(copied_res_stamp) $(android_api_xml_description)
- $(hide) echo "Copy generated resources: $(PRIVATE_MODULE)"
- $(hide) rm -f $@
+$(generated_res_stamp): $(android_api_xml_description)
+ @ echo "Copy generated resources: $(PRIVATE_MODULE)"
$(hide) python cts/tools/utils/android_api_description_splitter.py \
- $(android_api_xml_description) $(PRIVATE_RES_DIR) package
+ $(PRIVATE_API_XML_DESC) $(PRIVATE_RES_DIR) package
$(hide) touch $@
-$(R_file_stamp): $(generated_res_stamp) $(copied_res_stamp)
+$(R_file_stamp): $(generated_res_stamp)
+
+# clean up temp vars
+android_api_xml_description :=
+api_ver_file :=
+generated_res_stamp :=
+signature_res_dir :=
+android_api_description :=
+CTS_API_VERSION :=
+
# Use the folloing include to make our test apk.
include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/accessibility/AndroidManifest.xml b/tests/accessibility/AndroidManifest.xml
index dde1de8..0d18cef 100644
--- a/tests/accessibility/AndroidManifest.xml
+++ b/tests/accessibility/AndroidManifest.xml
@@ -19,6 +19,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.view.accessibility.services">
+ <uses-permission android:name="android.permission.CAN_REQUEST_TOUCH_EXPLORATION_MODE"/>
+
<application>
<service android:name=".SpeakingAccessibilityService"
diff --git a/tests/accessibility/res/xml/speaking_accessibilityservice.xml b/tests/accessibility/res/xml/speaking_accessibilityservice.xml
index 630940b..d43d3e7 100644
--- a/tests/accessibility/res/xml/speaking_accessibilityservice.xml
+++ b/tests/accessibility/res/xml/speaking_accessibilityservice.xml
@@ -16,5 +16,5 @@
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFeedbackType="feedbackSpoken"
- android:accessibilityFlags="flagDefault"
+ android:accessibilityFlags="flagDefault|flagRequestTouchExplorationMode"
android:canRetrieveWindowContent="true" />
diff --git a/tests/accessibility/res/xml/vibrating_accessibilityservice.xml b/tests/accessibility/res/xml/vibrating_accessibilityservice.xml
index 4ccec76..c2f8799 100644
--- a/tests/accessibility/res/xml/vibrating_accessibilityservice.xml
+++ b/tests/accessibility/res/xml/vibrating_accessibilityservice.xml
@@ -14,7 +14,7 @@
limitations under the License.
-->
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
- android:accessibilityEventTypes="typeAllMask"
+ android:accessibilityEventTypes="typeAllMask|"
android:accessibilityFeedbackType="feedbackHaptic"
- android:accessibilityFlags="flagDefault"
+ android:accessibilityFlags="flagDefault|flagRequestTouchExplorationMode"
android:canRetrieveWindowContent="true" />
diff --git a/tests/accessibilityservice/AndroidManifest.xml b/tests/accessibilityservice/AndroidManifest.xml
index 805f697..117578e 100644
--- a/tests/accessibilityservice/AndroidManifest.xml
+++ b/tests/accessibilityservice/AndroidManifest.xml
@@ -19,6 +19,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.accessibilityservice.delegate">
+ <uses-permission android:name="android.permission.CAN_REQUEST_TOUCH_EXPLORATION_MODE"/>
+
<application>
<uses-library android:name="android.test.runner"/>
diff --git a/tests/accessibilityservice/res/xml/accessibilityservice.xml b/tests/accessibilityservice/res/xml/accessibilityservice.xml
index 395d022..69e0651 100644
--- a/tests/accessibilityservice/res/xml/accessibilityservice.xml
+++ b/tests/accessibilityservice/res/xml/accessibilityservice.xml
@@ -16,7 +16,7 @@
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFeedbackType="feedbackGeneric"
- android:accessibilityFlags="flagDefault"
+ android:accessibilityFlags="flagDefault|flagRequestTouchExplorationMode"
android:canRetrieveWindowContent="true"
android:notificationTimeout="50"
android:settingsActivity="android.accessibilityservice.delegate.SomeActivity"
diff --git a/tests/assets/webkit/jswindow.html b/tests/assets/webkit/jswindow.html
index 534a683..7075d6e 100644
--- a/tests/assets/webkit/jswindow.html
+++ b/tests/assets/webkit/jswindow.html
@@ -23,7 +23,6 @@
childWindow = window.open();
childWindow.document.title = "javascript child window";
childWindow.document.write("javascript child window");
- childWindow.focus();
setTimeout(function(){childWindow.close();}, 2000);
}
</script>
diff --git a/tests/assets/webkit/network_state.html b/tests/assets/webkit/network_state.html
index 20358f4..4712b83 100644
--- a/tests/assets/webkit/network_state.html
+++ b/tests/assets/webkit/network_state.html
@@ -26,6 +26,6 @@
}
</script>
</head>
- <body onload="getNetworkState()">
+ <body onload="getNetworkState()" ononline="getNetworkState()" onoffline="getNetworkState()">
</body>
</html>
diff --git a/tests/assets/webkit/test_databaseaccess.html b/tests/assets/webkit/test_databaseaccess.html
new file mode 100644
index 0000000..dcf7b098
--- /dev/null
+++ b/tests/assets/webkit/test_databaseaccess.html
@@ -0,0 +1,27 @@
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<html>
+ <head><title>None</title><script>
+ function checkDatabase() {
+ if ('openDatabase' in window) {
+ var db = window.openDatabase('test', '1.0', 'results', 1*1024*1024);
+ }
+ document.title = db ? "Has database" : "No database";
+ }
+ </script></head>
+ <body onload='checkDatabase()'>
+ </body>
+</html>
diff --git a/tests/assets/webkit/test_firstPage.html b/tests/assets/webkit/test_firstPage.html
index 4a2f9b0..18a5548 100644
--- a/tests/assets/webkit/test_firstPage.html
+++ b/tests/assets/webkit/test_firstPage.html
@@ -17,6 +17,6 @@
<title>First page</title>
<body>
<h1>First page</h1>
- <a herf="webView_goBackAndForward_test_secondPage.html">Go to second page</a>
+ <a href="webView_goBackAndForward_test_secondPage.html">Go to second page</a>
</body>
</html>
diff --git a/tests/assets/webkit/test_secondPage.html b/tests/assets/webkit/test_secondPage.html
index 952d43f..ea8e2a1 100644
--- a/tests/assets/webkit/test_secondPage.html
+++ b/tests/assets/webkit/test_secondPage.html
@@ -17,6 +17,6 @@
<title>Second page</title>
<body>
<h1>Second page</h1>
- <a herf="webView_goBackAndForward_test_thirdPage.html">Go to third page</a>
+ <a href="webView_goBackAndForward_test_thirdPage.html">Go to third page</a>
</body>
</html>
diff --git a/tests/tests/openglperf/res/values/strings.xml b/tests/assets/webkit/test_stop_loading.html
similarity index 70%
rename from tests/tests/openglperf/res/values/strings.xml
rename to tests/assets/webkit/test_stop_loading.html
index 2cdba7f..1f0fc72 100644
--- a/tests/tests/openglperf/res/values/strings.xml
+++ b/tests/assets/webkit/test_stop_loading.html
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 The Android Open Source Project
+<!-- Copyright (C) 2013 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -13,6 +12,12 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<resources>
- <string name="app_name">OpenGlPerfTest</string>
-</resources>
\ No newline at end of file
+
+<html>
+ <head>
+ <title>Stop Loading test</title>
+ </head>
+ <body onload="window.javabridge.pageLoaded()">
+ <h3>hello world!</h3><br>
+ </body>
+</html>
diff --git a/tests/expectations/knownfailures.txt b/tests/expectations/knownfailures.txt
index 1992b11..d7ef39f 100644
--- a/tests/expectations/knownfailures.txt
+++ b/tests/expectations/knownfailures.txt
@@ -2,5 +2,9 @@
{
name: "android.openglperf.cts.GlVboPerfTest#testVboWithVaryingIndexBufferNumbers",
bug: 6950385
+},
+{
+ name: "android.holo.cts.HoloTest",
+ bug: 8148617
}
]
diff --git a/tests/res/layout/gridlayout_layout.xml b/tests/res/layout/gridlayout_layout.xml
new file mode 100644
index 0000000..54b3b2c
--- /dev/null
+++ b/tests/res/layout/gridlayout_layout.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2012 The Android Open Source Project.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+<GridLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ >
+</GridLayout>
diff --git a/tests/res/layout/viewtreeobserver_layout.xml b/tests/res/layout/viewtreeobserver_layout.xml
index 461a4bc..66e98b2 100644
--- a/tests/res/layout/viewtreeobserver_layout.xml
+++ b/tests/res/layout/viewtreeobserver_layout.xml
@@ -20,17 +20,19 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
- <ListView android:id="@+id/listview1"
+ <View android:id="@+id/view1"
android:layout_width="match_parent"
- android:layout_height="wrap_content"/>
+ android:layout_height="10dip"
+ android:focusable="true"/>
- <ListView android:id="@+id/listview2"
+ <View android:id="@+id/view2"
android:layout_width="match_parent"
- android:layout_height="wrap_content"/>
+ android:layout_height="10dip"
+ android:focusable="true"/>
<Button android:id="@+id/button1"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
+ android:layout_width="wrap_content"
+ android:layout_height="10dip" />
</LinearLayout>
diff --git a/tests/res/raw/set_target_api_11.bc b/tests/res/raw/set_target_api_11.bc
index babbd9e..fa04be6 100644
--- a/tests/res/raw/set_target_api_11.bc
+++ b/tests/res/raw/set_target_api_11.bc
Binary files differ
diff --git a/tests/res/raw/set_target_api_12.bc b/tests/res/raw/set_target_api_12.bc
index 92ee8de..abe314f 100644
--- a/tests/res/raw/set_target_api_12.bc
+++ b/tests/res/raw/set_target_api_12.bc
Binary files differ
diff --git a/tests/res/raw/set_target_api_13.bc b/tests/res/raw/set_target_api_13.bc
index c842b98..4fa5b75 100644
--- a/tests/res/raw/set_target_api_13.bc
+++ b/tests/res/raw/set_target_api_13.bc
Binary files differ
diff --git a/tests/res/raw/set_target_api_14.bc b/tests/res/raw/set_target_api_14.bc
index 24d2481..876b37c 100644
--- a/tests/res/raw/set_target_api_14.bc
+++ b/tests/res/raw/set_target_api_14.bc
Binary files differ
diff --git a/tests/res/raw/set_target_api_15.bc b/tests/res/raw/set_target_api_15.bc
index a6e86d4..d2de6d9 100644
--- a/tests/res/raw/set_target_api_15.bc
+++ b/tests/res/raw/set_target_api_15.bc
Binary files differ
diff --git a/tests/res/raw/set_target_api_16.bc b/tests/res/raw/set_target_api_16.bc
index 5ded6fd..ca9aa65 100644
--- a/tests/res/raw/set_target_api_16.bc
+++ b/tests/res/raw/set_target_api_16.bc
Binary files differ
diff --git a/tests/res/raw/set_target_api_17.bc b/tests/res/raw/set_target_api_17.bc
new file mode 100644
index 0000000..b08b389
--- /dev/null
+++ b/tests/res/raw/set_target_api_17.bc
Binary files differ
diff --git a/tests/res/raw/set_target_api_too_high.bc b/tests/res/raw/set_target_api_too_high.bc
new file mode 100644
index 0000000..95b2026
--- /dev/null
+++ b/tests/res/raw/set_target_api_too_high.bc
Binary files differ
diff --git a/tests/src/android/provider/cts/MediaStoreAudioTestHelper.java b/tests/src/android/provider/cts/MediaStoreAudioTestHelper.java
index 7bcd0c7..a87cd3a 100644
--- a/tests/src/android/provider/cts/MediaStoreAudioTestHelper.java
+++ b/tests/src/android/provider/cts/MediaStoreAudioTestHelper.java
@@ -221,6 +221,60 @@
}
}
+ public static class Audio3 extends Audio1 {
+ private Audio3() {
+ }
+
+ private static Audio3 sInstance = new Audio3();
+
+ public static Audio3 getInstance() {
+ return sInstance;
+ }
+
+ @Override
+ public ContentValues getContentValues(boolean isInternal) {
+ ContentValues values = super.getContentValues(isInternal);
+ values.put(Media.DATA, values.getAsString(Media.DATA) + "_3");
+ return values;
+ }
+ }
+
+ public static class Audio4 extends Audio1 {
+ private Audio4() {
+ }
+
+ private static Audio4 sInstance = new Audio4();
+
+ public static Audio4 getInstance() {
+ return sInstance;
+ }
+
+ @Override
+ public ContentValues getContentValues(boolean isInternal) {
+ ContentValues values = super.getContentValues(isInternal);
+ values.put(Media.DATA, values.getAsString(Media.DATA) + "_4");
+ return values;
+ }
+ }
+
+ public static class Audio5 extends Audio1 {
+ private Audio5() {
+ }
+
+ private static Audio5 sInstance = new Audio5();
+
+ public static Audio5 getInstance() {
+ return sInstance;
+ }
+
+ @Override
+ public ContentValues getContentValues(boolean isInternal) {
+ ContentValues values = super.getContentValues(isInternal);
+ values.put(Media.DATA, values.getAsString(Media.DATA) + "_5");
+ return values;
+ }
+ }
+
// These constants are not part of the public API
public static final String EXTERNAL_VOLUME_NAME = "external";
public static final String INTERNAL_VOLUME_NAME = "internal";
diff --git a/tests/src/android/renderscript/cts/foreach.rs b/tests/src/android/renderscript/cts/foreach.rs
index ac527b5..8747961 100644
--- a/tests/src/android/renderscript/cts/foreach.rs
+++ b/tests/src/android/renderscript/cts/foreach.rs
@@ -1,6 +1,7 @@
#include "shared.rsh"
int *a;
+rs_allocation aRaw;
int dimX;
int dimY;
static bool failed = false;
@@ -21,7 +22,8 @@
for (j = 0; j < dimY; j++) {
for (i = 0; i < dimX; i++) {
- _RS_ASSERT(a[i + j * dimX] == (i + j * dimX));
+ int v = rsGetElementAt_int(aRaw, i, j);
+ _RS_ASSERT(v == (i + j * dimX));
}
}
@@ -41,7 +43,8 @@
for (j = 0; j < dimY; j++) {
for (i = 0; i < dimX; i++) {
- _RS_ASSERT(a[i + j * dimX] == (99 + i + j * dimX));
+ int v = rsGetElementAt_int(aRaw, i, j);
+ _RS_ASSERT(v == (99 + i + j * dimX));
}
}
diff --git a/tests/src/android/renderscript/cts/instance.rs b/tests/src/android/renderscript/cts/instance.rs
new file mode 100644
index 0000000..097098a
--- /dev/null
+++ b/tests/src/android/renderscript/cts/instance.rs
@@ -0,0 +1,10 @@
+#include "shared.rsh"
+
+int i;
+rs_allocation ai;
+
+void instance_test() {
+ // Set our allocation based on the global input value.
+ rsSetElementAt(ai, &i, 0);
+}
+
diff --git a/tests/src/android/renderscript/cts/noroot.rs b/tests/src/android/renderscript/cts/noroot.rs
index 33944aa..f69effc 100644
--- a/tests/src/android/renderscript/cts/noroot.rs
+++ b/tests/src/android/renderscript/cts/noroot.rs
@@ -1,6 +1,7 @@
#include "shared.rsh"
int *a;
+rs_allocation aRaw;
int dimX;
int dimY;
static bool failed = false;
@@ -15,7 +16,8 @@
for (j = 0; j < dimY; j++) {
for (i = 0; i < dimX; i++) {
- _RS_ASSERT(a[i + j * dimX] == (99 + i + j * dimX));
+ int v = rsGetElementAt_int(aRaw, i, j);
+ _RS_ASSERT(v == (99 + i + j * dimX));
}
}
diff --git a/tests/src/android/renderscript/cts/setelementat.rs b/tests/src/android/renderscript/cts/setelementat.rs
new file mode 100644
index 0000000..75e96f5
--- /dev/null
+++ b/tests/src/android/renderscript/cts/setelementat.rs
@@ -0,0 +1,51 @@
+#pragma version(1)
+#pragma rs java_package_name(android.renderscript.cts)
+
+#include "shared.rsh"
+
+int memset_toValue = 0;
+
+int compare_value = 0;
+int compare_failure = 2;
+int failure_value = 0;
+
+uint32_t dimX = 0;
+uint32_t dimY = 0;
+rs_allocation array;
+
+void memset(int *aout) {
+ *aout = memset_toValue;
+ return;
+}
+
+void compare(const int *ain) {
+ if (*ain != compare_value) {
+ rsAtomicCas(&compare_failure, 2, -1);
+ failure_value = *ain;
+ }
+ return;
+}
+
+void getCompareResult(int* aout) {
+ *aout = compare_failure;
+}
+
+void setLargeArray(const int *ain, uint32_t x) {
+ int source = 10;
+ if (x == 0) {
+ for (uint32_t i = 0; i < dimX; i++) {
+ rsSetElementAt(array, &source, i);
+ }
+ }
+}
+
+void setLargeArray2D(const int *ain, uint32_t x) {
+ int source = 10;
+ if (x == 0) {
+ for (uint32_t y = 0; y < dimY; y++) {
+ for (uint32_t xtemp = 0; xtemp < dimX; xtemp++) {
+ rsSetElementAt(array, &source, xtemp, y);
+ }
+ }
+ }
+}
diff --git a/tests/src/android/text/EmojiStubActivity.java b/tests/src/android/text/EmojiStubActivity.java
new file mode 100755
index 0000000..1587c94
--- /dev/null
+++ b/tests/src/android/text/EmojiStubActivity.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text.cts;
+
+import com.android.cts.stub.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.webkit.WebView;
+
+public class EmojiStubActivity extends Activity {
+ private WebView mWebView;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.webview_layout);
+ mWebView = (WebView) findViewById(R.id.web_page);
+ }
+
+ public WebView getWebView() {
+ return mWebView;
+ }
+
+ @Override
+ public void onDestroy() {
+ mWebView.destroy();
+ super.onDestroy();
+ }
+}
diff --git a/tests/src/android/webkit/cts/WebViewOnUiThread.java b/tests/src/android/webkit/cts/WebViewOnUiThread.java
index bb07d08..cbda733 100644
--- a/tests/src/android/webkit/cts/WebViewOnUiThread.java
+++ b/tests/src/android/webkit/cts/WebViewOnUiThread.java
@@ -341,6 +341,24 @@
});
}
+ public void loadUrl(final String url) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mWebView.loadUrl(url);
+ }
+ });
+ }
+
+ public void stopLoading() {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mWebView.stopLoading();
+ }
+ });
+ }
+
public void loadDataAndWaitForCompletion(final String data,
final String mimeType, final String encoding) {
callAndWait(new Runnable() {
diff --git a/tests/src/android/widget/cts/GridLayoutStubActivity.java b/tests/src/android/widget/cts/GridLayoutStubActivity.java
new file mode 100644
index 0000000..3fa0f28
--- /dev/null
+++ b/tests/src/android/widget/cts/GridLayoutStubActivity.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.cts;
+
+import android.app.Activity;
+import android.os.Bundle;
+import com.android.cts.stub.R;
+
+/**
+ * A minimal application for {@link android.widget.GridLayout} test.
+ */
+public class GridLayoutStubActivity extends Activity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.gridlayout_layout);
+ }
+}
diff --git a/tests/tests/accessibility/src/android/view/accessibility/cts/AccessibilityEventTest.java b/tests/tests/accessibility/src/android/view/accessibility/cts/AccessibilityEventTest.java
index 9b46a9b..58b32d6 100644
--- a/tests/tests/accessibility/src/android/view/accessibility/cts/AccessibilityEventTest.java
+++ b/tests/tests/accessibility/src/android/view/accessibility/cts/AccessibilityEventTest.java
@@ -31,7 +31,7 @@
public class AccessibilityEventTest extends TestCase {
/** The number of properties of the {@link AccessibilityEvent} class. */
- private static final int NON_STATIC_FIELD_COUNT = 30;
+ private static final int NON_STATIC_FIELD_COUNT = 28;
/**
* Test that no new fields have been added without updating the
@@ -253,7 +253,6 @@
((Message) expectedEvent.getParcelableData()).what,
((Message) receivedEvent.getParcelableData()).what);
- AccessibilityRecord expectedRecord = expectedEvent.getRecord(0);
AccessibilityRecord receivedRecord = receivedEvent.getRecord(0);
AccessibilityRecordTest.assertEqualAccessibilityRecord(expectedEvent, receivedRecord);
}
diff --git a/tests/tests/accessibility/src/android/view/accessibility/cts/AccessibilityManagerTest.java b/tests/tests/accessibility/src/android/view/accessibility/cts/AccessibilityManagerTest.java
index 28e26eb..7ac0572 100644
--- a/tests/tests/accessibility/src/android/view/accessibility/cts/AccessibilityManagerTest.java
+++ b/tests/tests/accessibility/src/android/view/accessibility/cts/AccessibilityManagerTest.java
@@ -52,10 +52,6 @@
assertEquals("Accessibility should have been enabled by the test runner.",
1, Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_ENABLED));
-
- assertEquals("Touch exploration should have been enabled by the test runner.",
- 1, Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.TOUCH_EXPLORATION_ENABLED));
}
public void testAddAndRemoveAccessibilityStateChangeListener() throws Exception {
diff --git a/tests/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java b/tests/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java
index e0ffaf7..70a25b2 100644
--- a/tests/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java
+++ b/tests/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java
@@ -30,7 +30,7 @@
public class AccessibilityNodeInfoTest extends AndroidTestCase {
/** The number of properties of the {@link AccessibilityNodeInfo} class. */
- private static final int NON_STATIC_FIELD_COUNT = 19;
+ private static final int NON_STATIC_FIELD_COUNT = 20;
@SmallTest
public void testMarshaling() throws Exception {
@@ -124,6 +124,7 @@
info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
info.setLabeledBy(new View(getContext()));
info.setLabelFor(new View(getContext()));
+ info.setViewIdResourceName("foo.bar:id/baz");
}
/**
@@ -180,6 +181,8 @@
assertSame("movementGranularities has incorrect value",
expectedInfo.getMovementGranularities(),
receivedInfo.getMovementGranularities());
+ assertEquals("viewId has incorrect value", expectedInfo.getViewIdResourceName(),
+ receivedInfo.getViewIdResourceName());
}
/**
@@ -211,5 +214,6 @@
assertFalse("accessibilityFocused not properly recycled", info.isAccessibilityFocused());
assertSame("movementGranularities not properly recycled", 0,
info.getMovementGranularities());
+ assertNull("viewId not properly recycled", info.getViewIdResourceName());
}
}
diff --git a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityServiceInfoTest.java b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityServiceInfoTest.java
index 0f687d0..1d28470 100644
--- a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityServiceInfoTest.java
+++ b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityServiceInfoTest.java
@@ -110,7 +110,9 @@
AccessibilityServiceInfo speakingService = enabledServices.get(0);
assertSame(AccessibilityEvent.TYPES_ALL_MASK, speakingService.eventTypes);
assertSame(AccessibilityServiceInfo.FEEDBACK_GENERIC, speakingService.feedbackType);
- assertSame(AccessibilityServiceInfo.DEFAULT, speakingService.flags);
+ assertSame(AccessibilityServiceInfo.DEFAULT
+ | AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE,
+ speakingService.flags);
assertSame(50l, speakingService.notificationTimeout);
assertEquals("Delegating Accessibility Service", speakingService.getDescription());
assertNull(speakingService.packageNames /*all packages*/);
diff --git a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextTraversalTest.java b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextTraversalTest.java
index 517b71a..17c7ebd 100644
--- a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextTraversalTest.java
+++ b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextTraversalTest.java
@@ -17,6 +17,7 @@
import android.os.Bundle;
import android.test.suitebuilder.annotation.MediumTest;
import android.text.Selection;
+import android.text.TextUtils;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -86,7 +87,7 @@
(event.getEventType() ==
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
&& event.getAction() ==
- AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
&& event.getPackageName().equals(getActivity().getPackageName())
&& event.getClassName().equals(View.class.getName())
&& event.getContentDescription().toString().equals(
@@ -94,7 +95,7 @@
&& event.getFromIndex() == 0
&& event.getToIndex() == 1
&& event.getMovementGranularity() ==
- AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
}
}, TIMEOUT_ASYNC_PROCESSING);
@@ -116,7 +117,7 @@
(event.getEventType() ==
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
&& event.getAction() ==
- AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
&& event.getPackageName().equals(getActivity().getPackageName())
&& event.getClassName().equals(View.class.getName())
&& event.getContentDescription().toString().equals(
@@ -124,7 +125,7 @@
&& event.getFromIndex() == 1
&& event.getToIndex() == 2
&& event.getMovementGranularity() ==
- AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
}
}, TIMEOUT_ASYNC_PROCESSING);
@@ -146,7 +147,7 @@
(event.getEventType() ==
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
&& event.getAction() ==
- AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
&& event.getPackageName().equals(getActivity().getPackageName())
&& event.getClassName().equals(View.class.getName())
&& event.getContentDescription().toString().equals(
@@ -154,7 +155,7 @@
&& event.getFromIndex() == 2
&& event.getToIndex() == 3
&& event.getMovementGranularity() ==
- AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
}
}, TIMEOUT_ASYNC_PROCESSING);
@@ -165,7 +166,37 @@
assertFalse(getInteractionBridge().performAction(text,
AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments));
- // Move to the next character and wait for an event.
+ // Move to the previous character and wait for an event.
+ AccessibilityEvent fourthExpected = getInteractionBridge()
+ .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+ @Override
+ public void run() {
+ getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+ }
+ }, new AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent event) {
+ return
+ (event.getEventType() ==
+ AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+ && event.getAction() ==
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ && event.getPackageName().equals(getActivity().getPackageName())
+ && event.getClassName().equals(View.class.getName())
+ && event.getContentDescription().toString().equals(
+ getString(R.string.a_b))
+ && event.getFromIndex() == 2
+ && event.getToIndex() == 3
+ && event.getMovementGranularity() ==
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+ }
+ }, TIMEOUT_ASYNC_PROCESSING);
+
+ // Make sure we got the expected event.
+ assertNotNull(fourthExpected);
+
+ // Move to the previous character and wait for an event.
AccessibilityEvent fifthExpected = getInteractionBridge()
.executeCommandAndWaitForAccessibilityEvent(new Runnable() {
@Override
@@ -180,7 +211,7 @@
(event.getEventType() ==
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
&& event.getAction() ==
- AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
&& event.getPackageName().equals(getActivity().getPackageName())
&& event.getClassName().equals(View.class.getName())
&& event.getContentDescription().toString().equals(
@@ -188,7 +219,7 @@
&& event.getFromIndex() == 1
&& event.getToIndex() == 2
&& event.getMovementGranularity() ==
- AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
}
}, TIMEOUT_ASYNC_PROCESSING);
@@ -210,7 +241,7 @@
(event.getEventType() ==
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
&& event.getAction() ==
- AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
&& event.getPackageName().equals(getActivity().getPackageName())
&& event.getClassName().equals(View.class.getName())
&& event.getContentDescription().toString().equals(
@@ -218,7 +249,7 @@
&& event.getFromIndex() == 0
&& event.getToIndex() == 1
&& event.getMovementGranularity() ==
- AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
}
}, TIMEOUT_ASYNC_PROCESSING);
@@ -269,7 +300,7 @@
(event.getEventType() ==
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
&& event.getAction() ==
- AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
&& event.getPackageName().equals(getActivity().getPackageName())
&& event.getClassName().equals(View.class.getName())
&& event.getContentDescription().toString().equals(
@@ -277,7 +308,7 @@
&& event.getFromIndex() == 0
&& event.getToIndex() == 3
&& event.getMovementGranularity() ==
- AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
}
}, TIMEOUT_ASYNC_PROCESSING);
@@ -299,7 +330,7 @@
(event.getEventType() ==
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
&& event.getAction() ==
- AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
&& event.getPackageName().equals(getActivity().getPackageName())
&& event.getClassName().equals(View.class.getName())
&& event.getContentDescription().toString().equals(
@@ -307,7 +338,7 @@
&& event.getFromIndex() == 4
&& event.getToIndex() == 7
&& event.getMovementGranularity() ==
- AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
}
}, TIMEOUT_ASYNC_PROCESSING);
@@ -329,7 +360,7 @@
(event.getEventType() ==
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
&& event.getAction() ==
- AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
&& event.getPackageName().equals(getActivity().getPackageName())
&& event.getClassName().equals(View.class.getName())
&& event.getContentDescription().toString().equals(
@@ -337,7 +368,7 @@
&& event.getFromIndex() == 8
&& event.getToIndex() == 11
&& event.getMovementGranularity() ==
- AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
}
}, TIMEOUT_ASYNC_PROCESSING);
@@ -363,7 +394,7 @@
(event.getEventType() ==
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
&& event.getAction() ==
- AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
&& event.getPackageName().equals(getActivity().getPackageName())
&& event.getClassName().equals(View.class.getName())
&& event.getContentDescription().toString().equals(
@@ -371,7 +402,7 @@
&& event.getFromIndex() == 8
&& event.getToIndex() == 11
&& event.getMovementGranularity() ==
- AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
}
}, TIMEOUT_ASYNC_PROCESSING);
@@ -393,7 +424,7 @@
(event.getEventType() ==
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
&& event.getAction() ==
- AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
&& event.getPackageName().equals(getActivity().getPackageName())
&& event.getClassName().equals(View.class.getName())
&& event.getContentDescription().toString().equals(
@@ -401,7 +432,7 @@
&& event.getFromIndex() == 4
&& event.getToIndex() == 7
&& event.getMovementGranularity() ==
- AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
}
}, TIMEOUT_ASYNC_PROCESSING);
@@ -423,7 +454,7 @@
(event.getEventType() ==
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
&& event.getAction() ==
- AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
&& event.getPackageName().equals(getActivity().getPackageName())
&& event.getClassName().equals(View.class.getName())
&& event.getContentDescription().toString().equals(
@@ -431,7 +462,7 @@
&& event.getFromIndex() == 0
&& event.getToIndex() == 3
&& event.getMovementGranularity() ==
- AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
}
}, TIMEOUT_ASYNC_PROCESSING);
@@ -484,7 +515,7 @@
(event.getEventType() ==
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
&& event.getAction() ==
- AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
&& event.getPackageName().equals(getActivity().getPackageName())
&& event.getClassName().equals(TextView.class.getName())
&& event.getText().size() > 0
@@ -492,7 +523,7 @@
&& event.getFromIndex() == 0
&& event.getToIndex() == 1
&& event.getMovementGranularity() ==
- AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
}
}, TIMEOUT_ASYNC_PROCESSING);
@@ -518,7 +549,7 @@
(event.getEventType() ==
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
&& event.getAction() ==
- AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
&& event.getPackageName().equals(getActivity().getPackageName())
&& event.getClassName().equals(TextView.class.getName())
&& event.getText().size() > 0
@@ -526,7 +557,7 @@
&& event.getFromIndex() == 1
&& event.getToIndex() == 2
&& event.getMovementGranularity() ==
- AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
}
}, TIMEOUT_ASYNC_PROCESSING);
@@ -552,7 +583,7 @@
(event.getEventType() ==
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
&& event.getAction() ==
- AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
&& event.getPackageName().equals(getActivity().getPackageName())
&& event.getClassName().equals(TextView.class.getName())
&& event.getText().size() > 0
@@ -560,7 +591,7 @@
&& event.getFromIndex() == 2
&& event.getToIndex() == 3
&& event.getMovementGranularity() ==
- AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
}
}, TIMEOUT_ASYNC_PROCESSING);
@@ -579,7 +610,7 @@
assertEquals(3, Selection.getSelectionStart(textView.getText()));
assertEquals(3, Selection.getSelectionEnd(textView.getText()));
- // Move to the next character and wait for an event.
+ // Move to the previous character and wait for an event.
AccessibilityEvent fifthExpected = getInteractionBridge()
.executeCommandAndWaitForAccessibilityEvent(new Runnable() {
@Override
@@ -594,15 +625,15 @@
(event.getEventType() ==
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
&& event.getAction() ==
- AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
&& event.getPackageName().equals(getActivity().getPackageName())
&& event.getClassName().equals(TextView.class.getName())
&& event.getText().size() > 0
&& event.getText().get(0).toString().equals(getString(R.string.a_b))
- && event.getFromIndex() == 1
- && event.getToIndex() == 2
+ && event.getFromIndex() == 2
+ && event.getToIndex() == 3
&& event.getMovementGranularity() ==
- AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
}
}, TIMEOUT_ASYNC_PROCESSING);
@@ -613,7 +644,7 @@
assertEquals(2, Selection.getSelectionStart(textView.getText()));
assertEquals(2, Selection.getSelectionEnd(textView.getText()));
- // Move to the next character and wait for an event.
+ // Move to the previous character and wait for an event.
AccessibilityEvent sixthExpected = getInteractionBridge()
.executeCommandAndWaitForAccessibilityEvent(new Runnable() {
@Override
@@ -628,15 +659,15 @@
(event.getEventType() ==
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
&& event.getAction() ==
- AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
&& event.getPackageName().equals(getActivity().getPackageName())
&& event.getClassName().equals(TextView.class.getName())
&& event.getText().size() > 0
&& event.getText().get(0).toString().equals(getString(R.string.a_b))
- && event.getFromIndex() == 0
- && event.getToIndex() == 1
+ && event.getFromIndex() == 1
+ && event.getToIndex() == 2
&& event.getMovementGranularity() ==
- AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
}
}, TIMEOUT_ASYNC_PROCESSING);
@@ -647,6 +678,40 @@
assertEquals(1, Selection.getSelectionStart(textView.getText()));
assertEquals(1, Selection.getSelectionEnd(textView.getText()));
+ // Move to the previous character and wait for an event.
+ AccessibilityEvent seventhExpected = getInteractionBridge()
+ .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+ @Override
+ public void run() {
+ getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+ }
+ }, new AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent event) {
+ return
+ (event.getEventType() ==
+ AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+ && event.getAction() ==
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ && event.getPackageName().equals(getActivity().getPackageName())
+ && event.getClassName().equals(TextView.class.getName())
+ && event.getText().size() > 0
+ && event.getText().get(0).toString().equals(getString(R.string.a_b))
+ && event.getFromIndex() == 0
+ && event.getToIndex() == 1
+ && event.getMovementGranularity() ==
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+ }
+ }, TIMEOUT_ASYNC_PROCESSING);
+
+ // Make sure we got the expected event.
+ assertNotNull(seventhExpected);
+
+ // Verify the selection position.
+ assertEquals(0, Selection.getSelectionStart(textView.getText()));
+ assertEquals(0, Selection.getSelectionEnd(textView.getText()));
+
// Make sure there is no previous.
assertFalse(getInteractionBridge().performAction(text,
AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments));
@@ -657,6 +722,284 @@
}
@MediumTest
+ public void testActionNextAndPreviousAtGranularityCharacterOverTextExtend()
+ throws Exception {
+ final EditText editText = (EditText) getActivity().findViewById(R.id.edit);
+
+ getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ editText.setText(getString(R.string.a_b));
+ Selection.removeSelection(editText.getText());
+ }
+ });
+
+ final AccessibilityNodeInfo text = getInteractionBridge()
+ .findAccessibilityNodeInfoByTextFromRoot(getString(R.string.a_b));
+
+ final int granularities = text.getMovementGranularities();
+ assertEquals(granularities, AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
+ | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
+ | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
+ | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
+ | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+
+ final Bundle arguments = new Bundle();
+ arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+ arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN, true);
+
+ // Move to the next character and wait for an event.
+ AccessibilityEvent firstExpected = getInteractionBridge()
+ .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+ @Override
+ public void run() {
+ getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+ }
+ }, new AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent event) {
+ return
+ (event.getEventType() ==
+ AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+ && event.getAction() ==
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ && event.getPackageName().equals(getActivity().getPackageName())
+ && event.getClassName().equals(EditText.class.getName())
+ && event.getText().size() > 0
+ && event.getText().get(0).toString().equals(getString(R.string.a_b))
+ && event.getFromIndex() == 0
+ && event.getToIndex() == 1
+ && event.getMovementGranularity() ==
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+ }
+ }, TIMEOUT_ASYNC_PROCESSING);
+
+ // Make sure we got the expected event.
+ assertNotNull(firstExpected);
+
+ // Verify the selection position.
+ assertEquals(0, Selection.getSelectionStart(editText.getText()));
+ assertEquals(1, Selection.getSelectionEnd(editText.getText()));
+
+ // Move to the next character and wait for an event.
+ AccessibilityEvent secondExpected = getInteractionBridge()
+ .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+ @Override
+ public void run() {
+ getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+ }
+ }, new AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent event) {
+ return
+ (event.getEventType() ==
+ AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+ && event.getAction() ==
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ && event.getPackageName().equals(getActivity().getPackageName())
+ && event.getClassName().equals(EditText.class.getName())
+ && event.getText().size() > 0
+ && event.getText().get(0).toString().equals(getString(R.string.a_b))
+ && event.getFromIndex() == 1
+ && event.getToIndex() == 2
+ && event.getMovementGranularity() ==
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+ }
+ }, TIMEOUT_ASYNC_PROCESSING);
+
+ // Make sure we got the expected event.
+ assertNotNull(secondExpected);
+
+ // Verify the selection position.
+ assertEquals(0, Selection.getSelectionStart(editText.getText()));
+ assertEquals(2, Selection.getSelectionEnd(editText.getText()));
+
+ // Move to the next character and wait for an event.
+ AccessibilityEvent thirdExpected = getInteractionBridge()
+ .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+ @Override
+ public void run() {
+ getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+ }
+ }, new AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent event) {
+ return
+ (event.getEventType() ==
+ AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+ && event.getAction() ==
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ && event.getPackageName().equals(getActivity().getPackageName())
+ && event.getClassName().equals(EditText.class.getName())
+ && event.getText().size() > 0
+ && event.getText().get(0).toString().equals(getString(R.string.a_b))
+ && event.getFromIndex() == 2
+ && event.getToIndex() == 3
+ && event.getMovementGranularity() ==
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+ }
+ }, TIMEOUT_ASYNC_PROCESSING);
+
+ // Make sure we got the expected event.
+ assertNotNull(thirdExpected);
+
+ // Verify the selection position.
+ assertEquals(0, Selection.getSelectionStart(editText.getText()));
+ assertEquals(3, Selection.getSelectionEnd(editText.getText()));
+
+ // Make sure there is no next.
+ assertFalse(getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments));
+
+ // Verify the selection position.
+ assertEquals(0, Selection.getSelectionStart(editText.getText()));
+ assertEquals(3, Selection.getSelectionEnd(editText.getText()));
+
+ // Focus the view so we can change selection.
+ getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ editText.setFocusable(true);
+ editText.requestFocus();
+ }
+ });
+
+ // Put selection at the end of the text.
+ Bundle setSelectionArgs = new Bundle();
+ setSelectionArgs.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 3);
+ setSelectionArgs.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, 3);
+ assertTrue(getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_SET_SELECTION, setSelectionArgs));
+
+ // Verify the selection position.
+ assertEquals(3, Selection.getSelectionStart(editText.getText()));
+ assertEquals(3, Selection.getSelectionEnd(editText.getText()));
+
+ // Unfocus the view so we can get rid of the soft-keyboard.
+ getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ editText.clearFocus();
+ editText.setFocusable(false);
+ }
+ });
+
+ // Move to the previous character and wait for an event.
+ AccessibilityEvent fifthExpected = getInteractionBridge()
+ .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+ @Override
+ public void run() {
+ getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+ }
+ }, new AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent event) {
+ return
+ (event.getEventType() ==
+ AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+ && event.getAction() ==
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ && event.getPackageName().equals(getActivity().getPackageName())
+ && event.getClassName().equals(EditText.class.getName())
+ && event.getText().size() > 0
+ && event.getText().get(0).toString().equals(getString(R.string.a_b))
+ && event.getFromIndex() == 2
+ && event.getToIndex() == 3
+ && event.getMovementGranularity() ==
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+ }
+ }, TIMEOUT_ASYNC_PROCESSING);
+
+ // Make sure we got the expected event.
+ assertNotNull(fifthExpected);
+
+ // Verify the selection position.
+ assertEquals(2, Selection.getSelectionStart(editText.getText()));
+ assertEquals(3, Selection.getSelectionEnd(editText.getText()));
+
+ // Move to the previous character and wait for an event.
+ AccessibilityEvent sixthExpected = getInteractionBridge()
+ .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+ @Override
+ public void run() {
+ getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+ }
+ }, new AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent event) {
+ return
+ (event.getEventType() ==
+ AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+ && event.getAction() ==
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ && event.getPackageName().equals(getActivity().getPackageName())
+ && event.getClassName().equals(EditText.class.getName())
+ && event.getText().size() > 0
+ && event.getText().get(0).toString().equals(getString(R.string.a_b))
+ && event.getFromIndex() == 1
+ && event.getToIndex() == 2
+ && event.getMovementGranularity() ==
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+ }
+ }, TIMEOUT_ASYNC_PROCESSING);
+
+ // Make sure we got the expected event.
+ assertNotNull(sixthExpected);
+
+ // Verify the selection position.
+ assertEquals(1, Selection.getSelectionStart(editText.getText()));
+ assertEquals(3, Selection.getSelectionEnd(editText.getText()));
+
+ // Move to the previous character and wait for an event.
+ AccessibilityEvent seventhExpected = getInteractionBridge()
+ .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+ @Override
+ public void run() {
+ getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+ }
+ }, new AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent event) {
+ return
+ (event.getEventType() ==
+ AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+ && event.getAction() ==
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ && event.getPackageName().equals(getActivity().getPackageName())
+ && event.getClassName().equals(EditText.class.getName())
+ && event.getText().size() > 0
+ && event.getText().get(0).toString().equals(getString(R.string.a_b))
+ && event.getFromIndex() == 0
+ && event.getToIndex() == 1
+ && event.getMovementGranularity() ==
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+ }
+ }, TIMEOUT_ASYNC_PROCESSING);
+
+ // Make sure we got the expected event.
+ assertNotNull(seventhExpected);
+
+ // Verify the selection position.
+ assertEquals(0, Selection.getSelectionStart(editText.getText()));
+ assertEquals(3, Selection.getSelectionEnd(editText.getText()));
+
+ // Make sure there is no previous.
+ assertFalse(getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments));
+
+ // Verify the selection position.
+ assertEquals(0, Selection.getSelectionStart(editText.getText()));
+ assertEquals(3, Selection.getSelectionEnd(editText.getText()));
+ }
+
+ @MediumTest
public void testActionNextAndPreviousAtGranularityWordOverText() throws Exception {
final TextView textView = (TextView) getActivity().findViewById(R.id.text);
@@ -696,7 +1039,7 @@
(event.getEventType() ==
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
&& event.getAction() ==
- AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
&& event.getPackageName().equals(getActivity().getPackageName())
&& event.getClassName().equals(TextView.class.getName())
&& event.getText().size() > 0
@@ -704,7 +1047,7 @@
&& event.getFromIndex() == 0
&& event.getToIndex() == 3
&& event.getMovementGranularity() ==
- AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
}
}, TIMEOUT_ASYNC_PROCESSING);
@@ -730,7 +1073,7 @@
(event.getEventType() ==
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
&& event.getAction() ==
- AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
&& event.getPackageName().equals(getActivity().getPackageName())
&& event.getClassName().equals(TextView.class.getName())
&& event.getText().size() > 0
@@ -738,7 +1081,7 @@
&& event.getFromIndex() == 4
&& event.getToIndex() == 7
&& event.getMovementGranularity() ==
- AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
}
}, TIMEOUT_ASYNC_PROCESSING);
@@ -764,7 +1107,7 @@
(event.getEventType() ==
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
&& event.getAction() ==
- AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
&& event.getPackageName().equals(getActivity().getPackageName())
&& event.getClassName().equals(TextView.class.getName())
&& event.getText().size() > 0
@@ -772,7 +1115,7 @@
&& event.getFromIndex() == 8
&& event.getToIndex() == 11
&& event.getMovementGranularity() ==
- AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
}
}, TIMEOUT_ASYNC_PROCESSING);
@@ -806,7 +1149,7 @@
(event.getEventType() ==
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
&& event.getAction() ==
- AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
&& event.getPackageName().equals(getActivity().getPackageName())
&& event.getClassName().equals(TextView.class.getName())
&& event.getText().size() > 0
@@ -814,7 +1157,7 @@
&& event.getFromIndex() == 8
&& event.getToIndex() == 11
&& event.getMovementGranularity() ==
- AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
}
}, TIMEOUT_ASYNC_PROCESSING);
@@ -840,7 +1183,7 @@
(event.getEventType() ==
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
&& event.getAction() ==
- AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
&& event.getPackageName().equals(getActivity().getPackageName())
&& event.getClassName().equals(TextView.class.getName())
&& event.getText().size() > 0
@@ -848,7 +1191,7 @@
&& event.getFromIndex() == 4
&& event.getToIndex() == 7
&& event.getMovementGranularity() ==
- AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
}
}, TIMEOUT_ASYNC_PROCESSING);
@@ -874,7 +1217,7 @@
(event.getEventType() ==
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
&& event.getAction() ==
- AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
&& event.getPackageName().equals(getActivity().getPackageName())
&& event.getClassName().equals(TextView.class.getName())
&& event.getText().size() > 0
@@ -882,7 +1225,7 @@
&& event.getFromIndex() == 0
&& event.getToIndex() == 3
&& event.getMovementGranularity() ==
- AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
}
}, TIMEOUT_ASYNC_PROCESSING);
@@ -903,6 +1246,279 @@
}
@MediumTest
+ public void testActionNextAndPreviousAtGranularityWordOverTextExtend() throws Exception {
+ final EditText editText = (EditText) getActivity().findViewById(R.id.edit);
+
+ getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ editText.setText(getString(R.string.foo_bar_baz));
+ Selection.removeSelection(editText.getText());
+ }
+ });
+
+ final AccessibilityNodeInfo text = getInteractionBridge()
+ .findAccessibilityNodeInfoByTextFromRoot(getString(R.string.foo_bar_baz));
+
+ final int granularities = text.getMovementGranularities();
+ assertEquals(granularities, AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
+ | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
+ | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
+ | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
+ | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+
+ final Bundle arguments = new Bundle();
+ arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+ arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN, true);
+
+ // Move to the next word and wait for an event.
+ AccessibilityEvent firstExpected = getInteractionBridge()
+ .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+ @Override
+ public void run() {
+ getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+ }
+ }, new AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent event) {
+ return
+ (event.getEventType() ==
+ AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+ && event.getAction() ==
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ && event.getPackageName().equals(getActivity().getPackageName())
+ && event.getClassName().equals(EditText.class.getName())
+ && event.getText().size() > 0
+ && event.getText().get(0).toString().equals(getString(R.string.foo_bar_baz))
+ && event.getFromIndex() == 0
+ && event.getToIndex() == 3
+ && event.getMovementGranularity() ==
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+ }
+ }, TIMEOUT_ASYNC_PROCESSING);
+
+ // Make sure we got the expected event.
+ assertNotNull(firstExpected);
+
+ // Verify the selection position.
+ assertEquals(0, Selection.getSelectionStart(editText.getText()));
+ assertEquals(3, Selection.getSelectionEnd(editText.getText()));
+
+ // Move to the next word and wait for an event.
+ AccessibilityEvent secondExpected = getInteractionBridge()
+ .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+ @Override
+ public void run() {
+ getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+ }
+ }, new AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent event) {
+ return
+ (event.getEventType() ==
+ AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+ && event.getAction() ==
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ && event.getPackageName().equals(getActivity().getPackageName())
+ && event.getClassName().equals(EditText.class.getName())
+ && event.getText().size() > 0
+ && event.getText().get(0).toString().equals(getString(R.string.foo_bar_baz))
+ && event.getFromIndex() == 4
+ && event.getToIndex() == 7
+ && event.getMovementGranularity() ==
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+ }
+ }, TIMEOUT_ASYNC_PROCESSING);
+
+ // Make sure we got the expected event.
+ assertNotNull(secondExpected);
+
+ // Verify the selection position.
+ assertEquals(0, Selection.getSelectionStart(editText.getText()));
+ assertEquals(7, Selection.getSelectionEnd(editText.getText()));
+
+ // Move to the next word and wait for an event.
+ AccessibilityEvent thirdExpected = getInteractionBridge()
+ .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+ @Override
+ public void run() {
+ getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+ }
+ }, new AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent event) {
+ return
+ (event.getEventType() ==
+ AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+ && event.getAction() ==
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ && event.getPackageName().equals(getActivity().getPackageName())
+ && event.getClassName().equals(EditText.class.getName())
+ && event.getText().size() > 0
+ && event.getText().get(0).toString().equals(getString(R.string.foo_bar_baz))
+ && event.getFromIndex() == 8
+ && event.getToIndex() == 11
+ && event.getMovementGranularity() ==
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+ }
+ }, TIMEOUT_ASYNC_PROCESSING);
+
+ // Make sure we got the expected event.
+ assertNotNull(thirdExpected);
+
+ // Verify the selection position.
+ assertEquals(0, Selection.getSelectionStart(editText.getText()));
+ assertEquals(11, Selection.getSelectionEnd(editText.getText()));
+
+ // Make sure there is no next.
+ assertFalse(getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments));
+
+ // Focus the view so we can change selection.
+ getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ editText.setFocusable(true);
+ editText.requestFocus();
+ }
+ });
+
+ // Put selection at the end of the text.
+ Bundle setSelectionArgs = new Bundle();
+ setSelectionArgs.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 11);
+ setSelectionArgs.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, 11);
+ assertTrue(getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_SET_SELECTION, setSelectionArgs));
+
+ // Verify the selection position.
+ assertEquals(11, Selection.getSelectionStart(editText.getText()));
+ assertEquals(11, Selection.getSelectionEnd(editText.getText()));
+
+ // Unfocus the view so we can get rid of the soft-keyboard.
+ getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ editText.clearFocus();
+ editText.setFocusable(false);
+ }
+ });
+
+ // Move to the next word and wait for an event.
+ AccessibilityEvent fourthExpected = getInteractionBridge()
+ .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+ @Override
+ public void run() {
+ getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+ }
+ }, new AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent event) {
+ return
+ (event.getEventType() ==
+ AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+ && event.getAction() ==
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ && event.getPackageName().equals(getActivity().getPackageName())
+ && event.getClassName().equals(EditText.class.getName())
+ && event.getText().size() > 0
+ && event.getText().get(0).toString().equals(getString(R.string.foo_bar_baz))
+ && event.getFromIndex() == 8
+ && event.getToIndex() == 11
+ && event.getMovementGranularity() ==
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+ }
+ }, TIMEOUT_ASYNC_PROCESSING);
+
+ // Make sure we got the expected event.
+ assertNotNull(fourthExpected);
+
+ // Verify the selection position.
+ assertEquals(8, Selection.getSelectionStart(editText.getText()));
+ assertEquals(11, Selection.getSelectionEnd(editText.getText()));
+
+ // Move to the next word and wait for an event.
+ AccessibilityEvent fifthExpected = getInteractionBridge()
+ .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+ @Override
+ public void run() {
+ getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+ }
+ }, new AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent event) {
+ return
+ (event.getEventType() ==
+ AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+ && event.getAction() ==
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ && event.getPackageName().equals(getActivity().getPackageName())
+ && event.getClassName().equals(EditText.class.getName())
+ && event.getText().size() > 0
+ && event.getText().get(0).toString().equals(getString(R.string.foo_bar_baz))
+ && event.getFromIndex() == 4
+ && event.getToIndex() == 7
+ && event.getMovementGranularity() ==
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+ }
+ }, TIMEOUT_ASYNC_PROCESSING);
+
+ // Make sure we got the expected event.
+ assertNotNull(fifthExpected);
+
+ // Verify the selection position.
+ assertEquals(4, Selection.getSelectionStart(editText.getText()));
+ assertEquals(11, Selection.getSelectionEnd(editText.getText()));
+
+ // Move to the next character and wait for an event.
+ AccessibilityEvent sixthExpected = getInteractionBridge()
+ .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+ @Override
+ public void run() {
+ getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+ }
+ }, new AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent event) {
+ return
+ (event.getEventType() ==
+ AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+ && event.getAction() ==
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ && event.getPackageName().equals(getActivity().getPackageName())
+ && event.getClassName().equals(EditText.class.getName())
+ && event.getText().size() > 0
+ && event.getText().get(0).toString().equals(getString(R.string.foo_bar_baz))
+ && event.getFromIndex() == 0
+ && event.getToIndex() == 3
+ && event.getMovementGranularity() ==
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
+ }
+ }, TIMEOUT_ASYNC_PROCESSING);
+
+ // Make sure we got the expected event.
+ assertNotNull(sixthExpected);
+
+ // Verify the selection position.
+ assertEquals(0, Selection.getSelectionStart(editText.getText()));
+ assertEquals(11, Selection.getSelectionEnd(editText.getText()));
+
+ // Make sure there is no previous.
+ assertFalse(getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments));
+
+ // Verify the selection position.
+ assertEquals(0, Selection.getSelectionStart(editText.getText()));
+ assertEquals(11, Selection.getSelectionEnd(editText.getText()));
+ }
+
+ @MediumTest
public void testActionNextAndPreviousAtGranularityLineOverText() throws Exception {
final TextView textView = (TextView) getActivity().findViewById(R.id.text);
@@ -942,7 +1558,7 @@
(event.getEventType() ==
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
&& event.getAction() ==
- AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
&& event.getPackageName().equals(getActivity().getPackageName())
&& event.getClassName().equals(TextView.class.getName())
&& event.getText().size() > 0
@@ -951,7 +1567,7 @@
&& event.getFromIndex() == 0
&& event.getToIndex() == 25
&& event.getMovementGranularity() ==
- AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
}
}, TIMEOUT_ASYNC_PROCESSING);
@@ -977,7 +1593,7 @@
(event.getEventType() ==
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
&& event.getAction() ==
- AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
&& event.getPackageName().equals(getActivity().getPackageName())
&& event.getClassName().equals(TextView.class.getName())
&& event.getText().size() > 0
@@ -986,7 +1602,7 @@
&& event.getFromIndex() == 25
&& event.getToIndex() == 53
&& event.getMovementGranularity() ==
- AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
}
}, TIMEOUT_ASYNC_PROCESSING);
@@ -1012,7 +1628,7 @@
(event.getEventType() ==
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
&& event.getAction() ==
- AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
&& event.getPackageName().equals(getActivity().getPackageName())
&& event.getClassName().equals(TextView.class.getName())
&& event.getText().size() > 0
@@ -1021,7 +1637,7 @@
&& event.getFromIndex() == 53
&& event.getToIndex() == 60
&& event.getMovementGranularity() ==
- AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
}
}, TIMEOUT_ASYNC_PROCESSING);
@@ -1055,7 +1671,7 @@
(event.getEventType() ==
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
&& event.getAction() ==
- AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
&& event.getPackageName().equals(getActivity().getPackageName())
&& event.getClassName().equals(TextView.class.getName())
&& event.getText().size() > 0
@@ -1064,7 +1680,7 @@
&& event.getFromIndex() == 53
&& event.getToIndex() == 60
&& event.getMovementGranularity() ==
- AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
}
}, TIMEOUT_ASYNC_PROCESSING);
@@ -1090,7 +1706,7 @@
(event.getEventType() ==
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
&& event.getAction() ==
- AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
&& event.getPackageName().equals(getActivity().getPackageName())
&& event.getClassName().equals(TextView.class.getName())
&& event.getText().size() > 0
@@ -1099,7 +1715,7 @@
&& event.getFromIndex() == 25
&& event.getToIndex() == 53
&& event.getMovementGranularity() ==
- AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
}
}, TIMEOUT_ASYNC_PROCESSING);
@@ -1125,7 +1741,7 @@
(event.getEventType() ==
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
&& event.getAction() ==
- AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
&& event.getPackageName().equals(getActivity().getPackageName())
&& event.getClassName().equals(TextView.class.getName())
&& event.getText().size() > 0
@@ -1134,7 +1750,7 @@
&& event.getFromIndex() == 0
&& event.getToIndex() == 25
&& event.getMovementGranularity() ==
- AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
}
}, TIMEOUT_ASYNC_PROCESSING);
@@ -1155,6 +1771,281 @@
}
@MediumTest
+ public void testActionNextAndPreviousAtGranularityLineOverTextExtend() throws Exception {
+ final EditText editText = (EditText) getActivity().findViewById(R.id.edit);
+
+ getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ editText.setText(getString(R.string.android_wiki_short));
+ Selection.removeSelection(editText.getText());
+ }
+ });
+
+ final AccessibilityNodeInfo text = getInteractionBridge()
+ .findAccessibilityNodeInfoByTextFromRoot(getString(R.string.android_wiki_short));
+
+ final int granularities = text.getMovementGranularities();
+ assertEquals(granularities, AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
+ | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
+ | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
+ | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
+ | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+
+ final Bundle arguments = new Bundle();
+ arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
+ arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN, true);
+
+ // Move to the next line and wait for an event.
+ AccessibilityEvent firstExpected = getInteractionBridge()
+ .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+ @Override
+ public void run() {
+ getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+ }
+ }, new AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent event) {
+ return
+ (event.getEventType() ==
+ AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+ && event.getAction() ==
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ && event.getPackageName().equals(getActivity().getPackageName())
+ && event.getClassName().equals(EditText.class.getName())
+ && event.getText().size() > 0
+ && event.getText().get(0).toString().equals(getString(
+ R.string.android_wiki_short))
+ && event.getFromIndex() == 0
+ && event.getToIndex() == 19
+ && event.getMovementGranularity() ==
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
+ }
+ }, TIMEOUT_ASYNC_PROCESSING);
+
+ // Make sure we got the expected event.
+ assertNotNull(firstExpected);
+
+ // Verify the selection position.
+ assertEquals(0, Selection.getSelectionStart(editText.getText()));
+ assertEquals(19, Selection.getSelectionEnd(editText.getText()));
+
+ // Move to the next line and wait for an event.
+ AccessibilityEvent secondExpected = getInteractionBridge()
+ .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+ @Override
+ public void run() {
+ getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+ }
+ }, new AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent event) {
+ return
+ (event.getEventType() ==
+ AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+ && event.getAction() ==
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ && event.getPackageName().equals(getActivity().getPackageName())
+ && event.getClassName().equals(EditText.class.getName())
+ && event.getText().size() > 0
+ && event.getText().get(0).toString().equals(getString(
+ R.string.android_wiki_short))
+ && event.getFromIndex() == 19
+ && event.getToIndex() == 35
+ && event.getMovementGranularity() ==
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
+ }
+ }, TIMEOUT_ASYNC_PROCESSING);
+
+ // Make sure we got the expected event.
+ assertNotNull(secondExpected);
+
+ // Verify the selection position.
+ assertEquals(0, Selection.getSelectionStart(editText.getText()));
+ assertEquals(35, Selection.getSelectionEnd(editText.getText()));
+
+ // Move to the next line and wait for an event.
+ AccessibilityEvent thirdExpected = getInteractionBridge()
+ .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+ @Override
+ public void run() {
+ getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+ }
+ }, new AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent event) {
+ return
+ (event.getEventType() ==
+ AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+ && event.getAction() ==
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ && event.getPackageName().equals(getActivity().getPackageName())
+ && event.getClassName().equals(EditText.class.getName())
+ && event.getText().size() > 0
+ && event.getText().get(0).toString().equals(getString(
+ R.string.android_wiki_short))
+ && event.getFromIndex() == 35
+ && event.getToIndex() == 53
+ && event.getMovementGranularity() ==
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
+ }
+ }, TIMEOUT_ASYNC_PROCESSING);
+
+ // Make sure we got the expected event.
+ assertNotNull(thirdExpected);
+
+ // Verify the selection position.
+ assertEquals(0, Selection.getSelectionStart(editText.getText()));
+ assertEquals(53, Selection.getSelectionEnd(editText.getText()));
+
+ // Focus the view so we can change selection.
+ getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ editText.setFocusable(true);
+ editText.requestFocus();
+ }
+ });
+
+ // Put selection at the end of the text.
+ Bundle setSelectionArgs = new Bundle();
+ setSelectionArgs.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 53);
+ setSelectionArgs.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, 53);
+ assertTrue(getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_SET_SELECTION, setSelectionArgs));
+
+ // Verify the selection position.
+ assertEquals(53, Selection.getSelectionStart(editText.getText()));
+ assertEquals(53, Selection.getSelectionEnd(editText.getText()));
+
+ // Unocus the view so we can hide the keyboard.
+ getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ editText.clearFocus();
+ editText.setFocusable(false);
+ }
+ });
+
+ // Move to the previous line and wait for an event.
+ AccessibilityEvent fourthExpected = getInteractionBridge()
+ .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+ @Override
+ public void run() {
+ getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+ }
+ }, new AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent event) {
+ return
+ (event.getEventType() ==
+ AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+ && event.getAction() ==
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ && event.getPackageName().equals(getActivity().getPackageName())
+ && event.getClassName().equals(EditText.class.getName())
+ && event.getText().size() > 0
+ && event.getText().get(0).toString().equals(getString(
+ R.string.android_wiki_short))
+ && event.getFromIndex() == 35
+ && event.getToIndex() == 53
+ && event.getMovementGranularity() ==
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
+ }
+ }, TIMEOUT_ASYNC_PROCESSING);
+
+ // Make sure we got the expected event.
+ assertNotNull(fourthExpected);
+
+ // Verify the selection position.
+ assertEquals(35, Selection.getSelectionStart(editText.getText()));
+ assertEquals(53, Selection.getSelectionEnd(editText.getText()));
+
+ // Move to the previous line and wait for an event.
+ AccessibilityEvent fifthExpected = getInteractionBridge()
+ .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+ @Override
+ public void run() {
+ getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+ }
+ }, new AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent event) {
+ return
+ (event.getEventType() ==
+ AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+ && event.getAction() ==
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ && event.getPackageName().equals(getActivity().getPackageName())
+ && event.getClassName().equals(EditText.class.getName())
+ && event.getText().size() > 0
+ && event.getText().get(0).toString().equals(getString(
+ R.string.android_wiki_short))
+ && event.getFromIndex() == 19
+ && event.getToIndex() == 35
+ && event.getMovementGranularity() ==
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
+ }
+ }, TIMEOUT_ASYNC_PROCESSING);
+
+ // Make sure we got the expected event.
+ assertNotNull(fifthExpected);
+
+ // Verify the selection position.
+ assertEquals(19, Selection.getSelectionStart(editText.getText()));
+ assertEquals(53, Selection.getSelectionEnd(editText.getText()));
+
+ // Move to the previous line and wait for an event.
+ AccessibilityEvent sixthExpected = getInteractionBridge()
+ .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+ @Override
+ public void run() {
+ getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+ }
+ }, new AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent event) {
+ return
+ (event.getEventType() ==
+ AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+ && event.getAction() ==
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ && event.getPackageName().equals(getActivity().getPackageName())
+ && event.getClassName().equals(EditText.class.getName())
+ && event.getText().size() > 0
+ && event.getText().get(0).toString().equals(getString(
+ R.string.android_wiki_short))
+ && event.getFromIndex() == 0
+ && event.getToIndex() == 19
+ && event.getMovementGranularity() ==
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
+ }
+ }, TIMEOUT_ASYNC_PROCESSING);
+
+ // Make sure we got the expected event.
+ assertNotNull(sixthExpected);
+
+ // Verify the selection position.
+ assertEquals(0, Selection.getSelectionStart(editText.getText()));
+ assertEquals(53, Selection.getSelectionEnd(editText.getText()));
+
+ // Make sure there is no previous.
+ assertFalse(getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments));
+
+ // Verify the selection position.
+ assertEquals(0, Selection.getSelectionStart(editText.getText()));
+ assertEquals(53, Selection.getSelectionEnd(editText.getText()));
+ }
+
+ @MediumTest
public void testActionNextAndPreviousAtGranularityPageOverText() throws Exception {
final EditText editText = (EditText) getActivity().findViewById(R.id.edit);
@@ -1195,7 +2086,7 @@
(event.getEventType() ==
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
&& event.getAction() ==
- AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
&& event.getPackageName().equals(getActivity().getPackageName())
&& event.getClassName().equals(EditText.class.getName())
&& event.getText().size() > 0
@@ -1204,7 +2095,7 @@
&& event.getFromIndex() == 0
&& event.getToIndex() == 139
&& event.getMovementGranularity() ==
- AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
}
}, TIMEOUT_ASYNC_PROCESSING);
@@ -1230,7 +2121,7 @@
(event.getEventType() ==
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
&& event.getAction() ==
- AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
&& event.getPackageName().equals(getActivity().getPackageName())
&& event.getClassName().equals(EditText.class.getName())
&& event.getText().size() > 0
@@ -1239,7 +2130,7 @@
&& event.getFromIndex() == 139
&& event.getToIndex() == 285
&& event.getMovementGranularity() ==
- AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
}
}, TIMEOUT_ASYNC_PROCESSING);
@@ -1265,7 +2156,7 @@
(event.getEventType() ==
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
&& event.getAction() ==
- AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
&& event.getPackageName().equals(getActivity().getPackageName())
&& event.getClassName().equals(EditText.class.getName())
&& event.getText().size() > 0
@@ -1274,7 +2165,7 @@
&& event.getFromIndex() == 285
&& event.getToIndex() == 436
&& event.getMovementGranularity() ==
- AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
}
}, TIMEOUT_ASYNC_PROCESSING);
@@ -1308,7 +2199,7 @@
(event.getEventType() ==
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
&& event.getAction() ==
- AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
&& event.getPackageName().equals(getActivity().getPackageName())
&& event.getClassName().equals(EditText.class.getName())
&& event.getText().size() > 0
@@ -1317,7 +2208,7 @@
&& event.getFromIndex() == 285
&& event.getToIndex() == 436
&& event.getMovementGranularity() ==
- AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
}
}, TIMEOUT_ASYNC_PROCESSING);
@@ -1343,7 +2234,7 @@
(event.getEventType() ==
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
&& event.getAction() ==
- AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
&& event.getPackageName().equals(getActivity().getPackageName())
&& event.getClassName().equals(EditText.class.getName())
&& event.getText().size() > 0
@@ -1352,7 +2243,7 @@
&& event.getFromIndex() == 139
&& event.getToIndex() == 285
&& event.getMovementGranularity() ==
- AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
}
}, TIMEOUT_ASYNC_PROCESSING);
@@ -1378,7 +2269,7 @@
(event.getEventType() ==
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
&& event.getAction() ==
- AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
&& event.getPackageName().equals(getActivity().getPackageName())
&& event.getClassName().equals(EditText.class.getName())
&& event.getText().size() > 0
@@ -1387,7 +2278,7 @@
&& event.getFromIndex() == 0
&& event.getToIndex() == 139
&& event.getMovementGranularity() ==
- AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
}
}, TIMEOUT_ASYNC_PROCESSING);
@@ -1408,6 +2299,285 @@
}
@MediumTest
+ public void testActionNextAndPreviousAtGranularityPageOverTextExtend() throws Exception {
+ final EditText editText = (EditText) getActivity().findViewById(R.id.edit);
+
+ getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ editText.setText(getString(R.string.android_wiki));
+ Selection.removeSelection(editText.getText());
+ }
+ });
+
+ final AccessibilityNodeInfo text = getInteractionBridge()
+ .findAccessibilityNodeInfoByTextFromRoot(getString(R.string.android_wiki));
+
+ final int granularities = text.getMovementGranularities();
+ assertEquals(granularities, AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
+ | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
+ | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
+ | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
+ | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+
+ final Bundle arguments = new Bundle();
+ arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+ arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN, true);
+
+ // Move to the next page and wait for an event.
+ AccessibilityEvent firstExpected = getInteractionBridge()
+ .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+ @Override
+ public void run() {
+ getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+ }
+ }, new AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent event) {
+ return
+ (event.getEventType() ==
+ AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+ && event.getAction() ==
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ && event.getPackageName().equals(getActivity().getPackageName())
+ && event.getClassName().equals(EditText.class.getName())
+ && event.getText().size() > 0
+ && event.getText().get(0).toString().equals(getString(
+ R.string.android_wiki))
+ && event.getFromIndex() == 0
+ && event.getToIndex() == 139
+ && event.getMovementGranularity() ==
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+ }
+ }, TIMEOUT_ASYNC_PROCESSING);
+
+ // Make sure we got the expected event.
+ assertNotNull(firstExpected);
+
+ // Verify the selection position.
+ assertEquals(0, Selection.getSelectionStart(editText.getText()));
+ assertEquals(139, Selection.getSelectionEnd(editText.getText()));
+
+ // Move to the next page and wait for an event.
+ AccessibilityEvent secondExpected = getInteractionBridge()
+ .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+ @Override
+ public void run() {
+ getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+ }
+ }, new AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent event) {
+ return
+ (event.getEventType() ==
+ AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+ && event.getAction() ==
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ && event.getPackageName().equals(getActivity().getPackageName())
+ && event.getClassName().equals(EditText.class.getName())
+ && event.getText().size() > 0
+ && event.getText().get(0).toString().equals(getString(
+ R.string.android_wiki))
+ && event.getFromIndex() == 139
+ && event.getToIndex() == 285
+ && event.getMovementGranularity() ==
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+ }
+ }, TIMEOUT_ASYNC_PROCESSING);
+
+ // Make sure we got the expected event.
+ assertNotNull(secondExpected);
+
+ // Verify the selection position.
+ assertEquals(0, Selection.getSelectionStart(editText.getText()));
+ assertEquals(285, Selection.getSelectionEnd(editText.getText()));
+
+ // Move to the next page and wait for an event.
+ AccessibilityEvent thirdExpected = getInteractionBridge()
+ .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+ @Override
+ public void run() {
+ getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+ }
+ }, new AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent event) {
+ return
+ (event.getEventType() ==
+ AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+ && event.getAction() ==
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ && event.getPackageName().equals(getActivity().getPackageName())
+ && event.getClassName().equals(EditText.class.getName())
+ && event.getText().size() > 0
+ && event.getText().get(0).toString().equals(getString(
+ R.string.android_wiki))
+ && event.getFromIndex() == 285
+ && event.getToIndex() == 436
+ && event.getMovementGranularity() ==
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+ }
+ }, TIMEOUT_ASYNC_PROCESSING);
+
+ // Make sure we got the expected event.
+ assertNotNull(thirdExpected);
+
+ // Verify the selection position.
+ assertEquals(0, Selection.getSelectionStart(editText.getText()));
+ assertEquals(436, Selection.getSelectionEnd(editText.getText()));
+
+ // Make sure there is no next.
+ assertFalse(getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments));
+
+ // Focus the view so we can change selection.
+ getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ editText.setFocusable(true);
+ editText.requestFocus();
+ }
+ });
+
+ // Put selection at the end of the text.
+ Bundle setSelectionArgs = new Bundle();
+ setSelectionArgs.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 436);
+ setSelectionArgs.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, 436);
+ assertTrue(getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_SET_SELECTION, setSelectionArgs));
+
+ // Verify the selection position.
+ assertEquals(436, Selection.getSelectionStart(editText.getText()));
+ assertEquals(436, Selection.getSelectionEnd(editText.getText()));
+
+ // Unfocus the view so we can hide the soft-keyboard.
+ getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ editText.clearFocus();
+ editText.setFocusable(false);
+ }
+ });
+
+ // Move to the previous page and wait for an event.
+ AccessibilityEvent fourthExpected = getInteractionBridge()
+ .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+ @Override
+ public void run() {
+ getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+ }
+ }, new AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent event) {
+ return
+ (event.getEventType() ==
+ AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+ && event.getAction() ==
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ && event.getPackageName().equals(getActivity().getPackageName())
+ && event.getClassName().equals(EditText.class.getName())
+ && event.getText().size() > 0
+ && event.getText().get(0).toString().equals(getString(
+ R.string.android_wiki))
+ && event.getFromIndex() == 285
+ && event.getToIndex() == 436
+ && event.getMovementGranularity() ==
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+ }
+ }, TIMEOUT_ASYNC_PROCESSING);
+
+ // Make sure we got the expected event.
+ assertNotNull(fourthExpected);
+
+ // Verify the selection position.
+ assertEquals(285, Selection.getSelectionStart(editText.getText()));
+ assertEquals(436, Selection.getSelectionEnd(editText.getText()));
+
+ // Move to the previous page and wait for an event.
+ AccessibilityEvent fifthExpected = getInteractionBridge()
+ .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+ @Override
+ public void run() {
+ getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+ }
+ }, new AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent event) {
+ return
+ (event.getEventType() ==
+ AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+ && event.getAction() ==
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ && event.getPackageName().equals(getActivity().getPackageName())
+ && event.getClassName().equals(EditText.class.getName())
+ && event.getText().size() > 0
+ && event.getText().get(0).toString().equals(getString(
+ R.string.android_wiki))
+ && event.getFromIndex() == 139
+ && event.getToIndex() == 285
+ && event.getMovementGranularity() ==
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+ }
+ }, TIMEOUT_ASYNC_PROCESSING);
+
+ // Make sure we got the expected event.
+ assertNotNull(fifthExpected);
+
+ // Verify the selection position.
+ assertEquals(139, Selection.getSelectionStart(editText.getText()));
+ assertEquals(436, Selection.getSelectionEnd(editText.getText()));
+
+ // Move to the previous page and wait for an event.
+ AccessibilityEvent sixthExpected = getInteractionBridge()
+ .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+ @Override
+ public void run() {
+ getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+ }
+ }, new AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent event) {
+ return
+ (event.getEventType() ==
+ AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+ && event.getAction() ==
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ && event.getPackageName().equals(getActivity().getPackageName())
+ && event.getClassName().equals(EditText.class.getName())
+ && event.getText().size() > 0
+ && event.getText().get(0).toString().equals(getString(
+ R.string.android_wiki))
+ && event.getFromIndex() == 0
+ && event.getToIndex() == 139
+ && event.getMovementGranularity() ==
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+ }
+ }, TIMEOUT_ASYNC_PROCESSING);
+
+ // Make sure we got the expected event.
+ assertNotNull(sixthExpected);
+
+ // Verify the selection position.
+ assertEquals(0, Selection.getSelectionStart(editText.getText()));
+ assertEquals(436, Selection.getSelectionEnd(editText.getText()));
+
+ // Make sure there is no previous.
+ assertFalse(getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments));
+
+ // Verify the selection position.
+ assertEquals(0, Selection.getSelectionStart(editText.getText()));
+ assertEquals(436, Selection.getSelectionEnd(editText.getText()));
+ }
+
+ @MediumTest
public void testActionNextAndPreviousAtGranularityParagraphOverText() throws Exception {
final TextView textView = (TextView) getActivity().findViewById(R.id.edit);
@@ -1447,7 +2617,7 @@
(event.getEventType() ==
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
&& event.getAction() ==
- AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
&& event.getPackageName().equals(getActivity().getPackageName())
&& event.getClassName().equals(EditText.class.getName())
&& event.getText().size() > 0
@@ -1455,8 +2625,8 @@
R.string.android_wiki_paragraphs))
&& event.getFromIndex() == 2
&& event.getToIndex() == 104
- && event.getMovementGranularity()
- == AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
+ && event.getMovementGranularity() ==
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
}
}, TIMEOUT_ASYNC_PROCESSING);
@@ -1482,7 +2652,7 @@
(event.getEventType() ==
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
&& event.getAction() ==
- AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
&& event.getPackageName().equals(getActivity().getPackageName())
&& event.getClassName().equals(EditText.class.getName())
&& event.getText().size() > 0
@@ -1491,7 +2661,7 @@
&& event.getFromIndex() == 106
&& event.getToIndex() == 267
&& event.getMovementGranularity() ==
- AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
}
}, TIMEOUT_ASYNC_PROCESSING);
@@ -1517,7 +2687,7 @@
(event.getEventType() ==
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
&& event.getAction() ==
- AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
&& event.getPackageName().equals(getActivity().getPackageName())
&& event.getClassName().equals(EditText.class.getName())
&& event.getText().size() > 0
@@ -1526,7 +2696,7 @@
&& event.getFromIndex() == 268
&& event.getToIndex() == 582
&& event.getMovementGranularity() ==
- AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
}
}, TIMEOUT_ASYNC_PROCESSING);
@@ -1560,7 +2730,7 @@
(event.getEventType() ==
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
&& event.getAction() ==
- AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
&& event.getPackageName().equals(getActivity().getPackageName())
&& event.getClassName().equals(EditText.class.getName())
&& event.getText().size() > 0
@@ -1569,7 +2739,7 @@
&& event.getFromIndex() == 268
&& event.getToIndex() == 582
&& event.getMovementGranularity() ==
- AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
}
}, TIMEOUT_ASYNC_PROCESSING);
@@ -1595,7 +2765,7 @@
(event.getEventType() ==
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
&& event.getAction() ==
- AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
&& event.getPackageName().equals(getActivity().getPackageName())
&& event.getClassName().equals(EditText.class.getName())
&& event.getText().size() > 0
@@ -1604,7 +2774,7 @@
&& event.getFromIndex() == 106
&& event.getToIndex() == 267
&& event.getMovementGranularity() ==
- AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
}
}, TIMEOUT_ASYNC_PROCESSING);
@@ -1630,7 +2800,7 @@
(event.getEventType() ==
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
&& event.getAction() ==
- AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
&& event.getPackageName().equals(getActivity().getPackageName())
&& event.getClassName().equals(EditText.class.getName())
&& event.getText().size() > 0
@@ -1638,8 +2808,8 @@
R.string.android_wiki_paragraphs))
&& event.getFromIndex() == 2
&& event.getToIndex() == 104
- && event.getMovementGranularity()
- == AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
+ && event.getMovementGranularity() ==
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
}
}, TIMEOUT_ASYNC_PROCESSING);
@@ -1658,4 +2828,433 @@
assertEquals(2, Selection.getSelectionStart(textView.getText()));
assertEquals(2, Selection.getSelectionEnd(textView.getText()));
}
+
+ @MediumTest
+ public void testActionNextAndPreviousAtGranularityParagraphOverTextExtend() throws Exception {
+ final EditText editText = (EditText) getActivity().findViewById(R.id.edit);
+
+ getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ editText.setText(getString(R.string.android_wiki_paragraphs));
+ Selection.removeSelection(editText.getText());
+ }
+ });
+
+ final AccessibilityNodeInfo text = getInteractionBridge()
+ .findAccessibilityNodeInfoByTextFromRoot(getString(R.string.android_wiki_short));
+
+ final int granularities = text.getMovementGranularities();
+ assertEquals(granularities, AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
+ | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
+ | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
+ | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
+ | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+
+ final Bundle arguments = new Bundle();
+ arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
+ arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN, true);
+
+ // Move to the next paragraph and wait for an event.
+ AccessibilityEvent firstExpected = getInteractionBridge()
+ .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+ @Override
+ public void run() {
+ getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+ }
+ }, new AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent event) {
+ return
+ (event.getEventType() ==
+ AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+ && event.getAction() ==
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ && event.getPackageName().equals(getActivity().getPackageName())
+ && event.getClassName().equals(EditText.class.getName())
+ && event.getText().size() > 0
+ && event.getText().get(0).toString().equals(getString(
+ R.string.android_wiki_paragraphs))
+ && event.getFromIndex() == 2
+ && event.getToIndex() == 104
+ && event.getMovementGranularity() ==
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
+ }
+ }, TIMEOUT_ASYNC_PROCESSING);
+
+ // Make sure we got the expected event.
+ assertNotNull(firstExpected);
+
+ // Verify the selection position.
+ assertEquals(2, Selection.getSelectionStart(editText.getText()));
+ assertEquals(104, Selection.getSelectionEnd(editText.getText()));
+
+ // Move to the next paragraph and wait for an event.
+ AccessibilityEvent secondExpected = getInteractionBridge()
+ .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+ @Override
+ public void run() {
+ getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+ }
+ }, new AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent event) {
+ return
+ (event.getEventType() ==
+ AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+ && event.getAction() ==
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ && event.getPackageName().equals(getActivity().getPackageName())
+ && event.getClassName().equals(EditText.class.getName())
+ && event.getText().size() > 0
+ && event.getText().get(0).toString().equals(getString(
+ R.string.android_wiki_paragraphs))
+ && event.getFromIndex() == 106
+ && event.getToIndex() == 267
+ && event.getMovementGranularity() ==
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
+ }
+ }, TIMEOUT_ASYNC_PROCESSING);
+
+ // Make sure we got the expected event.
+ assertNotNull(secondExpected);
+
+ // Verify the selection position.
+ assertEquals(2, Selection.getSelectionStart(editText.getText()));
+ assertEquals(267, Selection.getSelectionEnd(editText.getText()));
+
+ // Move to the next paragraph and wait for an event.
+ AccessibilityEvent thirdExpected = getInteractionBridge()
+ .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+ @Override
+ public void run() {
+ getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+ }
+ }, new AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent event) {
+ return
+ (event.getEventType() ==
+ AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+ && event.getAction() ==
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ && event.getPackageName().equals(getActivity().getPackageName())
+ && event.getClassName().equals(EditText.class.getName())
+ && event.getText().size() > 0
+ && event.getText().get(0).toString().equals(getString(
+ R.string.android_wiki_paragraphs))
+ && event.getFromIndex() == 268
+ && event.getToIndex() == 582
+ && event.getMovementGranularity() ==
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
+ }
+ }, TIMEOUT_ASYNC_PROCESSING);
+
+ // Make sure we got the expected event.
+ assertNotNull(thirdExpected);
+
+ // Verify the selection position.
+ assertEquals(2, Selection.getSelectionStart(editText.getText()));
+ assertEquals(582, Selection.getSelectionEnd(editText.getText()));
+
+ // Make sure there is no next.
+ assertFalse(getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments));
+
+ // Verify the selection position.
+ assertEquals(2, Selection.getSelectionStart(editText.getText()));
+ assertEquals(582, Selection.getSelectionEnd(editText.getText()));
+
+ // Focus the view so we can change selection.
+ getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ editText.setFocusable(true);
+ editText.requestFocus();
+ }
+ });
+
+ // Put selection at the end of the text.
+ Bundle setSelectionArgs = new Bundle();
+ setSelectionArgs.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 582);
+ setSelectionArgs.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, 582);
+ assertTrue(getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_SET_SELECTION, setSelectionArgs));
+
+ // Verify the selection position.
+ assertEquals(582, Selection.getSelectionStart(editText.getText()));
+ assertEquals(582, Selection.getSelectionEnd(editText.getText()));
+
+ // Unfocus the view so we can get rid of the soft-keyboard.
+ getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ editText.clearFocus();
+ editText.setFocusable(false);
+ }
+ });
+
+ // Move to the previous paragraph and wait for an event.
+ AccessibilityEvent fourthExpected = getInteractionBridge()
+ .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+ @Override
+ public void run() {
+ getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+ }
+ }, new AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent event) {
+ return
+ (event.getEventType() ==
+ AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+ && event.getAction() ==
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ && event.getPackageName().equals(getActivity().getPackageName())
+ && event.getClassName().equals(EditText.class.getName())
+ && event.getText().size() > 0
+ && event.getText().get(0).toString().equals(getString(
+ R.string.android_wiki_paragraphs))
+ && event.getFromIndex() == 268
+ && event.getToIndex() == 582
+ && event.getMovementGranularity() ==
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
+ }
+ }, TIMEOUT_ASYNC_PROCESSING);
+
+ // Make sure we got the expected event.
+ assertNotNull(fourthExpected);
+
+ // Verify the selection position.
+ assertEquals(268, Selection.getSelectionStart(editText.getText()));
+ assertEquals(582, Selection.getSelectionEnd(editText.getText()));
+
+ // Move to the previous paragraph and wait for an event.
+ AccessibilityEvent fifthExpected = getInteractionBridge()
+ .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+ @Override
+ public void run() {
+ getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+ }
+ }, new AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent event) {
+ return
+ (event.getEventType() ==
+ AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+ && event.getAction() ==
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ && event.getPackageName().equals(getActivity().getPackageName())
+ && event.getClassName().equals(EditText.class.getName())
+ && event.getText().size() > 0
+ && event.getText().get(0).toString().equals(getString(
+ R.string.android_wiki_paragraphs))
+ && event.getFromIndex() == 106
+ && event.getToIndex() == 267
+ && event.getMovementGranularity() ==
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
+ }
+ }, TIMEOUT_ASYNC_PROCESSING);
+
+ // Make sure we got the expected event.
+ assertNotNull(fifthExpected);
+
+ // Verify the selection position.
+ assertEquals(106, Selection.getSelectionStart(editText.getText()));
+ assertEquals(582, Selection.getSelectionEnd(editText.getText()));
+
+ // Move to the previous paragraph and wait for an event.
+ AccessibilityEvent sixthExpected = getInteractionBridge()
+ .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+ @Override
+ public void run() {
+ getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments);
+ }
+ }, new AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent event) {
+ return
+ (event.getEventType() ==
+ AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+ && event.getAction() ==
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ && event.getPackageName().equals(getActivity().getPackageName())
+ && event.getClassName().equals(EditText.class.getName())
+ && event.getText().size() > 0
+ && event.getText().get(0).toString().equals(getString(
+ R.string.android_wiki_paragraphs))
+ && event.getFromIndex() == 2
+ && event.getToIndex() == 104
+ && event.getMovementGranularity() ==
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
+ }
+ }, TIMEOUT_ASYNC_PROCESSING);
+
+ // Make sure we got the expected event.
+ assertNotNull(sixthExpected);
+
+ // Verify the selection position.
+ assertEquals(2, Selection.getSelectionStart(editText.getText()));
+ assertEquals(582, Selection.getSelectionEnd(editText.getText()));
+
+ // Make sure there is no previous.
+ assertFalse(getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments));
+
+ // Verify the selection position.
+ assertEquals(2, Selection.getSelectionStart(editText.getText()));
+ assertEquals(582, Selection.getSelectionEnd(editText.getText()));
+ }
+
+ @MediumTest
+ public void testTextEditingActions() throws Exception {
+ final EditText editText = (EditText) getActivity().findViewById(R.id.edit);
+ final String textContent = getString(R.string.foo_bar_baz);
+
+ getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ editText.setFocusable(true);
+ editText.requestFocus();
+ editText.setText(getString(R.string.foo_bar_baz));
+ Selection.removeSelection(editText.getText());
+ }
+ });
+
+ final AccessibilityNodeInfo text = getInteractionBridge()
+ .findAccessibilityNodeInfoByTextFromRoot(getString(R.string.foo_bar_baz));
+
+ // Select all text.
+ Bundle arguments = new Bundle();
+ arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 0);
+ arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT,
+ editText.getText().length());
+ assertTrue(getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_SET_SELECTION, arguments));
+
+ // Copy the selected text.
+ getInteractionBridge().performAction(text, AccessibilityNodeInfo.ACTION_COPY);
+
+ // Set selection at the end.
+ final int textLength = editText.getText().length();
+ // Verify the selection position.
+ assertEquals(textLength, Selection.getSelectionStart(editText.getText()));
+ assertEquals(textLength, Selection.getSelectionEnd(editText.getText()));
+
+ // Paste the selected text.
+ assertTrue(getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_PASTE));
+
+ // Verify the content.
+ assertEquals(editText.getText().toString(), textContent + " " + textContent);
+
+ // Select all text.
+ arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 0);
+ arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT,
+ editText.getText().length());
+ assertTrue(getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_SET_SELECTION, arguments));
+
+ // Cut the selected text.
+ assertTrue(getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_CUT));
+
+ // Verify the content.
+ assertTrue(TextUtils.isEmpty(editText.getText()));
+ }
+
+ public void testSetSelectionInContentDescription() throws Exception {
+ final View view = getActivity().findViewById(R.id.view);
+
+ getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ view.setContentDescription(getString(R.string.foo_bar_baz));
+ }
+ });
+
+ AccessibilityNodeInfo text = getInteractionBridge()
+ .findAccessibilityNodeInfoByTextFromRoot(getString(R.string.foo_bar_baz));
+
+ // Set the cursor position.
+ Bundle arguments = new Bundle();
+ arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 4);
+ arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, 4);
+ assertTrue(getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_SET_SELECTION, arguments));
+
+ // Try and fail to set the selection longer than zero.
+ arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 4);
+ arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, 5);
+ assertFalse(getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_SET_SELECTION, arguments));
+ }
+
+ public void testSelectionPositionForNonEditableView() throws Exception {
+ final View view = getActivity().findViewById(R.id.view);
+
+ getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ view.setContentDescription(getString(R.string.foo_bar_baz));
+ }
+ });
+
+ final AccessibilityNodeInfo text = getInteractionBridge()
+ .findAccessibilityNodeInfoByTextFromRoot(getString(R.string.foo_bar_baz));
+
+ // Check the initial node properties.
+ assertFalse(text.isEditable());
+ assertSame(text.getTextSelectionStart(), -1);
+ assertSame(text.getTextSelectionEnd(), -1);
+
+ // Set the cursor position.
+ getInteractionBridge().executeCommandAndWaitForAccessibilityEvent(new Runnable() {
+ @Override
+ public void run() {
+ Bundle arguments = new Bundle();
+ arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 4);
+ arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, 4);
+ assertTrue(getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_SET_SELECTION, arguments));
+ }
+ }, new AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent event) {
+ return (event.getEventType()
+ == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
+ }
+ }, TIMEOUT_ASYNC_PROCESSING);
+
+ // Refresh the node.
+ AccessibilityNodeInfo refreshedText = getInteractionBridge()
+ .findAccessibilityNodeInfoByTextFromRoot(getString(R.string.foo_bar_baz));
+
+ // Check the related node properties.
+ assertFalse(refreshedText.isEditable());
+ assertSame(refreshedText.getTextSelectionStart(), 4);
+ assertSame(refreshedText.getTextSelectionEnd(), 4);
+
+ // Try to set to an invalid cursor position.
+ Bundle arguments = new Bundle();
+ arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 4);
+ arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, 5);
+ assertFalse(getInteractionBridge().performAction(text,
+ AccessibilityNodeInfo.ACTION_SET_SELECTION, arguments));
+
+ // Refresh the node.
+ refreshedText = getInteractionBridge()
+ .findAccessibilityNodeInfoByTextFromRoot(getString(R.string.foo_bar_baz));
+
+ // Check the related node properties.
+ assertFalse(refreshedText.isEditable());
+ assertSame(refreshedText.getTextSelectionStart(), 4);
+ assertSame(refreshedText.getTextSelectionEnd(), 4);
+ }
}
diff --git a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
index 2b6dab4..f83c35c 100644
--- a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
+++ b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
@@ -304,7 +304,7 @@
// char sequence attributes
assertEquals(button.getPackageName(), source.getPackageName());
assertEquals(button.getClassName(), source.getClassName());
- assertEquals(button.getText(), source.getText());
+ assertEquals(button.getText().toString(), source.getText().toString());
assertSame(button.getContentDescription(), source.getContentDescription());
// boolean attributes
diff --git a/tests/tests/app/Android.mk b/tests/tests/app/Android.mk
index 8d5877d..d4387b4 100644
--- a/tests/tests/app/Android.mk
+++ b/tests/tests/app/Android.mk
@@ -21,7 +21,7 @@
# and when built explicitly put it in the data partition
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common
+LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common voip-common
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
diff --git a/tests/tests/content/src/android/content/cts/ContentProviderTest.java b/tests/tests/content/src/android/content/cts/ContentProviderTest.java
index 2baa904..34b1e8c 100644
--- a/tests/tests/content/src/android/content/cts/ContentProviderTest.java
+++ b/tests/tests/content/src/android/content/cts/ContentProviderTest.java
@@ -249,12 +249,12 @@
IContentProvider iContentProvider = new IContentProvider() {
@Override
- public int bulkInsert(Uri url, ContentValues[] initialValues) {
+ public int bulkInsert(String callingPkg, Uri url, ContentValues[] initialValues) {
return 0;
}
@Override
- public int delete(Uri url, String selection, String[] selectionArgs) {
+ public int delete(String callingPkg, Uri url, String selection, String[] selectionArgs) {
return 0;
}
@@ -264,36 +264,36 @@
}
@Override
- public Uri insert(Uri url, ContentValues initialValues) {
+ public Uri insert(String callingPkg, Uri url, ContentValues initialValues) {
return null;
}
@Override
- public ParcelFileDescriptor openFile(Uri url, String mode) {
+ public ParcelFileDescriptor openFile(String callingPkg, Uri url, String mode) {
return null;
}
@Override
- public AssetFileDescriptor openAssetFile(Uri url, String mode) {
+ public AssetFileDescriptor openAssetFile(String callingPkg, Uri url, String mode) {
return null;
}
@Override
- public ContentProviderResult[] applyBatch(
+ public ContentProviderResult[] applyBatch(String callingPkg,
ArrayList<ContentProviderOperation> operations)
throws RemoteException, OperationApplicationException {
return null;
}
@Override
- public Cursor query(Uri url, String[] projection, String selection,
+ public Cursor query(String callingPkg, Uri url, String[] projection, String selection,
String[] selectionArgs, String sortOrder,
ICancellationSignal cancellationSignal) {
return null;
}
@Override
- public int update(Uri url, ContentValues values, String selection,
+ public int update(String callingPkg, Uri url, ContentValues values, String selection,
String[] selectionArgs) {
return 0;
}
@@ -304,7 +304,7 @@
}
@Override
- public Bundle call(String method, String request, Bundle args) {
+ public Bundle call(String callingPkg, String method, String request, Bundle args) {
return null;
}
@@ -314,7 +314,8 @@
}
@Override
- public AssetFileDescriptor openTypedAssetFile(Uri url, String mimeType, Bundle opts)
+ public AssetFileDescriptor openTypedAssetFile(String callingPkg, Uri url,
+ String mimeType, Bundle opts)
throws RemoteException, FileNotFoundException {
return null;
}
diff --git a/suite/pts/hostTests/browser/browserlauncher/Android.mk b/tests/tests/display/Android.mk
similarity index 66%
copy from suite/pts/hostTests/browser/browserlauncher/Android.mk
copy to tests/tests/display/Android.mk
index 0592017..ec5b40d 100644
--- a/suite/pts/hostTests/browser/browserlauncher/Android.mk
+++ b/tests/tests/display/Android.mk
@@ -13,21 +13,25 @@
# limitations under the License.
LOCAL_PATH:= $(call my-dir)
+
include $(CLEAR_VARS)
# don't include this package in any target
LOCAL_MODULE_TAGS := optional
+# and when built explicitly put it in the data partition
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
LOCAL_JAVA_LIBRARIES := android.test.runner
-LOCAL_STATIC_JAVA_LIBRARIES := ptsutil ctsutil ctstestrunner ctstestserver
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_PACKAGE_NAME := PtsDeviceBrowserLauncher
+LOCAL_PACKAGE_NAME := CtsDisplayTestCases
-LOCAL_SDK_VERSION := 16
+LOCAL_SDK_VERSION := current
-include $(BUILD_PTS_PACKAGE)
+# This test runner sets up/cleans up the device before/after running the tests.
+LOCAL_CTS_TEST_RUNNER := com.android.cts.tradefed.testtype.DisplayTestRunner
-include $(call all-makefiles-under,$(LOCAL_PATH))
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/display/AndroidManifest.xml b/tests/tests/display/AndroidManifest.xml
new file mode 100644
index 0000000..1ddce5a
--- /dev/null
+++ b/tests/tests/display/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cts.display">
+
+ <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ android:targetPackage="com.android.cts.display"
+ android:label="CTS tests of android.view.display"/>
+
+</manifest>
+
diff --git a/tests/tests/display/src/android/display/cts/DisplayTest.java b/tests/tests/display/src/android/display/cts/DisplayTest.java
new file mode 100644
index 0000000..595f7148
--- /dev/null
+++ b/tests/tests/display/src/android/display/cts/DisplayTest.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.display.cts;
+
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.hardware.display.DisplayManager;
+import android.test.AndroidTestCase;
+import android.util.DisplayMetrics;
+import android.view.Display;
+import android.view.WindowManager;
+
+public class DisplayTest extends AndroidTestCase {
+ // This test is called from DisplayTestRunner which brings up an overlay display on the target
+ // device. The overlay display parameters must match the ones defined there which are
+ // 1281x721/214 (wxh/dpi).
+
+ private static final int SECONDARY_DISPLAY_WIDTH = 1281;
+ private static final int SECONDARY_DISPLAY_HEIGHT = 721;
+ private static final int SECONDARY_DISPLAY_DPI = 214;
+ private static final float SCALE_DENSITY_LOWER_BOUND =
+ (float)(SECONDARY_DISPLAY_DPI - 1) / DisplayMetrics.DENSITY_DEFAULT;
+ private static final float SCALE_DENSITY_UPPER_BOUND =
+ (float)(SECONDARY_DISPLAY_DPI + 1) / DisplayMetrics.DENSITY_DEFAULT;
+ // Matches com.android.internal.R.string.display_manager_overlay_display_name.
+ private static final String OVERLAY_DISPLAY_NAME_PREFIX = "Overlay #";
+
+ private DisplayManager mDisplayManager;
+ private WindowManager mWindowManager;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mDisplayManager = (DisplayManager)mContext.getSystemService(Context.DISPLAY_SERVICE);
+ mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
+ }
+
+ private boolean isSecondarySize(Display display) {
+ final Point p = new Point();
+ display.getSize(p);
+ return p.x == SECONDARY_DISPLAY_WIDTH && p.y == SECONDARY_DISPLAY_HEIGHT;
+ }
+
+ private Display getSecondaryDisplay(Display[] displays) {
+ for (Display display : displays) {
+ if (isSecondarySize(display)) {
+ return display;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Verify that the getDisplays method returns both a default and an overlay display.
+ */
+ public void testGetDisplays() {
+ Display[] displays = mDisplayManager.getDisplays();
+ assertNotNull(displays);
+ assertTrue(2 <= displays.length);
+ boolean hasDefaultDisplay = false;
+ boolean hasSecondaryDisplay = false;
+ for (Display display : displays) {
+ if (display.getDisplayId() == Display.DEFAULT_DISPLAY) {
+ hasDefaultDisplay = true;
+ }
+ if (isSecondarySize(display)) {
+ hasSecondaryDisplay = true;
+ }
+ }
+ assertTrue(hasDefaultDisplay);
+ assertTrue(hasSecondaryDisplay);
+ }
+
+ /**
+ * Verify that the WindowManager returns the default display.
+ */
+ public void testDefaultDisplay() {
+ assertEquals(Display.DEFAULT_DISPLAY, mWindowManager.getDefaultDisplay().getDisplayId());
+ }
+
+ /**
+ * Verify that there is a secondary display.
+ */
+ public void testSecondaryDisplay() {
+ Display display = getSecondaryDisplay(mDisplayManager.getDisplays());
+ assertNotNull(display);
+ assertTrue(Display.DEFAULT_DISPLAY != display.getDisplayId());
+ }
+
+ /**
+ * Test the properties of the secondary Display.
+ */
+ public void testGetDisplayAttrs() {
+ Display display = getSecondaryDisplay(mDisplayManager.getDisplays());
+
+ assertEquals(SECONDARY_DISPLAY_WIDTH, display.getWidth());
+ assertEquals(SECONDARY_DISPLAY_HEIGHT, display.getHeight());
+
+ Point outSize = new Point();
+ display.getSize(outSize);
+ assertEquals(SECONDARY_DISPLAY_WIDTH, outSize.x);
+ assertEquals(SECONDARY_DISPLAY_HEIGHT, outSize.y);
+
+ assertEquals(0, display.getOrientation());
+
+ assertEquals(PixelFormat.RGBA_8888, display.getPixelFormat());
+
+ assertTrue(0 < display.getRefreshRate());
+
+ assertTrue(display.getName().contains(OVERLAY_DISPLAY_NAME_PREFIX));
+ }
+
+ /**
+ * Test that the getMetrics method fills in correct values.
+ */
+ public void testGetMetrics() {
+ Display display = getSecondaryDisplay(mDisplayManager.getDisplays());
+
+ Point outSize = new Point();
+ display.getSize(outSize);
+
+ DisplayMetrics outMetrics = new DisplayMetrics();
+ outMetrics.setToDefaults();
+ display.getMetrics(outMetrics);
+
+ assertEquals(SECONDARY_DISPLAY_WIDTH, outMetrics.widthPixels);
+ assertEquals(SECONDARY_DISPLAY_HEIGHT, outMetrics.heightPixels);
+
+ // The scale is in [0.1, 3], and density is the scale factor.
+ assertTrue(SCALE_DENSITY_LOWER_BOUND <= outMetrics.density
+ && outMetrics.density <= SCALE_DENSITY_UPPER_BOUND);
+ assertTrue(SCALE_DENSITY_LOWER_BOUND <= outMetrics.scaledDensity
+ && outMetrics.scaledDensity <= SCALE_DENSITY_UPPER_BOUND);
+
+ assertEquals(SECONDARY_DISPLAY_DPI, outMetrics.densityDpi);
+ assertEquals((float)SECONDARY_DISPLAY_DPI, outMetrics.xdpi);
+ assertEquals((float)SECONDARY_DISPLAY_DPI, outMetrics.ydpi);
+ }
+
+ /**
+ * Test that the getCurrentSizeRange method returns correct values.
+ */
+ public void testGetCurrentSizeRange() {
+ Display display = getSecondaryDisplay(mDisplayManager.getDisplays());
+
+ Point smallest = new Point();
+ Point largest = new Point();
+ display.getCurrentSizeRange(smallest, largest);
+
+ assertEquals(SECONDARY_DISPLAY_WIDTH, smallest.x);
+ assertEquals(SECONDARY_DISPLAY_HEIGHT, smallest.y);
+ assertEquals(SECONDARY_DISPLAY_WIDTH, largest.x);
+ assertEquals(SECONDARY_DISPLAY_HEIGHT, largest.y);
+ }
+
+ /**
+ * Test that the getFlags method returns no flag bits set for the overlay display.
+ */
+ public void testFlags() {
+ Display display = getSecondaryDisplay(mDisplayManager.getDisplays());
+
+ assertEquals(0, display.getFlags());
+ }
+}
diff --git a/suite/pts/hostTests/browser/browserlauncher/Android.mk b/tests/tests/dreams/Android.mk
similarity index 67%
copy from suite/pts/hostTests/browser/browserlauncher/Android.mk
copy to tests/tests/dreams/Android.mk
index 0592017..c630d8a 100644
--- a/suite/pts/hostTests/browser/browserlauncher/Android.mk
+++ b/tests/tests/dreams/Android.mk
@@ -13,21 +13,25 @@
# limitations under the License.
LOCAL_PATH:= $(call my-dir)
+
include $(CLEAR_VARS)
-# don't include this package in any target
+LOCAL_PACKAGE_NAME := CtsDreamsTestCases
+
+# Don't include this package in any target.
LOCAL_MODULE_TAGS := optional
+# When built, explicitly put it in the data partition.
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+# All tests should include android.test.runner.
LOCAL_JAVA_LIBRARIES := android.test.runner
-LOCAL_STATIC_JAVA_LIBRARIES := ptsutil ctsutil ctstestrunner ctstestserver
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_PACKAGE_NAME := PtsDeviceBrowserLauncher
+# Need access to ServiceManager
+#LOCAL_SDK_VERSION := current
-LOCAL_SDK_VERSION := 16
-
-include $(BUILD_PTS_PACKAGE)
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/dreams/AndroidManifest.xml b/tests/tests/dreams/AndroidManifest.xml
new file mode 100644
index 0000000..fb3e564
--- /dev/null
+++ b/tests/tests/dreams/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cts.dreams">
+
+ <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <!-- This is a self-instrumenting test package. -->
+ <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+ android:targetPackage="com.android.cts.dreams"
+ android:label="CTS tests for the android.service.dreams package"/>
+
+</manifest>
+
diff --git a/tests/tests/dreams/src/android/service/dreams/cts/DreamsFeatureTest.java b/tests/tests/dreams/src/android/service/dreams/cts/DreamsFeatureTest.java
new file mode 100644
index 0000000..358a7bf
--- /dev/null
+++ b/tests/tests/dreams/src/android/service/dreams/cts/DreamsFeatureTest.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.dreams.cts;
+
+import android.os.ServiceManager;
+import android.service.dreams.DreamService;
+import android.service.dreams.IDreamManager;
+
+import junit.framework.TestCase;
+
+public class DreamsFeatureTest extends TestCase {
+
+ public void testDreamManagerExists() {
+ IDreamManager service = IDreamManager.Stub.asInterface(
+ ServiceManager.getService(DreamService.DREAM_SERVICE));
+ assertTrue("Dream manager service missing", service != null);
+ }
+
+}
diff --git a/tests/tests/graphics/src/android/graphics/cts/AvoidXfermodeTest.java b/tests/tests/graphics/src/android/graphics/cts/AvoidXfermodeTest.java
index d58336d..beb3621 100644
--- a/tests/tests/graphics/src/android/graphics/cts/AvoidXfermodeTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/AvoidXfermodeTest.java
@@ -62,7 +62,7 @@
assertEquals(Color.GREEN, b.getPixel(BASE_SIZE / 2, BASE_SIZE / 2));
assertEquals(Color.RED, b.getPixel(BASE_SIZE + BASE_SIZE / 2, BASE_SIZE / 2));
- assertEquals(Color.CYAN, b.getPixel(BASE_SIZE / 2, BASE_SIZE + BASE_SIZE / 2));
+ assertEquals(Color.BLUE, b.getPixel(BASE_SIZE / 2, BASE_SIZE + BASE_SIZE / 2));
assertEquals(Color.BLACK, b.getPixel(BASE_SIZE + BASE_SIZE / 2, BASE_SIZE + BASE_SIZE / 2));
}
}
diff --git a/tests/tests/graphics/src/android/graphics/cts/MatrixTest.java b/tests/tests/graphics/src/android/graphics/cts/MatrixTest.java
index ebe20a2..29e4f5f 100644
--- a/tests/tests/graphics/src/android/graphics/cts/MatrixTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/MatrixTest.java
@@ -302,7 +302,7 @@
values[0] = 1000f;
matrix.setValues(values);
assertTrue(mMatrix.invert(matrix));
- String expect = "[1.0, -0.0, 0.0][-0.0, 1.0, 0.0][0.0, 0.0, 1.0]";
+ String expect = "[1.0, 0.0, 0.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0]";
assertEquals(expect, matrix.toShortString());
expect = "[1.0, 0.0, 0.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0]";
assertEquals(expect, mMatrix.toShortString());
diff --git a/tests/tests/hardware/src/android/hardware/cts/CameraTest.java b/tests/tests/hardware/src/android/hardware/cts/CameraTest.java
index bbb5643..31a72cf 100755
--- a/tests/tests/hardware/src/android/hardware/cts/CameraTest.java
+++ b/tests/tests/hardware/src/android/hardware/cts/CameraTest.java
@@ -53,6 +53,8 @@
import java.util.Iterator;
import java.util.List;
+import junit.framework.AssertionFailedError;
+
/**
* This test case must run with hardware. It can't be tested in emulator
*/
@@ -1713,6 +1715,9 @@
// ignore
}
mCamera.stopPreview();
+ // See if any frame duration violations occurred during preview run
+ AssertionFailedError e = callback.getDurationException();
+ if (e != null) throw(e);
}
// Test the invalid fps cases.
@@ -1741,6 +1746,7 @@
// An array storing the arrival time of the frames in the last second.
private ArrayList<Long> mFrames = new ArrayList<Long>();
private long firstFrameArrivalTime;
+ private AssertionFailedError mDurationException = null;
public void reset(double minFps, double maxFps) {
this.mMinFps = minFps;
@@ -1752,6 +1758,7 @@
+ ". Max frame interval=" + mMaxFrameInterval);
mFrames.clear();
firstFrameArrivalTime = 0;
+ mDurationException = null;
}
// This method tests if the actual fps is between minimum and maximum.
@@ -1783,34 +1790,50 @@
// variance is averaged out.
// Check if the frame interval is too large or too small.
- // x100 = percent, intervalMargin should be bigger than fpsMargin considering that
- // fps will be in the order of 10.
+ // x100 = percent, intervalMargin should be bigger than
+ // fpsMargin considering that fps will be in the order of 10.
double intervalMargin = 0.9;
long lastArrivalTime = mFrames.get(mFrames.size() - 1);
double interval = arrivalTime - lastArrivalTime;
if (LOGV) Log.v(TAG, "Frame interval=" + interval);
- assertTrue("Frame interval (" + interval + "ms) is too large." +
- " mMaxFrameInterval=" + mMaxFrameInterval + "ms",
- interval < mMaxFrameInterval * (1.0 + intervalMargin));
- assertTrue("Frame interval (" + interval + "ms) is too small." +
- " mMinFrameInterval=" + mMinFrameInterval + "ms",
- interval > mMinFrameInterval * (1.0 - intervalMargin));
+ try {
+ assertTrue("Frame interval (" + interval + "ms) is too " +
+ "large. mMaxFrameInterval=" +
+ mMaxFrameInterval + "ms",
+ interval < mMaxFrameInterval *
+ (1.0 + intervalMargin));
+ assertTrue("Frame interval (" + interval + "ms) is too " +
+ "small. mMinFrameInterval=" +
+ mMinFrameInterval + "ms",
+ interval > mMinFrameInterval *
+ (1.0 - intervalMargin));
- // Check if the fps is within range.
- double fpsMargin = 0.5; // x100 = percent
- double avgInterval = (double)(arrivalTime - mFrames.get(0))
- / mFrames.size();
- double fps = 1000.0 / avgInterval;
- assertTrue("Actual fps (" + fps + ") should be larger than " +
- "min fps (" + mMinFps + ")",
- fps >= mMinFps * (1.0 - fpsMargin));
- assertTrue("Actual fps (" + fps + ") should be smaller than " +
- "max fps (" + mMaxFps + ")",
- fps <= mMaxFps * (1.0 + fpsMargin));
+ // Check if the fps is within range.
+ double fpsMargin = 0.5; // x100 = percent
+ double avgInterval = (double)(arrivalTime - mFrames.get(0))
+ / mFrames.size();
+ double fps = 1000.0 / avgInterval;
+ assertTrue("Actual fps (" + fps + ") should be larger " +
+ "than min fps (" + mMinFps + ")",
+ fps >= mMinFps * (1.0 - fpsMargin));
+ assertTrue("Actual fps (" + fps + ") should be smaller" +
+ "than max fps (" + mMaxFps + ")",
+ fps <= mMaxFps * (1.0 + fpsMargin));
+ } catch (AssertionFailedError e) {
+ // Need to throw this only in the test body, instead of in
+ // the callback
+ if (mDurationException == null) {
+ mDurationException = e;
+ }
+ }
}
// Add the arrival time of this frame to the list.
mFrames.add(arrivalTime);
}
+
+ public AssertionFailedError getDurationException() {
+ return mDurationException;
+ }
}
private void assertEquals(Size expected, Size actual) {
diff --git a/tests/tests/hardware/src/android/hardware/cts/SensorTest.java b/tests/tests/hardware/src/android/hardware/cts/SensorTest.java
index 574e9e9..c865140 100644
--- a/tests/tests/hardware/src/android/hardware/cts/SensorTest.java
+++ b/tests/tests/hardware/src/android/hardware/cts/SensorTest.java
@@ -16,21 +16,36 @@
package android.hardware.cts;
+import java.lang.IllegalArgumentException;
+import java.lang.Override;
import java.util.List;
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
+import android.hardware.TriggerEvent;
+import android.hardware.TriggerEventListener;
import android.test.AndroidTestCase;
public class SensorTest extends AndroidTestCase {
+ private SensorManager mSensorManager;
+ private TriggerListener mTriggerListener;
+ private SensorListener mSensorListener;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mSensorManager = (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE);
+ mTriggerListener = new TriggerListener();
+ mSensorListener = new SensorListener();
+ }
public void testSensorOperations() {
// Because we can't know every sensors unit details, so we can't assert
// get values with specified values.
- final SensorManager mSensorManager =
- (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE);
List<Sensor> sensors = mSensorManager.getSensorList(Sensor.TYPE_ALL);
assertNotNull(sensors);
Sensor sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
@@ -70,7 +85,48 @@
}
}
- private void assertSensorValues(Sensor sensor) {
+ public void testRequestTriggerWithNonTriggerSensor() {
+ Sensor sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+ boolean result;
+ if (sensor != null) {
+ result = mSensorManager.requestTriggerSensor(mTriggerListener, sensor);
+ assertFalse(result);
+ }
+ }
+
+ public void testCancelTriggerWithNonTriggerSensor() {
+ Sensor sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+ boolean result;
+ if (sensor != null) {
+ result = mSensorManager.cancelTriggerSensor(mTriggerListener, sensor);
+ assertFalse(result);
+ }
+ }
+
+ public void testRegisterWithTriggerSensor() {
+ Sensor sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION);
+ boolean result;
+ if (sensor != null) {
+ result = mSensorManager.registerListener(mSensorListener, sensor,
+ SensorManager.SENSOR_DELAY_NORMAL);
+ assertFalse(result);
+ }
+ }
+
+ public void testRegisterTwiceWithSameSensor() {
+ Sensor sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+ boolean result;
+ if (sensor != null) {
+ result = mSensorManager.registerListener(mSensorListener, sensor,
+ SensorManager.SENSOR_DELAY_NORMAL);
+ assertTrue(result);
+ result = mSensorManager.registerListener(mSensorListener, sensor,
+ SensorManager.SENSOR_DELAY_NORMAL);
+ assertFalse(result);
+ }
+ }
+
+ private void assertSensorValues(Sensor sensor) {
assertTrue(sensor.getMaximumRange() >= 0);
assertTrue(sensor.getPower() >= 0);
assertTrue(sensor.getResolution() >= 0);
@@ -96,4 +152,20 @@
}
assertEquals(sensors, mSensorManager.getSensors());
}
+
+ class TriggerListener extends TriggerEventListener {
+ @Override
+ public void onTrigger(TriggerEvent event) {
+ }
+ }
+
+ class SensorListener implements SensorEventListener {
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ }
+ }
}
diff --git a/tests/tests/location/src/android/location/cts/LocationManagerTest.java b/tests/tests/location/src/android/location/cts/LocationManagerTest.java
index edfda5a..5e823bc 100755
--- a/tests/tests/location/src/android/location/cts/LocationManagerTest.java
+++ b/tests/tests/location/src/android/location/cts/LocationManagerTest.java
@@ -291,7 +291,7 @@
}
/**
- * Helper method to test a location update with given provider
+ * Helper method to test a location update with given mock location provider
*
* @param providerName name of provider to test. Must already exist.
* @throws InterruptedException
@@ -322,6 +322,7 @@
assertEquals(providerName, location.getProvider());
assertEquals(latitude1, location.getLatitude());
assertEquals(longitude1, location.getLongitude());
+ assertEquals(true, location.isFromMockProvider());
// update location without notifying listener
listener.reset();
@@ -373,6 +374,7 @@
assertEquals(TEST_MOCK_PROVIDER_NAME, location.getProvider());
assertEquals(latitude1, location.getLatitude());
assertEquals(longitude1, location.getLongitude());
+ assertEquals(true, location.isFromMockProvider());
// update location without notifying listener
mManager.removeUpdates(listener);
@@ -420,6 +422,7 @@
assertEquals(TEST_MOCK_PROVIDER_NAME, location.getProvider());
assertEquals(latitude1, location.getLatitude());
assertEquals(longitude1, location.getLongitude());
+ assertEquals(true, location.isFromMockProvider());
// update location without receiving broadcast.
mManager.removeUpdates(mPendingIntent);
@@ -509,6 +512,7 @@
assertEquals(TEST_MOCK_PROVIDER_NAME, location.getProvider());
assertEquals(latitude1, location.getLatitude());
assertEquals(longitude1, location.getLongitude());
+ assertEquals(true, location.isFromMockProvider());
mIntentReceiver.clearReceivedIntents();
updateLocation(latitude2, longitude2);
@@ -519,6 +523,7 @@
assertEquals(TEST_MOCK_PROVIDER_NAME, location.getProvider());
assertEquals(latitude2, location.getLatitude());
assertEquals(longitude2, location.getLongitude());
+ assertEquals(true, location.isFromMockProvider());
try {
mManager.getLastKnownLocation(null);
@@ -678,6 +683,7 @@
assertEquals(providerName, location.getProvider());
assertEquals(latitude, location.getLatitude());
assertEquals(longitude, location.getLongitude());
+ assertEquals(true, location.isFromMockProvider());
// Remove the listener.
mManager.removeUpdates(listener);
diff --git a/tests/tests/media/res/raw/video_176x144_yv12.raw b/tests/tests/media/res/raw/video_176x144_yv12.raw
new file mode 100644
index 0000000..23af164
--- /dev/null
+++ b/tests/tests/media/res/raw/video_176x144_yv12.raw
Binary files differ
diff --git a/tests/tests/media/src/android/media/cts/DecodeEditEncodeTest.java b/tests/tests/media/src/android/media/cts/DecodeEditEncodeTest.java
new file mode 100644
index 0000000..8cd1100
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/DecodeEditEncodeTest.java
@@ -0,0 +1,914 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.cts;
+
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecList;
+import android.media.MediaFormat;
+import android.opengl.GLES20;
+import android.test.AndroidTestCase;
+import android.util.Log;
+import android.view.Surface;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import javax.microedition.khronos.opengles.GL10;
+
+
+/**
+ * This test has three steps:
+ * <ol>
+ * <li>Generate a video test stream.
+ * <li>Decode the video from the stream, rendering frames into a SurfaceTexture.
+ * Render the texture onto a Surface that feeds a video encoder, modifying
+ * the output with a fragment shader.
+ * <li>Decode the second video and compare it to the expected result.
+ * </ol><p>
+ * The second step is a typical scenario for video editing. We could do all this in one
+ * step, feeding data through multiple stages of MediaCodec, but at some point we're
+ * no longer exercising the code in the way we expect it to be used (and the code
+ * gets a bit unwieldy).
+ */
+public class DecodeEditEncodeTest extends AndroidTestCase {
+ private static final String TAG = "DecodeEditEncode";
+ private static final boolean WORK_AROUND_BUGS = false; // avoid fatal codec bugs
+ private static final boolean VERBOSE = false; // lots of logging
+ private static final boolean DEBUG_SAVE_FILE = false; // save copy of encoded movie
+ private static final String DEBUG_FILE_NAME_BASE = "/sdcard/test.";
+
+ // parameters for the encoder
+ private static final String MIME_TYPE = "video/avc"; // H.264 Advanced Video Coding
+ private static final int BIT_RATE = 2000000; // 2Mbps
+ private static final int FRAME_RATE = 15; // 15fps
+ private static final int IFRAME_INTERVAL = 10; // 10 seconds between I-frames
+
+ // movie length, in frames
+ private static final int NUM_FRAMES = 30; // two seconds of video
+
+ private static final int TEST_R0 = 0; // dull green background
+ private static final int TEST_G0 = 136;
+ private static final int TEST_B0 = 0;
+ private static final int TEST_R1 = 236; // pink; BT.601 YUV {120,160,200}
+ private static final int TEST_G1 = 50;
+ private static final int TEST_B1 = 186;
+
+ // Replaces TextureRender.FRAGMENT_SHADER during edit; swaps green and blue channels.
+ private static final String FRAGMENT_SHADER =
+ "#extension GL_OES_EGL_image_external : require\n" +
+ "precision mediump float;\n" +
+ "varying vec2 vTextureCoord;\n" +
+ "uniform samplerExternalOES sTexture;\n" +
+ "void main() {\n" +
+ " gl_FragColor = texture2D(sTexture, vTextureCoord).rbga;\n" +
+ "}\n";
+
+ // size of a frame, in pixels
+ private int mWidth = -1;
+ private int mHeight = -1;
+
+
+ public void testVideoEditQCIF() throws Throwable {
+ setSize(176, 144);
+ VideoEditWrapper.runTest(this);
+ }
+ public void testVideoEditQVGA() throws Throwable {
+ setSize(320, 240);
+ VideoEditWrapper.runTest(this);
+ }
+ public void testVideoEdit720p() throws Throwable {
+ setSize(1280, 720);
+ VideoEditWrapper.runTest(this);
+ }
+
+ /**
+ * Wraps testEditVideo, running it in a new thread. Required because of the way
+ * SurfaceTexture.OnFrameAvailableListener works when the current thread has a Looper
+ * configured.
+ */
+ private static class VideoEditWrapper implements Runnable {
+ private Throwable mThrowable;
+ private DecodeEditEncodeTest mTest;
+
+ private VideoEditWrapper(DecodeEditEncodeTest test) {
+ mTest = test;
+ }
+
+ public void run() {
+ try {
+ mTest.videoEditTest();
+ } catch (Throwable th) {
+ mThrowable = th;
+ }
+ }
+
+ /** Entry point. */
+ public static void runTest(DecodeEditEncodeTest obj) throws Throwable {
+ VideoEditWrapper wrapper = new VideoEditWrapper(obj);
+ Thread th = new Thread(wrapper, "codec test");
+ th.start();
+ th.join();
+ if (wrapper.mThrowable != null) {
+ throw wrapper.mThrowable;
+ }
+ }
+ }
+
+ /**
+ * Sets the desired frame size.
+ */
+ private void setSize(int width, int height) {
+ if ((width % 16) != 0 || (height % 16) != 0) {
+ Log.w(TAG, "WARNING: width or height not multiple of 16");
+ }
+ mWidth = width;
+ mHeight = height;
+ }
+
+ /**
+ * Tests editing of a video file with GL.
+ */
+ private void videoEditTest() {
+ VideoChunks sourceChunks = new VideoChunks();
+
+ if (!generateVideoFile(sourceChunks)) {
+ // No AVC codec? Fail silently.
+ return;
+ }
+
+ if (DEBUG_SAVE_FILE) {
+ // Save a copy to a file. We call it ".mp4", but it's actually just an elementary
+ // stream, so not all video players will know what to do with it.
+ String dirName = getContext().getFilesDir().getAbsolutePath();
+ String fileName = "vedit1_" + mWidth + "x" + mHeight + ".mp4";
+ sourceChunks.saveToFile(new File(dirName, fileName));
+ }
+
+ VideoChunks destChunks = editVideoFile(sourceChunks);
+
+ if (DEBUG_SAVE_FILE) {
+ String dirName = getContext().getFilesDir().getAbsolutePath();
+ String fileName = "vedit2_" + mWidth + "x" + mHeight + ".mp4";
+ destChunks.saveToFile(new File(dirName, fileName));
+ }
+
+ checkVideoFile(destChunks);
+ }
+
+ /**
+ * Generates a test video file, saving it as VideoChunks. We generate frames with GL to
+ * avoid having to deal with multiple YUV formats.
+ *
+ * @return true on success, false on "soft" failure
+ */
+ private boolean generateVideoFile(VideoChunks output) {
+ if (VERBOSE) Log.d(TAG, "generateVideoFile " + mWidth + "x" + mHeight);
+ MediaCodec encoder = null;
+ InputSurface inputSurface = null;
+
+ try {
+ MediaCodecInfo codecInfo = selectCodec(MIME_TYPE);
+ if (codecInfo == null) {
+ // Don't fail CTS if they don't have an AVC codec (not here, anyway).
+ Log.e(TAG, "Unable to find an appropriate codec for " + MIME_TYPE);
+ return false;
+ }
+ if (VERBOSE) Log.d(TAG, "found codec: " + codecInfo.getName());
+
+ // We avoid the device-specific limitations on width and height by using values that
+ // are multiples of 16, which all tested devices seem to be able to handle.
+ MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, mWidth, mHeight);
+
+ // Set some properties. Failing to specify some of these can cause the MediaCodec
+ // configure() call to throw an unhelpful exception.
+ format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+ MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
+ format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
+ format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
+ format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
+ if (VERBOSE) Log.d(TAG, "format: " + format);
+ output.setMediaFormat(format);
+
+ // Create a MediaCodec for the desired codec, then configure it as an encoder with
+ // our desired properties.
+ encoder = MediaCodec.createByCodecName(codecInfo.getName());
+ encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+ inputSurface = new InputSurface(encoder.createInputSurface());
+ inputSurface.makeCurrent();
+ encoder.start();
+
+ generateVideoData(encoder, inputSurface, output);
+ } finally {
+ if (encoder != null) {
+ if (VERBOSE) Log.d(TAG, "releasing encoder");
+ encoder.stop();
+ encoder.release();
+ if (VERBOSE) Log.d(TAG, "released encoder");
+ }
+ if (inputSurface != null) {
+ inputSurface.release();
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns the first codec capable of encoding the specified MIME type, or null if no
+ * match was found.
+ */
+ private static MediaCodecInfo selectCodec(String mimeType) {
+ int numCodecs = MediaCodecList.getCodecCount();
+ for (int i = 0; i < numCodecs; i++) {
+ MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
+
+ if (!codecInfo.isEncoder()) {
+ continue;
+ }
+
+ String[] types = codecInfo.getSupportedTypes();
+ for (int j = 0; j < types.length; j++) {
+ if (types[j].equalsIgnoreCase(mimeType)) {
+ return codecInfo;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Generates video frames, feeds them into the encoder, and writes the output to the
+ * VideoChunks instance.
+ */
+ private void generateVideoData(MediaCodec encoder, InputSurface inputSurface,
+ VideoChunks output) {
+ final int TIMEOUT_USEC = 10000;
+ ByteBuffer[] encoderOutputBuffers = encoder.getOutputBuffers();
+ MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+ int generateIndex = 0;
+ int outputCount = 0;
+
+ // Loop until the output side is done.
+ boolean inputDone = false;
+ boolean outputDone = false;
+ while (!outputDone) {
+ if (VERBOSE) Log.d(TAG, "gen loop");
+
+ // If we're not done submitting frames, generate a new one and submit it. The
+ // eglSwapBuffers call will block if the input is full.
+ if (!inputDone) {
+ if (generateIndex == NUM_FRAMES) {
+ // Send an empty frame with the end-of-stream flag set.
+ if (VERBOSE) Log.d(TAG, "signaling input EOS");
+ if (WORK_AROUND_BUGS) {
+ // Might drop a frame, but at least we won't crash mediaserver.
+ try { Thread.sleep(500); } catch (InterruptedException ie) {}
+ outputDone = true;
+ } else {
+ encoder.signalEndOfInputStream();
+ }
+ inputDone = true;
+ } else {
+ generateSurfaceFrame(generateIndex);
+ inputSurface.setPresentationTime(computePresentationTime(generateIndex));
+ if (VERBOSE) Log.d(TAG, "inputSurface swapBuffers");
+ inputSurface.swapBuffers();
+ }
+ generateIndex++;
+ }
+
+ // Check for output from the encoder. If there's no output yet, we either need to
+ // provide more input, or we need to wait for the encoder to work its magic. We
+ // can't actually tell which is the case, so if we can't get an output buffer right
+ // away we loop around and see if it wants more input.
+ //
+ // If we do find output, drain it all before supplying more input.
+ while (true) {
+ int encoderStatus = encoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
+ if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
+ // no output available yet
+ if (VERBOSE) Log.d(TAG, "no output from encoder available");
+ break; // out of while
+ } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+ // not expected for an encoder
+ encoderOutputBuffers = encoder.getOutputBuffers();
+ if (VERBOSE) Log.d(TAG, "encoder output buffers changed");
+ } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+ // not expected for an encoder
+ MediaFormat newFormat = encoder.getOutputFormat();
+ if (VERBOSE) Log.d(TAG, "encoder output format changed: " + newFormat);
+ } else if (encoderStatus < 0) {
+ fail("unexpected result from encoder.dequeueOutputBuffer: " + encoderStatus);
+ } else { // encoderStatus >= 0
+ ByteBuffer encodedData = encoderOutputBuffers[encoderStatus];
+ if (encodedData == null) {
+ fail("encoderOutputBuffer " + encoderStatus + " was null");
+ }
+
+ // Codec config flag must be set iff this is the first chunk of output. This
+ // may not hold for all codecs, but it appears to be the case for video/avc.
+ assertTrue((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0 ||
+ outputCount != 0);
+
+ if (info.size != 0) {
+ // Adjust the ByteBuffer values to match BufferInfo.
+ encodedData.position(info.offset);
+ encodedData.limit(info.offset + info.size);
+
+ output.addChunk(encodedData, info.flags, info.presentationTimeUs);
+ outputCount++;
+ }
+
+ encoder.releaseOutputBuffer(encoderStatus, false);
+ if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+ outputDone = true;
+ break; // out of while
+ }
+ }
+ }
+ }
+
+ // One chunk per frame, plus one for the config data.
+ assertEquals("Frame count", NUM_FRAMES + 1, outputCount);
+ }
+
+ /**
+ * Generates a frame of data using GL commands.
+ * <p>
+ * We have an 8-frame animation sequence that wraps around. It looks like this:
+ * <pre>
+ * 0 1 2 3
+ * 7 6 5 4
+ * </pre>
+ * We draw one of the eight rectangles and leave the rest set to the zero-fill color. */
+ private void generateSurfaceFrame(int frameIndex) {
+ frameIndex %= 8;
+
+ int startX, startY;
+ if (frameIndex < 4) {
+ // (0,0) is bottom-left in GL
+ startX = frameIndex * (mWidth / 4);
+ startY = mHeight / 2;
+ } else {
+ startX = (7 - frameIndex) * (mWidth / 4);
+ startY = 0;
+ }
+
+ GLES20.glDisable(GLES20.GL_SCISSOR_TEST);
+ GLES20.glClearColor(TEST_R0 / 255.0f, TEST_G0 / 255.0f, TEST_B0 / 255.0f, 1.0f);
+ GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+ GLES20.glEnable(GLES20.GL_SCISSOR_TEST);
+ GLES20.glScissor(startX, startY, mWidth / 4, mHeight / 2);
+ GLES20.glClearColor(TEST_R1 / 255.0f, TEST_G1 / 255.0f, TEST_B1 / 255.0f, 1.0f);
+ GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+ }
+
+ /**
+ * Edits a video file, saving the contents to a new file. This involves decoding and
+ * re-encoding, not to mention conversions between YUV and RGB, and so may be lossy.
+ * <p>
+ * If we recognize the decoded format we can do this in Java code using the ByteBuffer[]
+ * output, but it's not practical to support all OEM formats. By using a SurfaceTexture
+ * for output and a Surface for input, we can avoid issues with obscure formats and can
+ * use a fragment shader to do transformations.
+ */
+ private VideoChunks editVideoFile(VideoChunks inputData) {
+ if (VERBOSE) Log.d(TAG, "editVideoFile " + mWidth + "x" + mHeight);
+ VideoChunks outputData = new VideoChunks();
+ MediaCodec decoder = null;
+ MediaCodec encoder = null;
+ InputSurface inputSurface = null;
+ OutputSurface outputSurface = null;
+
+ try {
+ MediaFormat inputFormat = inputData.getMediaFormat();
+
+ // Create an encoder format that matches the input format. (Might be able to just
+ // re-use the format used to generate the video, since we want it to be the same.)
+ MediaFormat outputFormat = MediaFormat.createVideoFormat(MIME_TYPE, mWidth, mHeight);
+ outputFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+ MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
+ outputFormat.setInteger(MediaFormat.KEY_BIT_RATE,
+ inputFormat.getInteger(MediaFormat.KEY_BIT_RATE));
+ outputFormat.setInteger(MediaFormat.KEY_FRAME_RATE,
+ inputFormat.getInteger(MediaFormat.KEY_FRAME_RATE));
+ outputFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL,
+ inputFormat.getInteger(MediaFormat.KEY_I_FRAME_INTERVAL));
+
+ outputData.setMediaFormat(outputFormat);
+
+ encoder = MediaCodec.createEncoderByType(MIME_TYPE);
+ encoder.configure(outputFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+ inputSurface = new InputSurface(encoder.createInputSurface());
+ inputSurface.makeCurrent();
+ encoder.start();
+
+ // OutputSurface uses the EGL context created by InputSurface.
+ decoder = MediaCodec.createDecoderByType(MIME_TYPE);
+ outputSurface = new OutputSurface();
+ outputSurface.changeFragmentShader(FRAGMENT_SHADER);
+ decoder.configure(inputFormat, outputSurface.getSurface(), null, 0);
+ decoder.start();
+
+ editVideoData(inputData, decoder, outputSurface, inputSurface, encoder, outputData);
+ } finally {
+ if (VERBOSE) Log.d(TAG, "shutting down encoder, decoder");
+ if (outputSurface != null) {
+ outputSurface.release();
+ }
+ if (inputSurface != null) {
+ inputSurface.release();
+ }
+ if (encoder != null) {
+ encoder.stop();
+ encoder.release();
+ }
+ if (decoder != null) {
+ decoder.stop();
+ decoder.release();
+ }
+ }
+
+ return outputData;
+ }
+
+ /**
+ * Edits a stream of video data.
+ */
+ private void editVideoData(VideoChunks inputData, MediaCodec decoder,
+ OutputSurface outputSurface, InputSurface inputSurface, MediaCodec encoder,
+ VideoChunks outputData) {
+ final int TIMEOUT_USEC = 10000;
+ ByteBuffer[] decoderInputBuffers = decoder.getInputBuffers();
+ ByteBuffer[] encoderOutputBuffers = encoder.getOutputBuffers();
+ MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+ int inputChunk = 0;
+ int outputCount = 0;
+
+ boolean outputDone = false;
+ boolean inputDone = false;
+ boolean decoderDone = false;
+ while (!outputDone) {
+ if (VERBOSE) Log.d(TAG, "edit loop");
+
+ // Feed more data to the decoder.
+ if (!inputDone) {
+ int inputBufIndex = decoder.dequeueInputBuffer(TIMEOUT_USEC);
+ if (inputBufIndex >= 0) {
+ if (inputChunk == inputData.getNumChunks()) {
+ // End of stream -- send empty frame with EOS flag set.
+ decoder.queueInputBuffer(inputBufIndex, 0, 0, 0L,
+ MediaCodec.BUFFER_FLAG_END_OF_STREAM);
+ inputDone = true;
+ if (VERBOSE) Log.d(TAG, "sent input EOS (with zero-length frame)");
+ } else {
+ // Copy a chunk of input to the decoder. The first chunk should have
+ // the BUFFER_FLAG_CODEC_CONFIG flag set.
+ ByteBuffer inputBuf = decoderInputBuffers[inputBufIndex];
+ inputBuf.clear();
+ inputData.getChunkData(inputChunk, inputBuf);
+ int flags = inputData.getChunkFlags(inputChunk);
+ long time = inputData.getChunkTime(inputChunk);
+ decoder.queueInputBuffer(inputBufIndex, 0, inputBuf.position(),
+ time, flags);
+ if (VERBOSE) {
+ Log.d(TAG, "submitted frame " + inputChunk + " to dec, size=" +
+ inputBuf.position() + " flags=" + flags);
+ }
+ inputChunk++;
+ }
+ } else {
+ if (VERBOSE) Log.d(TAG, "input buffer not available");
+ }
+ }
+
+ // Assume output is available. Loop until both assumptions are false.
+ boolean decoderOutputAvailable = !decoderDone;
+ boolean encoderOutputAvailable = true;
+ while (decoderOutputAvailable || encoderOutputAvailable) {
+ // Start by draining any pending output from the encoder. It's important to
+ // do this before we try to stuff any more data in.
+ int encoderStatus = encoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
+ if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
+ // no output available yet
+ if (VERBOSE) Log.d(TAG, "no output from encoder available");
+ encoderOutputAvailable = false;
+ } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+ encoderOutputBuffers = encoder.getOutputBuffers();
+ if (VERBOSE) Log.d(TAG, "encoder output buffers changed");
+ } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+ MediaFormat newFormat = encoder.getOutputFormat();
+ if (VERBOSE) Log.d(TAG, "encoder output format changed: " + newFormat);
+ } else if (encoderStatus < 0) {
+ fail("unexpected result from encoder.dequeueOutputBuffer: " + encoderStatus);
+ } else { // encoderStatus >= 0
+ ByteBuffer encodedData = encoderOutputBuffers[encoderStatus];
+ if (encodedData == null) {
+ fail("encoderOutputBuffer " + encoderStatus + " was null");
+ }
+
+ // Write the data to the output "file".
+ if (info.size != 0) {
+ encodedData.position(info.offset);
+ encodedData.limit(info.offset + info.size);
+
+ outputData.addChunk(encodedData, info.flags, info.presentationTimeUs);
+ outputCount++;
+
+ if (VERBOSE) Log.d(TAG, "encoder output " + info.size + " bytes");
+ }
+ outputDone = (info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
+ encoder.releaseOutputBuffer(encoderStatus, false);
+ }
+ if (encoderStatus != MediaCodec.INFO_TRY_AGAIN_LATER) {
+ // Continue attempts to drain output.
+ continue;
+ }
+
+ // Encoder is drained, check to see if we've got a new frame of output from
+ // the decoder. (The output is going to a Surface, rather than a ByteBuffer,
+ // but we still get information through BufferInfo.)
+ if (!decoderDone) {
+ int decoderStatus = decoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
+ if (decoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
+ // no output available yet
+ if (VERBOSE) Log.d(TAG, "no output from decoder available");
+ decoderOutputAvailable = false;
+ } else if (decoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+ //decoderOutputBuffers = decoder.getOutputBuffers();
+ if (VERBOSE) Log.d(TAG, "decoder output buffers changed (we don't care)");
+ } else if (decoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+ // expected before first buffer of data
+ MediaFormat newFormat = decoder.getOutputFormat();
+ if (VERBOSE) Log.d(TAG, "decoder output format changed: " + newFormat);
+ } else if (decoderStatus < 0) {
+ fail("unexpected result from decoder.dequeueOutputBuffer: "+decoderStatus);
+ } else { // decoderStatus >= 0
+ if (VERBOSE) Log.d(TAG, "surface decoder given buffer "
+ + decoderStatus + " (size=" + info.size + ")");
+ // The ByteBuffers are null references, but we still get a nonzero
+ // size for the decoded data.
+ boolean doRender = (info.size != 0);
+
+ // As soon as we call releaseOutputBuffer, the buffer will be forwarded
+ // to SurfaceTexture to convert to a texture. The API doesn't
+ // guarantee that the texture will be available before the call
+ // returns, so we need to wait for the onFrameAvailable callback to
+ // fire. If we don't wait, we risk rendering from the previous frame.
+ decoder.releaseOutputBuffer(decoderStatus, doRender);
+ if (doRender) {
+ // This waits for the image and renders it after it arrives.
+ if (VERBOSE) Log.d(TAG, "awaiting frame");
+ outputSurface.awaitNewImage();
+ outputSurface.drawImage();
+
+ // Send it to the encoder.
+ inputSurface.setPresentationTime(info.presentationTimeUs);
+ if (VERBOSE) Log.d(TAG, "swapBuffers");
+ inputSurface.swapBuffers();
+ }
+ if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+ // forward decoder EOS to encoder
+ if (VERBOSE) Log.d(TAG, "signaling input EOS");
+ if (WORK_AROUND_BUGS) {
+ // Bail early, possibly dropping a frame.
+ return;
+ } else {
+ encoder.signalEndOfInputStream();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (inputChunk != outputCount) {
+ throw new RuntimeException("frame lost: " + inputChunk + " in, " +
+ outputCount + " out");
+ }
+ }
+
+ /**
+ * Checks the video file to see if the contents match our expectations. We decode the
+ * video to a Surface and check the pixels with GL.
+ */
+ private void checkVideoFile(VideoChunks inputData) {
+ OutputSurface surface = null;
+ MediaCodec decoder = null;
+
+ if (VERBOSE) Log.d(TAG, "checkVideoFile");
+
+ try {
+ surface = new OutputSurface(mWidth, mHeight);
+
+ MediaFormat format = inputData.getMediaFormat();
+ decoder = MediaCodec.createDecoderByType(MIME_TYPE);
+ decoder.configure(format, surface.getSurface(), null, 0);
+ decoder.start();
+
+ int badFrames = checkVideoData(inputData, decoder, surface);
+ if (badFrames != 0) {
+ fail("Found " + badFrames + " bad frames");
+ }
+ } finally {
+ if (surface != null) {
+ surface.release();
+ }
+ if (decoder != null) {
+ decoder.stop();
+ decoder.release();
+ }
+ }
+ }
+
+ /**
+ * Checks the video data.
+ *
+ * @return the number of bad frames
+ */
+ private int checkVideoData(VideoChunks inputData, MediaCodec decoder, OutputSurface surface) {
+ final int TIMEOUT_USEC = 1000;
+ ByteBuffer[] decoderInputBuffers = decoder.getInputBuffers();
+ ByteBuffer[] decoderOutputBuffers = decoder.getOutputBuffers();
+ MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+ int inputChunk = 0;
+ int checkIndex = 0;
+ int badFrames = 0;
+
+ boolean outputDone = false;
+ boolean inputDone = false;
+ while (!outputDone) {
+ if (VERBOSE) Log.d(TAG, "check loop");
+
+ // Feed more data to the decoder.
+ if (!inputDone) {
+ int inputBufIndex = decoder.dequeueInputBuffer(TIMEOUT_USEC);
+ if (inputBufIndex >= 0) {
+ if (inputChunk == inputData.getNumChunks()) {
+ // End of stream -- send empty frame with EOS flag set.
+ decoder.queueInputBuffer(inputBufIndex, 0, 0, 0L,
+ MediaCodec.BUFFER_FLAG_END_OF_STREAM);
+ inputDone = true;
+ if (VERBOSE) Log.d(TAG, "sent input EOS");
+ } else {
+ // Copy a chunk of input to the decoder. The first chunk should have
+ // the BUFFER_FLAG_CODEC_CONFIG flag set.
+ ByteBuffer inputBuf = decoderInputBuffers[inputBufIndex];
+ inputBuf.clear();
+ inputData.getChunkData(inputChunk, inputBuf);
+ int flags = inputData.getChunkFlags(inputChunk);
+ long time = inputData.getChunkTime(inputChunk);
+ decoder.queueInputBuffer(inputBufIndex, 0, inputBuf.position(),
+ time, flags);
+ if (VERBOSE) {
+ Log.d(TAG, "submitted frame " + inputChunk + " to dec, size=" +
+ inputBuf.position() + " flags=" + flags);
+ }
+ inputChunk++;
+ }
+ } else {
+ if (VERBOSE) Log.d(TAG, "input buffer not available");
+ }
+ }
+
+ if (!outputDone) {
+ int decoderStatus = decoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
+ if (decoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
+ // no output available yet
+ if (VERBOSE) Log.d(TAG, "no output from decoder available");
+ } else if (decoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+ decoderOutputBuffers = decoder.getOutputBuffers();
+ if (VERBOSE) Log.d(TAG, "decoder output buffers changed");
+ } else if (decoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+ MediaFormat newFormat = decoder.getOutputFormat();
+ if (VERBOSE) Log.d(TAG, "decoder output format changed: " + newFormat);
+ } else if (decoderStatus < 0) {
+ fail("unexpected result from decoder.dequeueOutputBuffer: " + decoderStatus);
+ } else { // decoderStatus >= 0
+ ByteBuffer decodedData = decoderOutputBuffers[decoderStatus];
+
+ if (VERBOSE) Log.d(TAG, "surface decoder given buffer " + decoderStatus +
+ " (size=" + info.size + ")");
+ if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+ if (VERBOSE) Log.d(TAG, "output EOS");
+ outputDone = true;
+ }
+
+ boolean doRender = (info.size != 0);
+
+ // As soon as we call releaseOutputBuffer, the buffer will be forwarded
+ // to SurfaceTexture to convert to a texture. The API doesn't guarantee
+ // that the texture will be available before the call returns, so we
+ // need to wait for the onFrameAvailable callback to fire.
+ decoder.releaseOutputBuffer(decoderStatus, doRender);
+ if (doRender) {
+ if (VERBOSE) Log.d(TAG, "awaiting frame " + checkIndex);
+ assertEquals("Wrong time stamp", computePresentationTime(checkIndex),
+ info.presentationTimeUs);
+ surface.awaitNewImage();
+ surface.drawImage();
+ if (!checkSurfaceFrame(checkIndex++)) {
+ badFrames++;
+ }
+ }
+ }
+ }
+ }
+
+ return badFrames;
+ }
+
+ /**
+ * Checks the frame for correctness, using GL to check RGB values.
+ *
+ * @return true if the frame looks good
+ */
+ private boolean checkSurfaceFrame(int frameIndex) {
+ ByteBuffer pixelBuf = ByteBuffer.allocateDirect(4); // TODO - reuse this
+ boolean frameFailed = false;
+
+ for (int i = 0; i < 8; i++) {
+ // Note the coordinates are inverted on the Y-axis in GL.
+ int x, y;
+ if (i < 4) {
+ x = i * (mWidth / 4) + (mWidth / 8);
+ y = (mHeight * 3) / 4;
+ } else {
+ x = (7 - i) * (mWidth / 4) + (mWidth / 8);
+ y = mHeight / 4;
+ }
+
+ GLES20.glReadPixels(x, y, 1, 1, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, pixelBuf);
+ int r = pixelBuf.get(0) & 0xff;
+ int g = pixelBuf.get(1) & 0xff;
+ int b = pixelBuf.get(2) & 0xff;
+ //Log.d(TAG, "GOT(" + frameIndex + "/" + i + "): r=" + r + " g=" + g + " b=" + b);
+
+ int expR, expG, expB;
+ if (i == frameIndex % 8) {
+ // colored rect (green/blue swapped)
+ expR = TEST_R1;
+ expG = TEST_B1;
+ expB = TEST_G1;
+ } else {
+ // zero background color (green/blue swapped)
+ expR = TEST_R0;
+ expG = TEST_B0;
+ expB = TEST_G0;
+ }
+ if (!isColorClose(r, expR) ||
+ !isColorClose(g, expG) ||
+ !isColorClose(b, expB)) {
+ Log.w(TAG, "Bad frame " + frameIndex + " (rect=" + i + ": rgb=" + r +
+ "," + g + "," + b + " vs. expected " + expR + "," + expG +
+ "," + expB + ")");
+ frameFailed = true;
+ }
+ }
+
+ return !frameFailed;
+ }
+
+ /**
+ * Returns true if the actual color value is close to the expected color value.
+ */
+ static boolean isColorClose(int actual, int expected) {
+ final int MAX_DELTA = 8;
+ if (expected < MAX_DELTA) {
+ return actual < (expected + MAX_DELTA);
+ } else if (expected > (255 - MAX_DELTA)) {
+ return actual > (expected - MAX_DELTA);
+ } else {
+ return actual > (expected - MAX_DELTA) && actual < (expected + MAX_DELTA);
+ }
+ }
+
+ /**
+ * Generates the presentation time for frame N.
+ */
+ private static long computePresentationTime(int frameIndex) {
+ return 123 + frameIndex * 1000000 / FRAME_RATE;
+ }
+
+
+ /**
+ * The elementary stream coming out of the "video/avc" encoder needs to be fed back into
+ * the decoder one chunk at a time. If we just wrote the data to a file, we would lose
+ * the information about chunk boundaries. This class stores the encoded data in memory,
+ * retaining the chunk organization.
+ */
+ private static class VideoChunks {
+ private MediaFormat mMediaFormat;
+ private ArrayList<byte[]> mChunks = new ArrayList<byte[]>();
+ private ArrayList<Integer> mFlags = new ArrayList<Integer>();
+ private ArrayList<Long> mTimes = new ArrayList<Long>();
+
+ /**
+ * Sets the MediaFormat, for the benefit of a future decoder.
+ */
+ public void setMediaFormat(MediaFormat format) {
+ mMediaFormat = format;
+ }
+
+ /**
+ * Gets the MediaFormat that was used by the encoder.
+ */
+ public MediaFormat getMediaFormat() {
+ return mMediaFormat;
+ }
+
+ /**
+ * Adds a new chunk. Advances buf.position to buf.limit.
+ */
+ public void addChunk(ByteBuffer buf, int flags, long time) {
+ byte[] data = new byte[buf.remaining()];
+ buf.get(data);
+ mChunks.add(data);
+ mFlags.add(flags);
+ mTimes.add(time);
+ }
+
+ /**
+ * Returns the number of chunks currently held.
+ */
+ public int getNumChunks() {
+ return mChunks.size();
+ }
+
+ /**
+ * Copies the data from chunk N into "dest". Advances dest.position.
+ */
+ public void getChunkData(int chunk, ByteBuffer dest) {
+ byte[] data = mChunks.get(chunk);
+ dest.put(data);
+ }
+
+ /**
+ * Returns the flags associated with chunk N.
+ */
+ public int getChunkFlags(int chunk) {
+ return mFlags.get(chunk);
+ }
+
+ /**
+ * Returns the timestamp associated with chunk N.
+ */
+ public long getChunkTime(int chunk) {
+ return mTimes.get(chunk);
+ }
+
+ /**
+ * Writes the chunks to a file as a contiguous stream. Useful for debugging.
+ */
+ public void saveToFile(File file) {
+ Log.d(TAG, "saving chunk data to file " + file);
+ FileOutputStream fos = null;
+ BufferedOutputStream bos = null;
+
+ try {
+ fos = new FileOutputStream(file);
+ bos = new BufferedOutputStream(fos);
+ fos = null; // closing bos will also close fos
+
+ int numChunks = getNumChunks();
+ for (int i = 0; i < numChunks; i++) {
+ byte[] chunk = mChunks.get(i);
+ bos.write(chunk);
+ }
+ } catch (IOException ioe) {
+ throw new RuntimeException(ioe);
+ } finally {
+ try {
+ if (bos != null) {
+ bos.close();
+ }
+ if (fos != null) {
+ fos.close();
+ }
+ } catch (IOException ioe) {
+ throw new RuntimeException(ioe);
+ }
+ }
+ }
+ }
+}
diff --git a/tests/tests/media/src/android/media/cts/DecoderTest.java b/tests/tests/media/src/android/media/cts/DecoderTest.java
index ac80538..54a670d 100644
--- a/tests/tests/media/src/android/media/cts/DecoderTest.java
+++ b/tests/tests/media/src/android/media/cts/DecoderTest.java
@@ -18,29 +18,28 @@
import com.android.cts.media.R;
-import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.content.res.Resources;
import android.media.MediaCodec;
import android.media.MediaExtractor;
import android.media.MediaFormat;
-import android.test.AndroidTestCase;
import android.util.Log;
+import android.view.Surface;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
-import java.util.Map;
+import java.util.zip.CRC32;
-public class DecoderTest extends AndroidTestCase {
+public class DecoderTest extends MediaPlayerTestBase {
private static final String TAG = "DecoderTest";
private Resources mResources;
@Override
- public void setContext(Context context) {
- super.setContext(context);
+ protected void setUp() throws Exception {
+ super.setUp();
mResources = mContext.getResources();
}
@@ -91,6 +90,7 @@
masterBuffer[i] = (short) sample;
}
bis.close();
+ masterFd.close();
AssetFileDescriptor testFd = mResources.openRawResourceFd(testinput);
@@ -102,6 +102,7 @@
extractor = new MediaExtractor();
extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(),
testFd.getLength());
+ testFd.close();
assertEquals("wrong number of tracks", 1, extractor.getTrackCount());
MediaFormat format = extractor.getTrackFormat(0);
@@ -194,8 +195,6 @@
codec.stop();
codec.release();
- testFd.close();
- masterFd.close();
assertEquals("wrong data size", masterLength, numBytesDecoded);
long avgErrorSquared = (totalErrorSquared / (numBytesDecoded / 2));
@@ -203,5 +202,494 @@
assertTrue("decoding error too big: " + rmse, rmse <= maxerror);
}
+ public void testCodecBasicH264() throws Exception {
+ Surface s = getActivity().getSurfaceHolder().getSurface();
+ int frames1 = countFrames(
+ R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz,
+ false, s);
+ assertEquals("wrong number of frames decoded", 240, frames1);
+
+ int frames2 = countFrames(
+ R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz,
+ false, null);
+ assertEquals("different number of frames when using Surface", frames1, frames2);
+ }
+
+ public void testCodecBasicH263() throws Exception {
+ Surface s = getActivity().getSurfaceHolder().getSurface();
+ int frames1 = countFrames(
+ R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz,
+ false, s);
+ assertEquals("wrong number of frames decoded", 122, frames1);
+
+ int frames2 = countFrames(
+ R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz,
+ false, null);
+ assertEquals("different number of frames when using Surface", frames1, frames2);
+ }
+
+ public void testCodecReconfigH264WithoutSurface() throws Exception {
+ testCodecReconfig(
+ R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz, null);
+ }
+
+ public void testCodecReconfigH264WithSurface() throws Exception {
+ Surface s = getActivity().getSurfaceHolder().getSurface();
+ testCodecReconfig(
+ R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz, s);
+ }
+
+ public void testCodecReconfigH263WithoutSurface() throws Exception {
+ testCodecReconfig(
+ R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz, null);
+ }
+
+ public void testCodecReconfigH263WithSurface() throws Exception {
+ Surface s = getActivity().getSurfaceHolder().getSurface();
+ testCodecReconfig(
+ R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz, s);
+ }
+
+// public void testCodecReconfigOgg() throws Exception {
+// testCodecReconfig(R.raw.sinesweepogg, null);
+// }
+//
+ public void testCodecReconfigMp3() throws Exception {
+ testCodecReconfig(R.raw.sinesweepmp3lame, null);
+ }
+
+ public void testCodecReconfigM4a() throws Exception {
+ testCodecReconfig(R.raw.sinesweepm4a, null);
+ }
+
+ private void testCodecReconfig(int video, Surface s) throws Exception {
+ int frames1 = countFrames(video, false, s);
+ int frames2 = countFrames(video, true, s);
+ assertEquals("different number of frames when reusing codec", frames1, frames2);
+ }
+
+ private int countFrames(int video, boolean reconfigure, Surface s) throws Exception {
+ int numframes = 0;
+
+ AssetFileDescriptor testFd = mResources.openRawResourceFd(video);
+
+ MediaExtractor extractor;
+ MediaCodec codec = null;
+ ByteBuffer[] codecInputBuffers;
+ ByteBuffer[] codecOutputBuffers;
+
+ extractor = new MediaExtractor();
+ extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(),
+ testFd.getLength());
+
+ MediaFormat format = extractor.getTrackFormat(0);
+ String mime = format.getString(MediaFormat.KEY_MIME);
+ boolean isAudio = mime.startsWith("audio/");
+
+ codec = MediaCodec.createDecoderByType(mime);
+// if (mime.contains("avc")) {
+// codec = MediaCodec.createByCodecName("OMX.google.h264.decoder");
+// } else if (mime.contains("3gpp")) {
+// codec = MediaCodec.createByCodecName("OMX.google.h263.decoder");
+// }
+ assertNotNull("couldn't find codec", codec);
+ Log.i("@@@@", "using codec: " + codec.getName());
+ codec.configure(format, s /* surface */, null /* crypto */, 0 /* flags */);
+ codec.start();
+ codecInputBuffers = codec.getInputBuffers();
+ codecOutputBuffers = codec.getOutputBuffers();
+
+ if (reconfigure) {
+ codec.stop();
+ codec.configure(format, s /* surface */, null /* crypto */, 0 /* flags */);
+ codec.start();
+ codecInputBuffers = codec.getInputBuffers();
+ codecOutputBuffers = codec.getOutputBuffers();
+ }
+ Log.i("@@@@", "format: " + format);
+
+ extractor.selectTrack(0);
+
+ // start decoding
+ final long kTimeOutUs = 5000;
+ MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+ boolean sawInputEOS = false;
+ boolean sawOutputEOS = false;
+ int deadDecoderCounter = 0;
+ while (!sawOutputEOS && deadDecoderCounter < 100) {
+ if (!sawInputEOS) {
+ int inputBufIndex = codec.dequeueInputBuffer(kTimeOutUs);
+
+ if (inputBufIndex >= 0) {
+ ByteBuffer dstBuf = codecInputBuffers[inputBufIndex];
+
+ int sampleSize =
+ extractor.readSampleData(dstBuf, 0 /* offset */);
+
+ long presentationTimeUs = 0;
+
+ if (sampleSize < 0) {
+ Log.d(TAG, "saw input EOS.");
+ sawInputEOS = true;
+ sampleSize = 0;
+ } else {
+ presentationTimeUs = extractor.getSampleTime();
+ }
+
+ int flags = extractor.getSampleFlags();
+
+ codec.queueInputBuffer(
+ inputBufIndex,
+ 0 /* offset */,
+ sampleSize,
+ presentationTimeUs,
+ sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
+
+ if (!sawInputEOS) {
+ extractor.advance();
+ }
+ }
+ }
+
+ int res = codec.dequeueOutputBuffer(info, kTimeOutUs);
+
+ deadDecoderCounter++;
+ if (res >= 0) {
+ //Log.d(TAG, "got frame, size " + info.size + "/" + info.presentationTimeUs);
+
+ // Some decoders output a 0-sized buffer at the end. Disregard those.
+ if (info.size > 0) {
+ deadDecoderCounter = 0;
+ if (reconfigure) {
+ // once we've gotten some data out of the decoder, reconfigure it again
+ reconfigure = false;
+ numframes = 0;
+ extractor.seekTo(0, MediaExtractor.SEEK_TO_NEXT_SYNC);
+ sawInputEOS = false;
+ codec.stop();
+ codec.configure(format, s /* surface */, null /* crypto */, 0 /* flags */);
+ codec.start();
+ codecInputBuffers = codec.getInputBuffers();
+ codecOutputBuffers = codec.getOutputBuffers();
+ continue;
+ }
+
+ if (isAudio) {
+ // for audio, count the number of bytes that were decoded, not the number
+ // of access units
+ numframes += info.size;
+ } else {
+ // for video, count the number of video frames
+ numframes++;
+ }
+ }
+ int outputBufIndex = res;
+ codec.releaseOutputBuffer(outputBufIndex, true /* render */);
+
+ if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+ Log.d(TAG, "saw output EOS.");
+ sawOutputEOS = true;
+ }
+ } else if (res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+ codecOutputBuffers = codec.getOutputBuffers();
+
+ Log.d(TAG, "output buffers have changed.");
+ } else if (res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+ MediaFormat oformat = codec.getOutputFormat();
+
+ Log.d(TAG, "output format has changed to " + oformat);
+ } else {
+ Log.d(TAG, "no output");
+ }
+ }
+
+ codec.stop();
+ codec.release();
+ testFd.close();
+ return numframes;
+ }
+
+ public void testEOSBehaviorH264() throws Exception {
+ // this video has an I frame at 44
+ testEOSBehavior(R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz, 44);
+ testEOSBehavior(R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz, 45);
+ testEOSBehavior(R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz, 55);
+ }
+
+ public void testEOSBehaviorH263() throws Exception {
+ // this video has an I frame every 12 frames.
+ testEOSBehavior(R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz, 24);
+ testEOSBehavior(R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz, 25);
+ testEOSBehavior(R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz, 48);
+ testEOSBehavior(R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz, 50);
+ }
+
+ private void testEOSBehavior(int movie, int stopatsample) throws Exception {
+
+ int numframes = 0;
+
+ long [] checksums = new long[stopatsample];
+
+ AssetFileDescriptor testFd = mResources.openRawResourceFd(movie);
+
+ MediaExtractor extractor;
+ MediaCodec codec = null;
+ ByteBuffer[] codecInputBuffers;
+ ByteBuffer[] codecOutputBuffers;
+
+ extractor = new MediaExtractor();
+ extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(),
+ testFd.getLength());
+
+ MediaFormat format = extractor.getTrackFormat(0);
+ String mime = format.getString(MediaFormat.KEY_MIME);
+ boolean isAudio = mime.startsWith("audio/");
+
+ codec = MediaCodec.createDecoderByType(mime);
+// if (mime.contains("avc")) {
+// codec = MediaCodec.createByCodecName("OMX.google.h264.decoder");
+// } else if (mime.contains("3gpp")) {
+// codec = MediaCodec.createByCodecName("OMX.google.h263.decoder");
+// }
+ assertNotNull("couldn't find codec", codec);
+ Log.i("@@@@", "using codec: " + codec.getName());
+ codec.configure(format, null /* surface */, null /* crypto */, 0 /* flags */);
+ codec.start();
+ codecInputBuffers = codec.getInputBuffers();
+ codecOutputBuffers = codec.getOutputBuffers();
+
+ extractor.selectTrack(0);
+
+ // start decoding
+ final long kTimeOutUs = 5000;
+ MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+ boolean sawInputEOS = false;
+ boolean sawOutputEOS = false;
+ int deadDecoderCounter = 0;
+ int samplenum = 0;
+ while (!sawOutputEOS && deadDecoderCounter < 100) {
+ if (!sawInputEOS) {
+ int inputBufIndex = codec.dequeueInputBuffer(kTimeOutUs);
+
+ if (inputBufIndex >= 0) {
+ ByteBuffer dstBuf = codecInputBuffers[inputBufIndex];
+
+ int sampleSize =
+ extractor.readSampleData(dstBuf, 0 /* offset */);
+// Log.i("@@@@", "read sample " + samplenum + ":" + extractor.getSampleFlags()
+// + " @ " + extractor.getSampleTime() + " size " + sampleSize);
+ samplenum++;
+
+ long presentationTimeUs = 0;
+
+ if (sampleSize < 0 || samplenum >= (stopatsample + 100)) {
+ Log.d(TAG, "saw input EOS.");
+ sawInputEOS = true;
+ sampleSize = 0;
+ } else {
+ presentationTimeUs = extractor.getSampleTime();
+ }
+
+ int flags = extractor.getSampleFlags();
+
+ codec.queueInputBuffer(
+ inputBufIndex,
+ 0 /* offset */,
+ sampleSize,
+ presentationTimeUs,
+ sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
+
+ if (!sawInputEOS) {
+ extractor.advance();
+ }
+ }
+ }
+
+ int res = codec.dequeueOutputBuffer(info, kTimeOutUs);
+
+ deadDecoderCounter++;
+ if (res >= 0) {
+
+ // Some decoders output a 0-sized buffer at the end. Disregard those.
+ if (info.size > 0) {
+ deadDecoderCounter = 0;
+
+ if (isAudio) {
+ // for audio, count the number of bytes that were decoded, not the number
+ // of access units
+ numframes += info.size;
+ } else {
+ // for video, count the number of video frames
+ long sum = checksum(codecOutputBuffers[res], info.size);
+ if (numframes < checksums.length) {
+ checksums[numframes] = sum;
+ }
+ numframes++;
+ }
+ }
+// Log.d(TAG, "got frame, size " + info.size + "/" + info.presentationTimeUs +
+// "/" + numframes + "/" + info.flags);
+
+ int outputBufIndex = res;
+ codec.releaseOutputBuffer(outputBufIndex, true /* render */);
+
+ if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+ Log.d(TAG, "saw output EOS.");
+ sawOutputEOS = true;
+ }
+ } else if (res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+ codecOutputBuffers = codec.getOutputBuffers();
+
+ Log.d(TAG, "output buffers have changed.");
+ } else if (res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+ MediaFormat oformat = codec.getOutputFormat();
+
+ Log.d(TAG, "output format has changed to " + oformat);
+ } else {
+ Log.d(TAG, "no output");
+ }
+ }
+
+ codec.stop();
+ codec.release();
+ extractor.release();
+
+
+ // We now have checksums for every frame.
+ // Now decode again, but signal EOS right before an index frame, and ensure the frames
+ // prior to that are the same.
+
+ extractor = new MediaExtractor();
+ extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(),
+ testFd.getLength());
+
+ codec = MediaCodec.createDecoderByType(mime);
+// if (mime.contains("avc")) {
+// codec = MediaCodec.createByCodecName("OMX.google.h264.decoder");
+// } else if (mime.contains("3gpp")) {
+// codec = MediaCodec.createByCodecName("OMX.google.h263.decoder");
+// }
+ codec.configure(format, null /* surface */, null /* crypto */, 0 /* flags */);
+ codec.start();
+ codecInputBuffers = codec.getInputBuffers();
+ codecOutputBuffers = codec.getOutputBuffers();
+
+ extractor.selectTrack(0);
+
+ // start decoding
+ info = new MediaCodec.BufferInfo();
+ sawInputEOS = false;
+ sawOutputEOS = false;
+ deadDecoderCounter = 0;
+ samplenum = 0;
+ numframes = 0;
+ while (!sawOutputEOS && deadDecoderCounter < 100) {
+ if (!sawInputEOS) {
+ int inputBufIndex = codec.dequeueInputBuffer(kTimeOutUs);
+
+ if (inputBufIndex >= 0) {
+ ByteBuffer dstBuf = codecInputBuffers[inputBufIndex];
+
+ int sampleSize =
+ extractor.readSampleData(dstBuf, 0 /* offset */);
+// Log.i("@@@@", "read sample " + samplenum + ":" + extractor.getSampleFlags()
+// + " @ " + extractor.getSampleTime() + " size " + sampleSize);
+ samplenum++;
+
+ long presentationTimeUs = extractor.getSampleTime();
+
+ if (sampleSize < 0 || samplenum >= stopatsample) {
+ Log.d(TAG, "saw input EOS.");
+ sawInputEOS = true;
+ if (sampleSize < 0) {
+ sampleSize = 0;
+ }
+ }
+
+ int flags = extractor.getSampleFlags();
+
+ codec.queueInputBuffer(
+ inputBufIndex,
+ 0 /* offset */,
+ sampleSize,
+ presentationTimeUs,
+ sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
+
+ if (!sawInputEOS) {
+ extractor.advance();
+ }
+ }
+ }
+
+ int res = codec.dequeueOutputBuffer(info, kTimeOutUs);
+
+ deadDecoderCounter++;
+ if (res >= 0) {
+
+ // Some decoders output a 0-sized buffer at the end. Disregard those.
+ if (info.size > 0) {
+ deadDecoderCounter = 0;
+
+ if (isAudio) {
+ // for audio, count the number of bytes that were decoded, not the number
+ // of access units
+ numframes += info.size;
+ } else {
+ // for video, count the number of video frames
+ long sum = checksum(codecOutputBuffers[res], info.size);
+ if (numframes < checksums.length) {
+ assertEquals("frame data mismatch at frame " + numframes,
+ checksums[numframes], sum);
+ }
+ numframes++;
+ }
+ }
+// Log.d(TAG, "got frame, size " + info.size + "/" + info.presentationTimeUs +
+// "/" + numframes + "/" + info.flags);
+
+ int outputBufIndex = res;
+ codec.releaseOutputBuffer(outputBufIndex, true /* render */);
+
+ if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+ Log.d(TAG, "saw output EOS.");
+ sawOutputEOS = true;
+ }
+ } else if (res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+ codecOutputBuffers = codec.getOutputBuffers();
+
+ Log.d(TAG, "output buffers have changed.");
+ } else if (res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+ MediaFormat oformat = codec.getOutputFormat();
+
+ Log.d(TAG, "output format has changed to " + oformat);
+ } else {
+ Log.d(TAG, "no output");
+ }
+ }
+
+ codec.stop();
+ codec.release();
+ extractor.release();
+
+ assertEquals("I!=O", samplenum, numframes);
+ assertTrue("last frame didn't have EOS", sawOutputEOS);
+ assertEquals(stopatsample, numframes);
+
+ testFd.close();
+ }
+
+ private long checksum(ByteBuffer buf, int size) {
+ assertTrue(size != 0);
+ assertTrue(size <= buf.capacity());
+ CRC32 crc = new CRC32();
+ int pos = buf.position();
+ buf.rewind();
+ for (int i = 0; i < buf.capacity(); i++) {
+ crc.update(buf.get());
+ }
+ buf.position(pos);
+ return crc.getValue();
+ }
+
}
diff --git a/tests/tests/media/src/android/media/cts/EncodeDecodeTest.java b/tests/tests/media/src/android/media/cts/EncodeDecodeTest.java
new file mode 100644
index 0000000..c07427c
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/EncodeDecodeTest.java
@@ -0,0 +1,1116 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.cts;
+
+import android.graphics.SurfaceTexture;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecList;
+import android.media.MediaFormat;
+import android.opengl.GLES20;
+import android.test.AndroidTestCase;
+import android.util.Log;
+import android.view.Surface;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+import javax.microedition.khronos.opengles.GL10;
+
+
+/**
+ * Generates a series of video frames, encodes them, decodes them, and tests for significant
+ * divergence from the original.
+ * <p>
+ * We copy the data from the encoder's output buffers to the decoder's input buffers, running
+ * them in parallel. The first buffer output for video/avc contains codec configuration data,
+ * which we must carefully forward to the decoder.
+ * <p>
+ * An alternative approach would be to save the output of the decoder as an mpeg4 video
+ * file, and read it back in from disk. The data we're generating is just an elementary
+ * stream, so we'd need to perform additional steps to make that happen.
+ */
+public class EncodeDecodeTest extends AndroidTestCase {
+ private static final String TAG = "EncodeDecodeTest";
+ private static final boolean VERBOSE = false; // lots of logging
+ private static final boolean DEBUG_SAVE_FILE = false; // save copy of encoded movie
+ private static final String DEBUG_FILE_NAME_BASE = "/sdcard/test.";
+
+ // parameters for the encoder
+ private static final String MIME_TYPE = "video/avc"; // H.264 Advanced Video Coding
+ private static final int BIT_RATE = 2000000; // 2Mbps
+ private static final int FRAME_RATE = 15; // 15fps
+ private static final int IFRAME_INTERVAL = 10; // 10 seconds between I-frames
+
+ // movie length, in frames
+ private static final int NUM_FRAMES = 30; // two seconds of video
+
+ private static final int TEST_Y = 120; // YUV values for colored rect
+ private static final int TEST_U = 160;
+ private static final int TEST_V = 200;
+ private static final int TEST_R0 = 0; // RGB equivalent of {0,0,0}
+ private static final int TEST_G0 = 136;
+ private static final int TEST_B0 = 0;
+ private static final int TEST_R1 = 236; // RGB equivalent of {120,160,200}
+ private static final int TEST_G1 = 50;
+ private static final int TEST_B1 = 186;
+
+ // size of a frame, in pixels
+ private int mWidth = -1;
+ private int mHeight = -1;
+
+
+ /**
+ * Tests streaming of AVC video through the encoder and decoder. Data is encoded from
+ * a series of byte[] buffers and decoded into ByteBuffers. The output is checked for
+ * validity.
+ */
+ public void testEncodeDecodeVideoFromBufferToBufferQCIF() throws Exception {
+ setSize(176, 144);
+ encodeDecodeVideoFromBuffer(false);
+ }
+ public void testEncodeDecodeVideoFromBufferToBufferQVGA() throws Exception {
+ setSize(320, 240);
+ encodeDecodeVideoFromBuffer(false);
+ }
+ public void testEncodeDecodeVideoFromBufferToBuffer720p() throws Exception {
+ setSize(1280, 720);
+ encodeDecodeVideoFromBuffer(false);
+ }
+
+ /**
+ * Tests streaming of AVC video through the encoder and decoder. Data is encoded from
+ * a series of byte[] buffers and decoded into Surfaces. The output is checked for
+ * validity.
+ * <p>
+ * Because of the way SurfaceTexture.OnFrameAvailableListener works, we need to run this
+ * test on a thread that doesn't have a Looper configured. If we don't, the test will
+ * pass, but we won't actually test the output because we'll never receive the "frame
+ * available" notifications". The CTS test framework seems to be configuring a Looper on
+ * the test thread, so we have to hand control off to a new thread for the duration of
+ * the test.
+ */
+ public void testEncodeDecodeVideoFromBufferToSurfaceQCIF() throws Throwable {
+ setSize(176, 144);
+ BufferToSurfaceWrapper.runTest(this);
+ }
+ public void testEncodeDecodeVideoFromBufferToSurfaceQVGA() throws Throwable {
+ setSize(320, 240);
+ BufferToSurfaceWrapper.runTest(this);
+ }
+ public void testEncodeDecodeVideoFromBufferToSurface720p() throws Throwable {
+ setSize(1280, 720);
+ BufferToSurfaceWrapper.runTest(this);
+ }
+
+ /** Wraps testEncodeDecodeVideoFromBuffer(true) */
+ private static class BufferToSurfaceWrapper implements Runnable {
+ private Throwable mThrowable;
+ private EncodeDecodeTest mTest;
+
+ private BufferToSurfaceWrapper(EncodeDecodeTest test) {
+ mTest = test;
+ }
+
+ public void run() {
+ try {
+ mTest.encodeDecodeVideoFromBuffer(true);
+ } catch (Throwable th) {
+ mThrowable = th;
+ }
+ }
+
+ /**
+ * Entry point.
+ */
+ public static void runTest(EncodeDecodeTest obj) throws Throwable {
+ BufferToSurfaceWrapper wrapper = new BufferToSurfaceWrapper(obj);
+ Thread th = new Thread(wrapper, "codec test");
+ th.start();
+ th.join();
+ if (wrapper.mThrowable != null) {
+ throw wrapper.mThrowable;
+ }
+ }
+ }
+
+ /**
+ * Tests streaming of AVC video through the encoder and decoder. Data is provided through
+ * a Surface and decoded onto a Surface. The output is checked for validity.
+ */
+ public void testEncodeDecodeVideoFromSurfaceToSurfaceQCIF() throws Throwable {
+ setSize(176, 144);
+ SurfaceToSurfaceWrapper.runTest(this);
+ }
+ public void testEncodeDecodeVideoFromSurfaceToSurfaceQVGA() throws Throwable {
+ setSize(320, 240);
+ SurfaceToSurfaceWrapper.runTest(this);
+ }
+ public void testEncodeDecodeVideoFromSurfaceToSurface720p() throws Throwable {
+ setSize(1280, 720);
+ SurfaceToSurfaceWrapper.runTest(this);
+ }
+
+ /** Wraps testEncodeDecodeVideoFromSurfaceToSurface() */
+ private static class SurfaceToSurfaceWrapper implements Runnable {
+ private Throwable mThrowable;
+ private EncodeDecodeTest mTest;
+
+ private SurfaceToSurfaceWrapper(EncodeDecodeTest test) {
+ mTest = test;
+ }
+
+ public void run() {
+ try {
+ mTest.encodeDecodeVideoFromSurfaceToSurface();
+ } catch (Throwable th) {
+ mThrowable = th;
+ }
+ }
+
+ /**
+ * Entry point.
+ */
+ public static void runTest(EncodeDecodeTest obj) throws Throwable {
+ SurfaceToSurfaceWrapper wrapper = new SurfaceToSurfaceWrapper(obj);
+ Thread th = new Thread(wrapper, "codec test");
+ th.start();
+ th.join();
+ if (wrapper.mThrowable != null) {
+ throw wrapper.mThrowable;
+ }
+ }
+ }
+
+ /**
+ * Sets the desired frame size.
+ */
+ private void setSize(int width, int height) {
+ if ((width % 16) != 0 || (height % 16) != 0) {
+ Log.w(TAG, "WARNING: width or height not multiple of 16");
+ }
+ mWidth = width;
+ mHeight = height;
+ }
+
+ /**
+ * Tests encoding and subsequently decoding video from frames generated into a buffer.
+ * <p>
+ * We encode several frames of a video test pattern using MediaCodec, then decode the
+ * output with MediaCodec and do some simple checks.
+ * <p>
+ * See http://b.android.com/37769 for a discussion of input format pitfalls.
+ */
+ private void encodeDecodeVideoFromBuffer(boolean toSurface) throws Exception {
+ MediaCodec encoder = null;
+ MediaCodec decoder = null;
+
+ try {
+ MediaCodecInfo codecInfo = selectCodec(MIME_TYPE);
+ if (codecInfo == null) {
+ // Don't fail CTS if they don't have an AVC codec (not here, anyway).
+ Log.e(TAG, "Unable to find an appropriate codec for " + MIME_TYPE);
+ return;
+ }
+ if (VERBOSE) Log.d(TAG, "found codec: " + codecInfo.getName());
+
+ int colorFormat = selectColorFormat(codecInfo, MIME_TYPE);
+ if (VERBOSE) Log.d(TAG, "found colorFormat: " + colorFormat);
+
+ // We avoid the device-specific limitations on width and height by using values that
+ // are multiples of 16, which all tested devices seem to be able to handle.
+ MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, mWidth, mHeight);
+
+ // Set some properties. Failing to specify some of these can cause the MediaCodec
+ // configure() call to throw an unhelpful exception.
+ format.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);
+ format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
+ format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
+ format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
+ if (VERBOSE) Log.d(TAG, "format: " + format);
+
+ // Create a MediaCodec for the desired codec, then configure it as an encoder with
+ // our desired properties.
+ encoder = MediaCodec.createByCodecName(codecInfo.getName());
+ encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+ encoder.start();
+
+ // Create a MediaCodec for the decoder, just based on the MIME type. The various
+ // format details will be passed through the csd-0 meta-data later on.
+ decoder = MediaCodec.createDecoderByType(MIME_TYPE);
+
+ doEncodeDecodeVideoFromBuffer(encoder, colorFormat, decoder, toSurface);
+ } finally {
+ if (VERBOSE) Log.d(TAG, "releasing codecs");
+ if (encoder != null) {
+ encoder.stop();
+ encoder.release();
+ }
+ if (decoder != null) {
+ decoder.stop();
+ decoder.release();
+ }
+ }
+ }
+
+ /**
+ * Tests encoding and subsequently decoding video from frames generated into a buffer.
+ * <p>
+ * We encode several frames of a video test pattern using MediaCodec, then decode the
+ * output with MediaCodec and do some simple checks.
+ */
+ private void encodeDecodeVideoFromSurfaceToSurface() throws Exception {
+ MediaCodec encoder = null;
+ MediaCodec decoder = null;
+ InputSurface inputSurface = null;
+ OutputSurface outputSurface = null;
+
+ try {
+ MediaCodecInfo codecInfo = selectCodec(MIME_TYPE);
+ if (codecInfo == null) {
+ // Don't fail CTS if they don't have an AVC codec (not here, anyway).
+ Log.e(TAG, "Unable to find an appropriate codec for " + MIME_TYPE);
+ return;
+ }
+ if (VERBOSE) Log.d(TAG, "found codec: " + codecInfo.getName());
+
+ int colorFormat = MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface;
+
+ // We avoid the device-specific limitations on width and height by using values that
+ // are multiples of 16, which all tested devices seem to be able to handle.
+ MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, mWidth, mHeight);
+
+ // Set some properties. Failing to specify some of these can cause the MediaCodec
+ // configure() call to throw an unhelpful exception.
+ format.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);
+ format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
+ format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
+ format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
+ if (VERBOSE) Log.d(TAG, "format: " + format);
+
+ // Create the output surface.
+ outputSurface = new OutputSurface(mWidth, mHeight);
+
+ // Create a MediaCodec for the decoder, just based on the MIME type. The various
+ // format details will be passed through the csd-0 meta-data later on.
+ decoder = MediaCodec.createDecoderByType(MIME_TYPE);
+ MediaFormat decoderFormat = MediaFormat.createVideoFormat(MIME_TYPE, mWidth, mHeight);
+ decoder.configure(format, outputSurface.getSurface(), null, 0);
+ decoder.start();
+
+ // Create a MediaCodec for the desired codec, then configure it as an encoder with
+ // our desired properties. Request a Surface to use for input.
+ encoder = MediaCodec.createByCodecName(codecInfo.getName());
+ encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+ inputSurface = new InputSurface(encoder.createInputSurface());
+ encoder.start();
+
+ doEncodeDecodeVideoFromSurfaceToSurface(encoder, inputSurface, colorFormat, decoder, outputSurface);
+ } finally {
+ if (VERBOSE) Log.d(TAG, "releasing codecs");
+ if (inputSurface != null) {
+ inputSurface.release();
+ }
+ if (outputSurface != null) {
+ outputSurface.release();
+ }
+ if (encoder != null) {
+ encoder.stop();
+ encoder.release();
+ }
+ if (decoder != null) {
+ decoder.stop();
+ decoder.release();
+ }
+ }
+ }
+
+ /**
+ * Returns the first codec capable of encoding the specified MIME type, or null if no
+ * match was found.
+ */
+ private static MediaCodecInfo selectCodec(String mimeType) {
+ int numCodecs = MediaCodecList.getCodecCount();
+ for (int i = 0; i < numCodecs; i++) {
+ MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
+
+ if (!codecInfo.isEncoder()) {
+ continue;
+ }
+
+ String[] types = codecInfo.getSupportedTypes();
+ for (int j = 0; j < types.length; j++) {
+ if (types[j].equalsIgnoreCase(mimeType)) {
+ return codecInfo;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns a color format that is supported by the codec and by this test code. If no
+ * match is found, this throws a test failure -- the set of formats known to the test
+ * should be expanded for new platforms.
+ */
+ private static int selectColorFormat(MediaCodecInfo codecInfo, String mimeType) {
+ MediaCodecInfo.CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(mimeType);
+ for (int i = 0; i < capabilities.colorFormats.length; i++) {
+ int colorFormat = capabilities.colorFormats[i];
+ if (isRecognizedFormat(colorFormat)) {
+ return colorFormat;
+ }
+ }
+ fail("couldn't find a good color format for " + codecInfo.getName() + " / " + mimeType);
+ return 0; // not reached
+ }
+
+ /**
+ * Returns true if this is a color format that this test code understands (i.e. we know how
+ * to read and generate frames in this format).
+ */
+ private static boolean isRecognizedFormat(int colorFormat) {
+ switch (colorFormat) {
+ // these are the formats we know how to handle for this test
+ case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar:
+ case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar:
+ case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar:
+ case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar:
+ case MediaCodecInfo.CodecCapabilities.COLOR_TI_FormatYUV420PackedSemiPlanar:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Returns true if the specified color format is semi-planar YUV. Throws an exception
+ * if the color format is not recognized (e.g. not YUV).
+ */
+ private static boolean isSemiPlanarYUV(int colorFormat) {
+ switch (colorFormat) {
+ case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar:
+ case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar:
+ return false;
+ case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar:
+ case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar:
+ case MediaCodecInfo.CodecCapabilities.COLOR_TI_FormatYUV420PackedSemiPlanar:
+ return true;
+ default:
+ throw new RuntimeException("unknown format " + colorFormat);
+ }
+ }
+
+ /**
+ * Does the actual work for encoding frames from buffers of byte[].
+ */
+ private void doEncodeDecodeVideoFromBuffer(MediaCodec encoder, int encoderColorFormat,
+ MediaCodec decoder, boolean toSurface) {
+ final int TIMEOUT_USEC = 10000;
+ ByteBuffer[] encoderInputBuffers = encoder.getInputBuffers();
+ ByteBuffer[] encoderOutputBuffers = encoder.getOutputBuffers();
+ ByteBuffer[] decoderInputBuffers = null;
+ ByteBuffer[] decoderOutputBuffers = null;
+ MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+ int decoderColorFormat = -12345; // init to invalid value
+ int generateIndex = 0;
+ int checkIndex = 0;
+ int badFrames = 0;
+ boolean decoderConfigured = false;
+ OutputSurface outputSurface = null;
+
+ // The size of a frame of video data, in the formats we handle, is stride*sliceHeight
+ // for Y, and (stride/2)*(sliceHeight/2) for each of the Cb and Cr channels. Application
+ // of algebra and assuming that stride==width and sliceHeight==height yields:
+ byte[] frameData = new byte[mWidth * mHeight * 3 / 2];
+
+ // Just out of curiosity.
+ long rawSize = 0;
+ long encodedSize = 0;
+
+ // Save a copy to disk. Useful for debugging the test. Note this is a raw elementary
+ // stream, not a .mp4 file, so not all players will know what to do with it.
+ FileOutputStream outputStream = null;
+ if (DEBUG_SAVE_FILE) {
+ String fileName = DEBUG_FILE_NAME_BASE + mWidth + "x" + mHeight + ".mp4";
+ try {
+ outputStream = new FileOutputStream(fileName);
+ Log.d(TAG, "encoded output will be saved as " + fileName);
+ } catch (IOException ioe) {
+ Log.w(TAG, "Unable to create debug output file " + fileName);
+ throw new RuntimeException(ioe);
+ }
+ }
+
+ if (toSurface) {
+ outputSurface = new OutputSurface(mWidth, mHeight);
+ }
+
+ // Loop until the output side is done.
+ boolean inputDone = false;
+ boolean encoderDone = false;
+ boolean outputDone = false;
+ while (!outputDone) {
+ if (VERBOSE) Log.d(TAG, "loop");
+
+ // If we're not done submitting frames, generate a new one and submit it. By
+ // doing this on every loop we're working to ensure that the encoder always has
+ // work to do.
+ //
+ // We don't really want a timeout here, but sometimes there's a delay opening
+ // the encoder device, so a short timeout can keep us from spinning hard.
+ if (!inputDone) {
+ int inputBufIndex = encoder.dequeueInputBuffer(TIMEOUT_USEC);
+ if (VERBOSE) Log.d(TAG, "inputBufIndex=" + inputBufIndex);
+ if (inputBufIndex >= 0) {
+ long ptsUsec = computePresentationTime(generateIndex);
+ if (generateIndex == NUM_FRAMES) {
+ // Send an empty frame with the end-of-stream flag set. If we set EOS
+ // on a frame with data, that frame data will be ignored, and the
+ // output will be short one frame.
+ encoder.queueInputBuffer(inputBufIndex, 0, 0, ptsUsec,
+ MediaCodec.BUFFER_FLAG_END_OF_STREAM);
+ inputDone = true;
+ if (VERBOSE) Log.d(TAG, "sent input EOS (with zero-length frame)");
+ } else {
+ generateFrame(generateIndex, encoderColorFormat, frameData);
+
+ ByteBuffer inputBuf = encoderInputBuffers[inputBufIndex];
+ // the buffer should be sized to hold one full frame
+ assertTrue(inputBuf.capacity() >= frameData.length);
+ inputBuf.clear();
+ inputBuf.put(frameData);
+
+ encoder.queueInputBuffer(inputBufIndex, 0, frameData.length, ptsUsec, 0);
+ if (VERBOSE) Log.d(TAG, "submitted frame " + generateIndex + " to enc");
+ }
+ generateIndex++;
+ } else {
+ // either all in use, or we timed out during initial setup
+ if (VERBOSE) Log.d(TAG, "input buffer not available");
+ }
+ }
+
+ // Check for output from the encoder. If there's no output yet, we either need to
+ // provide more input, or we need to wait for the encoder to work its magic. We
+ // can't actually tell which is the case, so if we can't get an output buffer right
+ // away we loop around and see if it wants more input.
+ //
+ // Once we get EOS from the encoder, we don't need to do this anymore.
+ if (!encoderDone) {
+ int encoderStatus = encoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
+ if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
+ // no output available yet
+ if (VERBOSE) Log.d(TAG, "no output from encoder available");
+ } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+ // not expected for an encoder
+ encoderOutputBuffers = encoder.getOutputBuffers();
+ if (VERBOSE) Log.d(TAG, "encoder output buffers changed");
+ } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+ // not expected for an encoder
+ MediaFormat newFormat = encoder.getOutputFormat();
+ if (VERBOSE) Log.d(TAG, "encoder output format changed: " + newFormat);
+ } else if (encoderStatus < 0) {
+ fail("unexpected result from encoder.dequeueOutputBuffer: " + encoderStatus);
+ } else { // encoderStatus >= 0
+ ByteBuffer encodedData = encoderOutputBuffers[encoderStatus];
+ if (encodedData == null) {
+ fail("encoderOutputBuffer " + encoderStatus + " was null");
+ }
+
+ // It's usually necessary to adjust the ByteBuffer values to match BufferInfo.
+ encodedData.position(info.offset);
+ encodedData.limit(info.offset + info.size);
+
+ encodedSize += info.size;
+ if (outputStream != null) {
+ byte[] data = new byte[info.size];
+ encodedData.get(data);
+ encodedData.position(info.offset);
+ try {
+ outputStream.write(data);
+ } catch (IOException ioe) {
+ Log.w(TAG, "failed writing debug data to file");
+ throw new RuntimeException(ioe);
+ }
+ }
+ if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
+ // Codec config info. Only expected on first packet. One way to
+ // handle this is to manually stuff the data into the MediaFormat
+ // and pass that to configure(). We do that here to exercise the API.
+ assertFalse(decoderConfigured);
+ MediaFormat format =
+ MediaFormat.createVideoFormat(MIME_TYPE, mWidth, mHeight);
+ format.setByteBuffer("csd-0", encodedData);
+ decoder.configure(format, toSurface ? outputSurface.getSurface() : null,
+ null, 0);
+ decoder.start();
+ decoderInputBuffers = decoder.getInputBuffers();
+ decoderOutputBuffers = decoder.getOutputBuffers();
+ decoderConfigured = true;
+ if (VERBOSE) Log.d(TAG, "decoder configured (" + info.size + " bytes)");
+ } else {
+ // Get a decoder input buffer, blocking until it's available.
+ assertTrue(decoderConfigured);
+ int inputBufIndex = decoder.dequeueInputBuffer(-1);
+ ByteBuffer inputBuf = decoderInputBuffers[inputBufIndex];
+ inputBuf.clear();
+ inputBuf.put(encodedData);
+ decoder.queueInputBuffer(inputBufIndex, 0, info.size,
+ info.presentationTimeUs, info.flags);
+
+ encoderDone = (info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
+ if (VERBOSE) Log.d(TAG, "passed " + info.size + " bytes to decoder"
+ + (encoderDone ? " (EOS)" : ""));
+ }
+
+ encoder.releaseOutputBuffer(encoderStatus, false);
+ }
+ }
+
+ // Check for output from the decoder. We want to do this on every loop to avoid
+ // the possibility of stalling the pipeline. We use a short timeout to avoid
+ // burning CPU if the decoder is hard at work but the next frame isn't quite ready.
+ //
+ // If we're decoding to a Surface, we'll get notified here as usual but the
+ // ByteBuffer references will be null. The data is sent to Surface instead.
+ if (decoderConfigured) {
+ int decoderStatus = decoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
+ if (decoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
+ // no output available yet
+ if (VERBOSE) Log.d(TAG, "no output from decoder available");
+ } else if (decoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+ // The storage associated with the direct ByteBuffer may already be unmapped,
+ // so attempting to access data through the old output buffer array could
+ // lead to a native crash.
+ if (VERBOSE) Log.d(TAG, "decoder output buffers changed");
+ decoderOutputBuffers = decoder.getOutputBuffers();
+ } else if (decoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+ // this happens before the first frame is returned
+ MediaFormat decoderOutputFormat = decoder.getOutputFormat();
+ decoderColorFormat =
+ decoderOutputFormat.getInteger(MediaFormat.KEY_COLOR_FORMAT);
+ if (VERBOSE) Log.d(TAG, "decoder output format changed: " +
+ decoderOutputFormat);
+ } else if (decoderStatus < 0) {
+ fail("unexpected result from deocder.dequeueOutputBuffer: " + decoderStatus);
+ } else { // decoderStatus >= 0
+ if (!toSurface) {
+ ByteBuffer outputFrame = decoderOutputBuffers[decoderStatus];
+
+ outputFrame.position(info.offset);
+ outputFrame.limit(info.offset + info.size);
+
+ rawSize += info.size;
+ if (info.size == 0) {
+ if (VERBOSE) Log.d(TAG, "got empty frame");
+ } else {
+ if (VERBOSE) Log.d(TAG, "decoded, checking frame " + checkIndex);
+ assertEquals("Wrong time stamp", computePresentationTime(checkIndex),
+ info.presentationTimeUs);
+ if (!checkFrame(checkIndex++, decoderColorFormat, outputFrame)) {
+ badFrames++;
+ }
+ }
+
+ if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+ if (VERBOSE) Log.d(TAG, "output EOS");
+ outputDone = true;
+ }
+ decoder.releaseOutputBuffer(decoderStatus, false /*render*/);
+ } else {
+ if (VERBOSE) Log.d(TAG, "surface decoder given buffer " + decoderStatus +
+ " (size=" + info.size + ")");
+ rawSize += info.size;
+ if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+ if (VERBOSE) Log.d(TAG, "output EOS");
+ outputDone = true;
+ }
+
+ boolean doRender = (info.size != 0);
+
+ // As soon as we call releaseOutputBuffer, the buffer will be forwarded
+ // to SurfaceTexture to convert to a texture. The API doesn't guarantee
+ // that the texture will be available before the call returns, so we
+ // need to wait for the onFrameAvailable callback to fire.
+ decoder.releaseOutputBuffer(decoderStatus, doRender);
+ if (doRender) {
+ if (VERBOSE) Log.d(TAG, "awaiting frame " + checkIndex);
+ assertEquals("Wrong time stamp", computePresentationTime(checkIndex),
+ info.presentationTimeUs);
+ outputSurface.awaitNewImage();
+ outputSurface.drawImage();
+ if (!checkSurfaceFrame(checkIndex++)) {
+ badFrames++;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (VERBOSE) Log.d(TAG, "decoded " + checkIndex + " frames at "
+ + mWidth + "x" + mHeight + ": raw=" + rawSize + ", enc=" + encodedSize);
+ if (outputStream != null) {
+ try {
+ outputStream.close();
+ } catch (IOException ioe) {
+ Log.w(TAG, "failed closing debug file");
+ throw new RuntimeException(ioe);
+ }
+ }
+
+ if (outputSurface != null) {
+ outputSurface.release();
+ }
+
+ if (checkIndex != NUM_FRAMES) {
+ fail("expected " + NUM_FRAMES + " frames, only decoded " + checkIndex);
+ }
+ if (badFrames != 0) {
+ fail("Found " + badFrames + " bad frames");
+ }
+ }
+
+ /**
+ * Does the actual work for encoding and decoding from Surface to Surface.
+ */
+ private void doEncodeDecodeVideoFromSurfaceToSurface(MediaCodec encoder,
+ InputSurface inputSurface, int encoderColorFormat, MediaCodec decoder,
+ OutputSurface outputSurface) {
+ final int TIMEOUT_USEC = 10000;
+ ByteBuffer[] encoderOutputBuffers = encoder.getOutputBuffers();
+ ByteBuffer[] decoderInputBuffers = decoder.getInputBuffers();
+ MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+ int generateIndex = 0;
+ int checkIndex = 0;
+ int badFrames = 0;
+
+ // Save a copy to disk. Useful for debugging the test. Note this is a raw elementary
+ // stream, not a .mp4 file, so not all players will know what to do with it.
+ FileOutputStream outputStream = null;
+ if (DEBUG_SAVE_FILE) {
+ String fileName = DEBUG_FILE_NAME_BASE + mWidth + "x" + mHeight + ".mp4";
+ try {
+ outputStream = new FileOutputStream(fileName);
+ Log.d(TAG, "encoded output will be saved as " + fileName);
+ } catch (IOException ioe) {
+ Log.w(TAG, "Unable to create debug output file " + fileName);
+ throw new RuntimeException(ioe);
+ }
+ }
+
+ // Loop until the output side is done.
+ boolean inputDone = false;
+ boolean encoderDone = false;
+ boolean outputDone = false;
+ while (!outputDone) {
+ if (VERBOSE) Log.d(TAG, "loop");
+
+ // If we're not done submitting frames, generate a new one and submit it. The
+ // eglSwapBuffers call will block if the input is full.
+ if (!inputDone) {
+ if (generateIndex == NUM_FRAMES) {
+ // Send an empty frame with the end-of-stream flag set.
+ if (VERBOSE) Log.d(TAG, "signaling input EOS");
+ encoder.signalEndOfInputStream();
+ inputDone = true;
+ } else {
+ inputSurface.makeCurrent();
+ generateSurfaceFrame(generateIndex);
+ inputSurface.setPresentationTime(computePresentationTime(generateIndex));
+ if (VERBOSE) Log.d(TAG, "inputSurface swapBuffers");
+ inputSurface.swapBuffers();
+ }
+ generateIndex++;
+ }
+
+ // Assume output is available. Loop until both assumptions are false.
+ boolean decoderOutputAvailable = true;
+ boolean encoderOutputAvailable = !encoderDone;
+ while (decoderOutputAvailable || encoderOutputAvailable) {
+ // Start by draining any pending output from the decoder. It's important to
+ // do this before we try to stuff any more data in.
+ int decoderStatus = decoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
+ if (decoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
+ // no output available yet
+ if (VERBOSE) Log.d(TAG, "no output from decoder available");
+ decoderOutputAvailable = false;
+ } else if (decoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+ if (VERBOSE) Log.d(TAG, "decoder output buffers changed (but we don't care)");
+ } else if (decoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+ // this happens before the first frame is returned
+ MediaFormat decoderOutputFormat = decoder.getOutputFormat();
+ if (VERBOSE) Log.d(TAG, "decoder output format changed: " +
+ decoderOutputFormat);
+ } else if (decoderStatus < 0) {
+ fail("unexpected result from deocder.dequeueOutputBuffer: " + decoderStatus);
+ } else { // decoderStatus >= 0
+ if (VERBOSE) Log.d(TAG, "surface decoder given buffer " + decoderStatus +
+ " (size=" + info.size + ")");
+ if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+ if (VERBOSE) Log.d(TAG, "output EOS");
+ outputDone = true;
+ }
+
+ // The ByteBuffers are null references, but we still get a nonzero size for
+ // the decoded data.
+ boolean doRender = (info.size != 0);
+
+ // As soon as we call releaseOutputBuffer, the buffer will be forwarded
+ // to SurfaceTexture to convert to a texture. The API doesn't guarantee
+ // that the texture will be available before the call returns, so we
+ // need to wait for the onFrameAvailable callback to fire. If we don't
+ // wait, we risk dropping frames.
+ outputSurface.makeCurrent();
+ decoder.releaseOutputBuffer(decoderStatus, doRender);
+ if (doRender) {
+ assertEquals("Wrong time stamp", computePresentationTime(checkIndex),
+ info.presentationTimeUs);
+ if (VERBOSE) Log.d(TAG, "awaiting frame " + checkIndex);
+ outputSurface.awaitNewImage();
+ outputSurface.drawImage();
+ if (!checkSurfaceFrame(checkIndex++)) {
+ badFrames++;
+ }
+ }
+ }
+ if (decoderStatus != MediaCodec.INFO_TRY_AGAIN_LATER) {
+ // Continue attempts to drain output.
+ continue;
+ }
+
+ // Decoder is drained, check to see if we've got a new buffer of output from
+ // the encoder.
+ if (!encoderDone) {
+ int encoderStatus = encoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
+ if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
+ // no output available yet
+ if (VERBOSE) Log.d(TAG, "no output from encoder available");
+ encoderOutputAvailable = false;
+ } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+ // not expected for an encoder
+ encoderOutputBuffers = encoder.getOutputBuffers();
+ if (VERBOSE) Log.d(TAG, "encoder output buffers changed");
+ } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+ // not expected for an encoder
+ MediaFormat newFormat = encoder.getOutputFormat();
+ if (VERBOSE) Log.d(TAG, "encoder output format changed: " + newFormat);
+ } else if (encoderStatus < 0) {
+ fail("unexpected result from encoder.dequeueOutputBuffer: " + encoderStatus);
+ } else { // encoderStatus >= 0
+ ByteBuffer encodedData = encoderOutputBuffers[encoderStatus];
+ if (encodedData == null) {
+ fail("encoderOutputBuffer " + encoderStatus + " was null");
+ }
+
+ // It's usually necessary to adjust the ByteBuffer values to match BufferInfo.
+ encodedData.position(info.offset);
+ encodedData.limit(info.offset + info.size);
+
+ if (outputStream != null) {
+ byte[] data = new byte[info.size];
+ encodedData.get(data);
+ encodedData.position(info.offset);
+ try {
+ outputStream.write(data);
+ } catch (IOException ioe) {
+ Log.w(TAG, "failed writing debug data to file");
+ throw new RuntimeException(ioe);
+ }
+ }
+
+ // Get a decoder input buffer, blocking until it's available. We just
+ // drained the decoder output, so we expect there to be a free input
+ // buffer now or in the near future (i.e. this should never deadlock
+ // if the codec is meeting requirements).
+ //
+ // The first buffer of data we get will have the BUFFER_FLAG_CODEC_CONFIG
+ // flag set; the decoder will see this and finish configuring itself.
+ int inputBufIndex = decoder.dequeueInputBuffer(-1);
+ ByteBuffer inputBuf = decoderInputBuffers[inputBufIndex];
+ inputBuf.clear();
+ inputBuf.put(encodedData);
+ decoder.queueInputBuffer(inputBufIndex, 0, info.size,
+ info.presentationTimeUs, info.flags);
+
+ // If everything from the encoder has been passed to the decoder, we
+ // can stop polling the encoder output. (This just an optimization.)
+ if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+ encoderDone = true;
+ encoderOutputAvailable = false;
+ }
+ if (VERBOSE) Log.d(TAG, "passed " + info.size + " bytes to decoder"
+ + (encoderDone ? " (EOS)" : ""));
+
+ encoder.releaseOutputBuffer(encoderStatus, false);
+ }
+ }
+ }
+ }
+
+ if (outputStream != null) {
+ try {
+ outputStream.close();
+ } catch (IOException ioe) {
+ Log.w(TAG, "failed closing debug file");
+ throw new RuntimeException(ioe);
+ }
+ }
+
+ if (checkIndex != NUM_FRAMES) {
+ fail("expected " + NUM_FRAMES + " frames, only decoded " + checkIndex);
+ }
+ if (badFrames != 0) {
+ fail("Found " + badFrames + " bad frames");
+ }
+ }
+
+
+ /**
+ * Generates data for frame N into the supplied buffer. We have an 8-frame animation
+ * sequence that wraps around. It looks like this:
+ * <pre>
+ * 0 1 2 3
+ * 7 6 5 4
+ * </pre>
+ * We draw one of the eight rectangles and leave the rest set to the zero-fill color.
+ */
+ private void generateFrame(int frameIndex, int colorFormat, byte[] frameData) {
+ final int HALF_WIDTH = mWidth / 2;
+ boolean semiPlanar = isSemiPlanarYUV(colorFormat);
+
+ // Set to zero. In YUV this is a dull green.
+ Arrays.fill(frameData, (byte) 0);
+
+ int startX, startY, countX, countY;
+
+ frameIndex %= 8;
+ //frameIndex = (frameIndex / 8) % 8; // use this instead for debug -- easier to see
+ if (frameIndex < 4) {
+ startX = frameIndex * (mWidth / 4);
+ startY = 0;
+ } else {
+ startX = (7 - frameIndex) * (mWidth / 4);
+ startY = mHeight / 2;
+ }
+
+ for (int y = startY + (mHeight/2) - 1; y >= startY; --y) {
+ for (int x = startX + (mWidth/4) - 1; x >= startX; --x) {
+ if (semiPlanar) {
+ // full-size Y, followed by UV pairs at half resolution
+ // e.g. Nexus 4 OMX.qcom.video.encoder.avc COLOR_FormatYUV420SemiPlanar
+ // e.g. Galaxy Nexus OMX.TI.DUCATI1.VIDEO.H264E
+ // OMX_TI_COLOR_FormatYUV420PackedSemiPlanar
+ frameData[y * mWidth + x] = (byte) TEST_Y;
+ if ((x & 0x01) == 0 && (y & 0x01) == 0) {
+ frameData[mWidth*mHeight + y * HALF_WIDTH + x] = (byte) TEST_U;
+ frameData[mWidth*mHeight + y * HALF_WIDTH + x + 1] = (byte) TEST_V;
+ }
+ } else {
+ // full-size Y, followed by quarter-size U and quarter-size V
+ // e.g. Nexus 10 OMX.Exynos.AVC.Encoder COLOR_FormatYUV420Planar
+ // e.g. Nexus 7 OMX.Nvidia.h264.encoder COLOR_FormatYUV420Planar
+ frameData[y * mWidth + x] = (byte) TEST_Y;
+ if ((x & 0x01) == 0 && (y & 0x01) == 0) {
+ frameData[mWidth*mHeight + (y/2) * HALF_WIDTH + (x/2)] = (byte) TEST_U;
+ frameData[mWidth*mHeight + HALF_WIDTH * (mHeight / 2) +
+ (y/2) * HALF_WIDTH + (x/2)] = (byte) TEST_V;
+ }
+ }
+ }
+ }
+
+ if (false) {
+ // make sure that generate and check agree
+ if (!checkFrame(frameIndex, colorFormat, ByteBuffer.wrap(frameData))) {
+ fail("spot check failed");
+ }
+ }
+ }
+
+ /**
+ * Performs a simple check to see if the frame is more or less right.
+ * <p>
+ * See {@link generateFrame} for a description of the layout. The idea is to sample
+ * one pixel from the middle of the 8 regions, and verify that the correct one has
+ * the non-background color. We can't know exactly what the video encoder has done
+ * with our frames, so we just check to see if it looks like more or less the right thing.
+ *
+ * @return true if the frame looks good
+ */
+ private boolean checkFrame(int frameIndex, int colorFormat, ByteBuffer frameData) {
+ // Check for color formats we don't understand. There is no requirement for video
+ // decoders to use a "mundane" format, so we just give a pass on proprietary formats.
+ // e.g. Nexus 4 0x7FA30C03 OMX_QCOM_COLOR_FormatYUV420PackedSemiPlanar64x32Tile2m8ka
+ if (!isRecognizedFormat(colorFormat)) {
+ Log.d(TAG, "unable to check frame contents for colorFormat=" +
+ Integer.toHexString(colorFormat));
+ return true;
+ }
+
+ final int HALF_WIDTH = mWidth / 2;
+ boolean frameFailed = false;
+ boolean semiPlanar = isSemiPlanarYUV(colorFormat);
+
+ for (int i = 0; i < 8; i++) {
+ int x, y;
+ if (i < 4) {
+ x = i * (mWidth / 4) + (mWidth / 8);
+ y = mHeight / 4;
+ } else {
+ x = (7 - i) * (mWidth / 4) + (mWidth / 8);
+ y = (mHeight * 3) / 4;
+ }
+
+ int testY, testU, testV;
+ if (semiPlanar) {
+ // Galaxy Nexus uses OMX_TI_COLOR_FormatYUV420PackedSemiPlanar
+ testY = frameData.get(y * mWidth + x) & 0xff;
+ testU = frameData.get(mWidth*mHeight + 2*(y/2) * HALF_WIDTH + 2*(x/2)) & 0xff;
+ testV = frameData.get(mWidth*mHeight + 2*(y/2) * HALF_WIDTH + 2*(x/2) + 1) & 0xff;
+ } else {
+ // Nexus 10, Nexus 7 use COLOR_FormatYUV420Planar
+ testY = frameData.get(y * mWidth + x) & 0xff;
+ testU = frameData.get(mWidth*mHeight + (y/2) * HALF_WIDTH + (x/2)) & 0xff;
+ testV = frameData.get(mWidth*mHeight + HALF_WIDTH * (mHeight / 2) +
+ (y/2) * HALF_WIDTH + (x/2)) & 0xff;
+ }
+
+ int expY, expU, expV;
+ if (i == frameIndex % 8) {
+ // colored rect
+ expY = TEST_Y;
+ expU = TEST_U;
+ expV = TEST_V;
+ } else {
+ // should be our zeroed-out buffer
+ expY = expU = expV = 0;
+ }
+ if (!isColorClose(testY, expY) ||
+ !isColorClose(testU, expU) ||
+ !isColorClose(testV, expV)) {
+ Log.w(TAG, "Bad frame " + frameIndex + " (rect=" + i + ": yuv=" + testY +
+ "," + testU + "," + testV + " vs. expected " + expY + "," + expU +
+ "," + expV + ")");
+ frameFailed = true;
+ }
+ }
+
+ return !frameFailed;
+ }
+
+ /**
+ * Generates a frame of data using GL commands.
+ */
+ private void generateSurfaceFrame(int frameIndex) {
+ frameIndex %= 8;
+
+ int startX, startY;
+ if (frameIndex < 4) {
+ // (0,0) is bottom-left in GL
+ startX = frameIndex * (mWidth / 4);
+ startY = mHeight / 2;
+ } else {
+ startX = (7 - frameIndex) * (mWidth / 4);
+ startY = 0;
+ }
+
+ GLES20.glDisable(GLES20.GL_SCISSOR_TEST);
+ GLES20.glClearColor(TEST_R0 / 255.0f, TEST_G0 / 255.0f, TEST_B0 / 255.0f, 1.0f);
+ GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+ GLES20.glEnable(GLES20.GL_SCISSOR_TEST);
+ GLES20.glScissor(startX, startY, mWidth / 4, mHeight / 2);
+ GLES20.glClearColor(TEST_R1 / 255.0f, TEST_G1 / 255.0f, TEST_B1 / 255.0f, 1.0f);
+ GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+ }
+
+ /**
+ * Checks the frame for correctness. Similar to {@link checkFrame}, but uses GL to
+ * read pixels from the current surface.
+ *
+ * @return true if the frame looks good
+ */
+ private boolean checkSurfaceFrame(int frameIndex) {
+ ByteBuffer pixelBuf = ByteBuffer.allocateDirect(4); // TODO - reuse this
+ boolean frameFailed = false;
+
+ for (int i = 0; i < 8; i++) {
+ // Note the coordinates are inverted on the Y-axis in GL.
+ int x, y;
+ if (i < 4) {
+ x = i * (mWidth / 4) + (mWidth / 8);
+ y = (mHeight * 3) / 4;
+ } else {
+ x = (7 - i) * (mWidth / 4) + (mWidth / 8);
+ y = mHeight / 4;
+ }
+
+ GLES20.glReadPixels(x, y, 1, 1, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, pixelBuf);
+ int r = pixelBuf.get(0) & 0xff;
+ int g = pixelBuf.get(1) & 0xff;
+ int b = pixelBuf.get(2) & 0xff;
+ //Log.d(TAG, "GOT(" + frameIndex + "/" + i + "): r=" + r + " g=" + g + " b=" + b);
+
+ int expR, expG, expB;
+ if (i == frameIndex % 8) {
+ // colored rect
+ expR = TEST_R1;
+ expG = TEST_G1;
+ expB = TEST_B1;
+ } else {
+ // zero background color
+ expR = TEST_R0;
+ expG = TEST_G0;
+ expB = TEST_B0;
+ }
+ if (!isColorClose(r, expR) ||
+ !isColorClose(g, expG) ||
+ !isColorClose(b, expB)) {
+ Log.w(TAG, "Bad frame " + frameIndex + " (rect=" + i + ": rgb=" + r +
+ "," + g + "," + b + " vs. expected " + expR + "," + expG +
+ "," + expB + ")");
+ frameFailed = true;
+ }
+ }
+
+ return !frameFailed;
+ }
+
+ /**
+ * Returns true if the actual color value is close to the expected color value.
+ */
+ static boolean isColorClose(int actual, int expected) {
+ final int MAX_DELTA = 8;
+ if (expected < MAX_DELTA) {
+ return actual < (expected + MAX_DELTA);
+ } else if (expected > (255 - MAX_DELTA)) {
+ return actual > (expected - MAX_DELTA);
+ } else {
+ return actual > (expected - MAX_DELTA) && actual < (expected + MAX_DELTA);
+ }
+ }
+
+ /**
+ * Generates the presentation time for frame N.
+ */
+ private static long computePresentationTime(int frameIndex) {
+ return 132 + frameIndex * 1000000 / FRAME_RATE;
+ }
+}
diff --git a/tests/tests/media/src/android/media/cts/InputSurface.java b/tests/tests/media/src/android/media/cts/InputSurface.java
new file mode 100644
index 0000000..ff6ece1
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/InputSurface.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.cts;
+
+import android.opengl.EGL14;
+import android.opengl.EGLConfig;
+import android.opengl.EGLContext;
+import android.opengl.EGLDisplay;
+import android.opengl.EGLSurface;
+import android.opengl.GLES10;
+import android.opengl.GLES11Ext;
+import android.opengl.GLES20;
+import android.opengl.Matrix;
+import android.util.Log;
+import android.view.Surface;
+
+import java.nio.ByteBuffer;
+
+
+/**
+ * Holds state associated with a Surface used for MediaCodec encoder input.
+ * <p>
+ * The constructor takes a Surface obtained from MediaCodec.createInputSurface(), and uses that
+ * to create an EGL window surface. Calls to eglSwapBuffers() cause a frame of data to be sent
+ * to the video encoder.
+ */
+class InputSurface {
+ private static final String TAG = "InputSurface";
+ private static final boolean VERBOSE = false;
+
+ private static final int EGL_RECORDABLE_ANDROID = 0x3142;
+ private static final int EGL_OPENGL_ES2_BIT = 4;
+
+ private EGLDisplay mEGLDisplay;
+ private EGLContext mEGLContext;
+ private EGLSurface mEGLSurface;
+
+ private Surface mSurface;
+
+ /**
+ * Creates an InputSurface from a Surface.
+ */
+ public InputSurface(Surface surface) {
+ if (surface == null) {
+ throw new NullPointerException();
+ }
+ mSurface = surface;
+
+ eglSetup();
+ }
+
+ /**
+ * Prepares EGL. We want a GLES 2.0 context and a surface that supports recording.
+ */
+ private void eglSetup() {
+ mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
+ if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
+ throw new RuntimeException("unable to get EGL14 display");
+ }
+ int[] version = new int[2];
+ if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) {
+ mEGLDisplay = null;
+ throw new RuntimeException("unable to initialize EGL14");
+ }
+
+ // Configure EGL for pbuffer and OpenGL ES 2.0. We want enough RGB bits
+ // to be able to tell if the frame is reasonable.
+ int[] attribList = {
+ EGL14.EGL_RED_SIZE, 8,
+ EGL14.EGL_GREEN_SIZE, 8,
+ EGL14.EGL_BLUE_SIZE, 8,
+ EGL14.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+ EGL_RECORDABLE_ANDROID, 1,
+ EGL14.EGL_NONE
+ };
+ EGLConfig[] configs = new EGLConfig[1];
+ int[] numConfigs = new int[1];
+ if (!EGL14.eglChooseConfig(mEGLDisplay, attribList, 0, configs, 0, configs.length,
+ numConfigs, 0)) {
+ throw new RuntimeException("unable to find RGB888+recordable ES2 EGL config");
+ }
+
+ // Configure context for OpenGL ES 2.0.
+ int[] attrib_list = {
+ EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
+ EGL14.EGL_NONE
+ };
+ mEGLContext = EGL14.eglCreateContext(mEGLDisplay, configs[0], EGL14.EGL_NO_CONTEXT,
+ attrib_list, 0);
+ checkEglError("eglCreateContext");
+ if (mEGLContext == null) {
+ throw new RuntimeException("null context");
+ }
+
+ // Create a window surface, and attach it to the Surface we received.
+ int[] surfaceAttribs = {
+ EGL14.EGL_NONE
+ };
+ mEGLSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, configs[0], mSurface,
+ surfaceAttribs, 0);
+ checkEglError("eglCreateWindowSurface");
+ if (mEGLSurface == null) {
+ throw new RuntimeException("surface was null");
+ }
+ }
+
+ /**
+ * Discard all resources held by this class, notably the EGL context. Also releases the
+ * Surface that was passed to our constructor.
+ */
+ public void release() {
+ if (EGL14.eglGetCurrentContext() == mEGLContext) {
+ // Clear the current context and surface to ensure they are discarded immediately.
+ EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
+ EGL14.EGL_NO_CONTEXT);
+ }
+ EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface);
+ EGL14.eglDestroyContext(mEGLDisplay, mEGLContext);
+ //EGL14.eglTerminate(mEGLDisplay);
+
+ mSurface.release();
+
+ // null everything out so future attempts to use this object will cause an NPE
+ mEGLDisplay = null;
+ mEGLContext = null;
+ mEGLSurface = null;
+
+ mSurface = null;
+ }
+
+ /**
+ * Makes our EGL context and surface current.
+ */
+ public void makeCurrent() {
+ if (!EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) {
+ throw new RuntimeException("eglMakeCurrent failed");
+ }
+ }
+
+ /**
+ * Calls eglSwapBuffers. Use this to "publish" the current frame.
+ */
+ public boolean swapBuffers() {
+ return EGL14.eglSwapBuffers(mEGLDisplay, mEGLSurface);
+ }
+
+ /**
+ * Returns the Surface that the MediaCodec receives buffers from.
+ */
+ public Surface getSurface() {
+ return mSurface;
+ }
+
+ /**
+ * Sends the presentation time stamp to EGL.
+ */
+ public void setPresentationTime(long when) {
+ EGL14.eglPresentationTimeANDROID(mEGLDisplay, mEGLSurface, when);
+ }
+
+ /**
+ * Checks for EGL errors.
+ */
+ private void checkEglError(String msg) {
+ boolean failed = false;
+ int error;
+ while ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) {
+ Log.e(TAG, msg + ": EGL error: 0x" + Integer.toHexString(error));
+ failed = true;
+ }
+ if (failed) {
+ throw new RuntimeException("EGL error encountered (see log)");
+ }
+ }
+}
diff --git a/tests/tests/media/src/android/media/cts/IvfReader.java b/tests/tests/media/src/android/media/cts/IvfReader.java
new file mode 100644
index 0000000..508ae25
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/IvfReader.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.cts;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+/**
+ * A simple reader for an IVF file.
+ *
+ * IVF format is a simple container format for VP8 encoded frames.
+ * This reader is capable of getting frame count, width and height
+ * from the header, and access individual frames randomly by
+ * frame number.
+ */
+
+public class IvfReader {
+ private static final byte HEADER_END = 32;
+ private static final byte FOURCC_HEAD = 8;
+ private static final byte WIDTH_HEAD = 12;
+ private static final byte HEIGHT_HEAD = 14;
+ private static final byte FRAMECOUNT_HEAD = 24;
+ private static final byte FRAME_HEADER_SIZE = 12;
+
+ private RandomAccessFile mIvfFile;
+ private boolean mHeaderValid;
+ private int mWidth;
+ private int mHeight;
+ private int mFrameCount;
+ private int[] mFrameHeads; // Head of frame header
+ private int[] mFrameSizes; // Frame size excluding header
+
+
+ /**
+ * Initializes the IVF file reader.
+ *
+ * Only minimal verification is done to check if this
+ * is indeed a valid IVF file. (fourcc, signature)
+ *
+ * All frame headers are read in advance.
+ *
+ * @param filename name of the IVF file
+ */
+ public IvfReader(String filename) throws IOException{
+ mIvfFile = new RandomAccessFile(filename, "r");
+
+ mHeaderValid = verifyHeader();
+ readHeaderData();
+ readFrameMetadata();
+ }
+
+ /**
+ * Tells if file header seems to be valid.
+ *
+ * Only minimal verification is done to check if this
+ * is indeed a valid IVF file. (fourcc, signature)
+ */
+ public boolean isHeaderValid(){
+ return mHeaderValid;
+ }
+
+ /**
+ * Returns frame width according to header information.
+ */
+ public int getWidth(){
+ return mWidth;
+ }
+
+ /**
+ * Returns frame height according to header information.
+ */
+ public int getHeight(){
+ return mHeight;
+ }
+
+ /**
+ * Returns frame count according to header information.
+ */
+ public int getFrameCount(){
+ return mFrameCount;
+ }
+
+ /**
+ * Returns frame data by index.
+ *
+ * @param frameIndex index of the frame to read, greater-equal
+ * than 0 and less than frameCount.
+ */
+ public byte[] readFrame(int frameIndex) throws IOException {
+ if (frameIndex > mFrameCount | frameIndex < 0){
+ return null;
+ }
+ int frameSize = mFrameSizes[frameIndex];
+ int frameHead = mFrameHeads[frameIndex];
+
+ byte[] frame = new byte[frameSize];
+ mIvfFile.seek(frameHead + FRAME_HEADER_SIZE);
+ mIvfFile.read(frame);
+
+ return frame;
+ }
+
+ /**
+ * Closes IVF file.
+ */
+ public void close() throws IOException{
+ mIvfFile.close();
+ }
+
+ private boolean verifyHeader() throws IOException{
+ mIvfFile.seek(0);
+
+ if (mIvfFile.length() < HEADER_END){
+ return false;
+ }
+
+ // DKIF signature
+ boolean signatureMatch = ((mIvfFile.readByte() == (byte)'D') &&
+ (mIvfFile.readByte() == (byte)'K') &&
+ (mIvfFile.readByte() == (byte)'I') &&
+ (mIvfFile.readByte() == (byte)'F'));
+
+ // Fourcc
+ mIvfFile.seek(FOURCC_HEAD);
+ boolean fourccMatch = ((mIvfFile.readByte() == (byte)'V') &&
+ (mIvfFile.readByte() == (byte)'P') &&
+ (mIvfFile.readByte() == (byte)'8') &&
+ (mIvfFile.readByte() == (byte)'0'));
+
+ return signatureMatch && fourccMatch;
+ }
+
+ private void readHeaderData() throws IOException{
+ // width
+ mIvfFile.seek(WIDTH_HEAD);
+ mWidth = (int) changeEndianness(mIvfFile.readShort());
+
+ // height
+ mIvfFile.seek(HEIGHT_HEAD);
+ mHeight = (int) changeEndianness(mIvfFile.readShort());
+
+ // frame count
+ mIvfFile.seek(FRAMECOUNT_HEAD);
+ mFrameCount = changeEndianness(mIvfFile.readInt());
+
+ // allocate frame metadata
+ mFrameHeads = new int[mFrameCount];
+ mFrameSizes = new int[mFrameCount];
+ }
+
+ private void readFrameMetadata() throws IOException{
+ int frameHead = HEADER_END;
+ for(int i = 0; i < mFrameCount; i++){
+ mIvfFile.seek(frameHead);
+ int frameSize = changeEndianness(mIvfFile.readInt());
+ mFrameHeads[i] = frameHead;
+ mFrameSizes[i] = frameSize;
+ // next frame
+ frameHead += FRAME_HEADER_SIZE + frameSize;
+ }
+ }
+
+ private static short changeEndianness(short value){
+ // Rationale for down-cast;
+ // Java Language specification 15.19:
+ // "The type of the shift expression is the promoted type of the left-hand operand."
+ // Java Language specification 5.6:
+ // "...if the operand is of compile-time type byte, short, or char,
+ // unary numeric promotion promotes it to a value of type int by a widening conversion."
+ return (short) (((value << 8) & 0XFF00) | ((value >> 8) & 0X00FF));
+ }
+
+ private static int changeEndianness(int value){
+ return (((value << 24) & 0XFF000000) |
+ ((value << 8) & 0X00FF0000) |
+ ((value >> 8) & 0X0000FF00) |
+ ((value >> 24) & 0X000000FF));
+ }
+}
diff --git a/tests/tests/media/src/android/media/cts/IvfWriter.java b/tests/tests/media/src/android/media/cts/IvfWriter.java
new file mode 100644
index 0000000..ccc0ac5
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/IvfWriter.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.cts;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+/**
+ * Writes an IVF file.
+ *
+ * IVF format is a simple container format for VP8 encoded frames.
+ */
+
+public class IvfWriter {
+ private static final byte HEADER_END = 32;
+ private RandomAccessFile mOutputFile;
+ private int mWidth;
+ private int mHeight;
+ private int mScale;
+ private int mRate;
+ private int mFrameCount;
+
+ /**
+ * Initializes the IVF file writer.
+ *
+ * Timebase fraction is in format scale/rate, e.g. 1/1000
+ * Timestamp values supplied while writing frames should be in accordance
+ * with this timebase value.
+ *
+ * @param filename name of the IVF file
+ * @param width frame width
+ * @param height frame height
+ * @param scale timebase scale (or numerator of the timebase fraction)
+ * @param rate timebase rate (or denominator of the timebase fraction)
+ */
+ public IvfWriter(String filename,
+ int width, int height,
+ int scale, int rate) throws IOException {
+ mOutputFile = new RandomAccessFile(filename, "rw");
+ mWidth = width;
+ mHeight = height;
+ mScale = scale;
+ mRate = rate;
+ mFrameCount = 0;
+ mOutputFile.seek(HEADER_END); // Skip the header for now, as framecount is unknown
+ }
+
+ /**
+ * Initializes the IVF file writer with a microsecond timebase.
+ *
+ *
+ * Microsecond timebase is default for OMX thus stagefright.
+ *
+ * @param filename name of the IVF file
+ * @param width frame width
+ * @param height frame height
+ */
+ public IvfWriter(String filename, int width, int height) throws IOException {
+ this(filename, width, height, 1, 1000000);
+ }
+
+ /**
+ * Finalizes the IVF header and closes the file.
+ */
+ public void close() throws IOException{
+ // Write header now
+ mOutputFile.seek(0);
+ mOutputFile.write(makeIvfHeader(mFrameCount, mWidth, mHeight, mScale, mRate));
+ mOutputFile.close();
+ }
+
+ /**
+ * Writes a single encoded VP8 frame with its frame header.
+ *
+ * @param frame actual contents of the encoded frame data
+ * @param width timestamp of the frame (in accordance to specified timebase)
+ */
+ public void writeFrame(byte[] frame, long timeStamp) throws IOException {
+ mOutputFile.write(makeIvfFrameHeader(frame.length, timeStamp));
+ mOutputFile.write(frame);
+ mFrameCount++;
+ }
+
+ /**
+ * Makes a 32 byte file header for IVF format.
+ *
+ * Timebase fraction is in format scale/rate, e.g. 1/1000
+ *
+ * @param frameCount total number of frames file contains
+ * @param width frame width
+ * @param height frame height
+ * @param scale timebase scale (or numerator of the timebase fraction)
+ * @param rate timebase rate (or denominator of the timebase fraction)
+ */
+ private static byte[] makeIvfHeader(int frameCount, int width, int height, int scale, int rate){
+ byte[] ivfHeader = new byte[32];
+ ivfHeader[0] = 'D';
+ ivfHeader[1] = 'K';
+ ivfHeader[2] = 'I';
+ ivfHeader[3] = 'F';
+ lay16Bits(ivfHeader, 4, 0); // version
+ lay16Bits(ivfHeader, 6, 32); // header size
+ ivfHeader[8] = 'V'; // fourcc
+ ivfHeader[9] = 'P';
+ ivfHeader[10] = '8';
+ ivfHeader[11] = '0';
+ lay16Bits(ivfHeader, 12, width);
+ lay16Bits(ivfHeader, 14, height);
+ lay32Bits(ivfHeader, 16, rate); // scale/rate
+ lay32Bits(ivfHeader, 20, scale);
+ lay32Bits(ivfHeader, 24, frameCount);
+ lay32Bits(ivfHeader, 28, 0); // unused
+ return ivfHeader;
+ }
+
+ /**
+ * Makes a 12 byte header for an encoded frame.
+ *
+ * @param size frame size
+ * @param timestamp presentation timestamp of the frame
+ */
+ private static byte[] makeIvfFrameHeader(int size, long timestamp){
+ byte[] frameHeader = new byte[12];
+ lay32Bits(frameHeader, 0, size);
+ lay64bits(frameHeader, 4, timestamp);
+ return frameHeader;
+ }
+
+
+ /**
+ * Lays least significant 16 bits of an int into 2 items of a byte array.
+ *
+ * Note that ordering is little-endian.
+ *
+ * @param array the array to be modified
+ * @param index index of the array to start laying down
+ * @param value the integer to use least significant 16 bits
+ */
+ private static void lay16Bits(byte[] array, int index, int value){
+ array[index] = (byte) (value);
+ array[index + 1] = (byte) (value >> 8);
+ }
+
+ /**
+ * Lays an int into 4 items of a byte array.
+ *
+ * Note that ordering is little-endian.
+ *
+ * @param array the array to be modified
+ * @param index index of the array to start laying down
+ * @param value the integer to use
+ */
+ private static void lay32Bits(byte[] array, int index, int value){
+ for (int i = 0; i < 4; i++){
+ array[index + i] = (byte) (value >> (i * 8));
+ }
+ }
+
+ /**
+ * Lays a long int into 8 items of a byte array.
+ *
+ * Note that ordering is little-endian.
+ *
+ * @param array the array to be modified
+ * @param index index of the array to start laying down
+ * @param value the integer to use
+ */
+ private static void lay64bits(byte[] array, int index, long value){
+ for (int i = 0; i < 8; i++){
+ array[index + i] = (byte) (value >> (i * 8));
+ }
+ }
+}
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecListTest.java b/tests/tests/media/src/android/media/cts/MediaCodecListTest.java
old mode 100755
new mode 100644
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecTest.java b/tests/tests/media/src/android/media/cts/MediaCodecTest.java
new file mode 100644
index 0000000..afa34e8
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/MediaCodecTest.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.cts;
+
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecList;
+import android.media.MediaFormat;
+import android.opengl.GLES20;
+import android.test.AndroidTestCase;
+import android.util.Log;
+import android.view.Surface;
+
+
+/**
+ * General MediaCodec tests.
+ *
+ * In particular, check various API edge cases.
+ */
+public class MediaCodecTest extends AndroidTestCase {
+ private static final String TAG = "MediaCodecTest";
+ private static final boolean VERBOSE = false; // lots of logging
+
+ // parameters for the encoder
+ private static final String MIME_TYPE = "video/avc"; // H.264 Advanced Video Coding
+ private static final int BIT_RATE = 2000000; // 2Mbps
+ private static final int FRAME_RATE = 15; // 15fps
+ private static final int IFRAME_INTERVAL = 10; // 10 seconds between I-frames
+ private static final int WIDTH = 1280;
+ private static final int HEIGHT = 720;
+
+ /**
+ * Tests:
+ * <br> calling createInputSurface() before configure() throws exception
+ * <br> calling createInputSurface() after start() throws exception
+ * <br> calling createInputSurface() with a non-Surface color format throws exception
+ */
+ public void testCreateInputSurfaceErrors() {
+ MediaFormat format = createMediaFormat();
+ MediaCodec encoder = null;
+ Surface surface = null;
+
+ // Replace color format with something that isn't COLOR_FormatSurface.
+ MediaCodecInfo codecInfo = selectCodec(MIME_TYPE);
+ int colorFormat = findNonSurfaceColorFormat(codecInfo, MIME_TYPE);
+ format.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);
+
+ try {
+ encoder = MediaCodec.createByCodecName(codecInfo.getName());;
+ try {
+ surface = encoder.createInputSurface();
+ fail("createInputSurface should not work pre-configure");
+ } catch (IllegalStateException ise) {
+ // good
+ }
+ encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+
+ try {
+ surface = encoder.createInputSurface();
+ fail("createInputSurface should require COLOR_FormatSurface");
+ } catch (IllegalStateException ise) {
+ // good
+ }
+
+ encoder.start();
+
+ try {
+ surface = encoder.createInputSurface();
+ fail("createInputSurface should not work post-start");
+ } catch (IllegalStateException ise) {
+ // good
+ }
+ } finally {
+ if (encoder != null) {
+ encoder.stop();
+ encoder.release();
+ }
+ }
+ assertNull(surface);
+ }
+
+
+ /**
+ * Tests:
+ * <br> signaling end-of-stream before any data is sent works
+ * <br> signaling EOS twice throws exception
+ * <br> submitting a frame after EOS throws exception [TODO]
+ */
+ public void testSignalSurfaceEOS() {
+ MediaFormat format = createMediaFormat();
+ MediaCodec encoder = null;
+ InputSurface inputSurface = null;
+
+ try {
+ encoder = MediaCodec.createEncoderByType(MIME_TYPE);
+ encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+ inputSurface = new InputSurface(encoder.createInputSurface());
+ inputSurface.makeCurrent();
+ encoder.start();
+
+ // send an immediate EOS
+ encoder.signalEndOfInputStream();
+
+ try {
+ encoder.signalEndOfInputStream();
+ fail("should not be able to signal EOS twice");
+ } catch (IllegalStateException ise) {
+ // good
+ }
+
+ // submit a frame post-EOS
+ GLES20.glClearColor(0.0f, 0.5f, 0.0f, 1.0f);
+ GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+ try {
+ inputSurface.swapBuffers();
+ if (false) { // TODO
+ fail("should not be able to submit frame after EOS");
+ }
+ } catch (Exception ex) {
+ // good
+ }
+ } finally {
+ if (encoder != null) {
+ encoder.stop();
+ encoder.release();
+ }
+ if (inputSurface != null) {
+ inputSurface.release();
+ }
+ }
+ }
+
+ /**
+ * Tests:
+ * <br> dequeueInputBuffer() fails when encoder configured with an input Surface
+ */
+ public void testDequeueSurface() {
+ MediaFormat format = createMediaFormat();
+ MediaCodec encoder = null;
+ Surface surface = null;
+
+ try {
+ encoder = MediaCodec.createEncoderByType(MIME_TYPE);
+ encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+ surface = encoder.createInputSurface();
+ encoder.start();
+
+ try {
+ encoder.dequeueInputBuffer(-1);
+ fail("dequeueInputBuffer should fail on encoder with input surface");
+ } catch (IllegalStateException ise) {
+ // good
+ }
+
+ } finally {
+ if (encoder != null) {
+ encoder.stop();
+ encoder.release();
+ }
+ if (surface != null) {
+ surface.release();
+ }
+ }
+ }
+
+ /**
+ * Tests:
+ * <br> configure() encoder with Surface, re-configure() without Surface works
+ * <br> sending EOS with signalEndOfInputStream on non-Surface encoder fails
+ */
+ public void testReconfigureWithoutSurface() {
+ MediaFormat format = createMediaFormat();
+ MediaCodec encoder = null;
+ Surface surface = null;
+
+ try {
+ encoder = MediaCodec.createEncoderByType(MIME_TYPE);
+ encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+ surface = encoder.createInputSurface();
+ encoder.start();
+
+ encoder.getOutputBuffers();
+
+ // re-configure, this time without an input surface
+ if (VERBOSE) Log.d(TAG, "reconfiguring");
+ encoder.stop();
+ encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+ encoder.start();
+ if (VERBOSE) Log.d(TAG, "reconfigured");
+
+ encoder.getOutputBuffers();
+ encoder.dequeueInputBuffer(-1);
+
+ try {
+ encoder.signalEndOfInputStream();
+ fail("signalEndOfInputStream only works on surface input");
+ } catch (IllegalStateException ise) {
+ // good
+ }
+ } finally {
+ if (encoder != null) {
+ encoder.stop();
+ encoder.release();
+ }
+ if (surface != null) {
+ surface.release();
+ }
+ }
+ }
+
+ /**
+ * Creates a MediaFormat with the basic set of values.
+ */
+ private static MediaFormat createMediaFormat() {
+ MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, WIDTH, HEIGHT);
+ format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+ MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
+ format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
+ format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
+ format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
+ return format;
+ }
+
+ /**
+ * Returns the first codec capable of encoding the specified MIME type, or null if no
+ * match was found.
+ */
+ private static MediaCodecInfo selectCodec(String mimeType) {
+ int numCodecs = MediaCodecList.getCodecCount();
+ for (int i = 0; i < numCodecs; i++) {
+ MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
+
+ if (!codecInfo.isEncoder()) {
+ continue;
+ }
+
+ String[] types = codecInfo.getSupportedTypes();
+ for (int j = 0; j < types.length; j++) {
+ if (types[j].equalsIgnoreCase(mimeType)) {
+ return codecInfo;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns a color format that is supported by the codec and isn't COLOR_FormatSurface. Throws
+ * an exception if none found.
+ */
+ private static int findNonSurfaceColorFormat(MediaCodecInfo codecInfo, String mimeType) {
+ MediaCodecInfo.CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(mimeType);
+ for (int i = 0; i < capabilities.colorFormats.length; i++) {
+ int colorFormat = capabilities.colorFormats[i];
+ if (colorFormat != MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface) {
+ return colorFormat;
+ }
+ }
+ fail("couldn't find a good color format for " + codecInfo.getName() + " / " + MIME_TYPE);
+ return 0; // not reached
+ }
+}
diff --git a/tests/tests/media/src/android/media/cts/MediaPlayerTest.java b/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
index dda78f8..c6d2985 100644
--- a/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
@@ -750,7 +750,7 @@
return;
}
for (int i = 0; i < trackInfos.length; ++i) {
- if (trackInfos[i] == null) continue;
+ assertTrue(trackInfos[i] != null);
if (trackInfos[i].getTrackType() ==
MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT) {
mTimedTextTrackIndex.add(i);
@@ -907,6 +907,35 @@
mMediaPlayer.stop();
}
+ public void testGetTrackInfo() throws Exception {
+ loadResource(R.raw.testvideo_with_2_subtitles);
+ loadSubtitleSource(R.raw.test_subtitle1_srt);
+ loadSubtitleSource(R.raw.test_subtitle2_srt);
+ mMediaPlayer.prepare();
+ mMediaPlayer.start();
+
+ readTimedTextTracks();
+ selectSubtitleTrack(2);
+
+ int count = 0;
+ MediaPlayer.TrackInfo[] trackInfos = mMediaPlayer.getTrackInfo();
+ assertTrue(trackInfos != null && trackInfos.length != 0);
+ for (int i = 0; i < trackInfos.length; ++i) {
+ assertTrue(trackInfos[i] != null);
+ if (trackInfos[i].getTrackType() == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT) {
+ String trackLanguage = trackInfos[i].getLanguage();
+ assertTrue(trackLanguage != null);
+ trackLanguage.trim();
+ Log.d(LOG_TAG, "track info lang: " + trackLanguage);
+ assertTrue("Should not see empty track language with our test data.",
+ trackLanguage.length() > 0);
+ count++;
+ }
+ }
+ // There are 4 subtitle tracks in total in our test data.
+ assertEquals(4, count);
+ }
+
public void testCallback() throws Throwable {
final int mp4Duration = 8484;
diff --git a/tests/tests/media/src/android/media/cts/OutputSurface.java b/tests/tests/media/src/android/media/cts/OutputSurface.java
new file mode 100644
index 0000000..8494796
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/OutputSurface.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.cts;
+
+import android.graphics.SurfaceTexture;
+import android.opengl.EGL14;
+import android.opengl.GLES20;
+import android.opengl.GLES11Ext;
+import android.opengl.GLSurfaceView;
+import android.opengl.Matrix;
+import android.util.Log;
+import android.view.Surface;
+
+import java.nio.ByteBuffer;
+
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLContext;
+import javax.microedition.khronos.egl.EGLDisplay;
+import javax.microedition.khronos.egl.EGLSurface;
+import javax.microedition.khronos.opengles.GL;
+import javax.microedition.khronos.opengles.GL10;
+
+
+
+/**
+ * Holds state associated with a Surface used for MediaCodec decoder output.
+ * <p>
+ * The (width,height) constructor for this class will prepare GL, create a SurfaceTexture,
+ * and then create a Surface for that SurfaceTexture. The Surface can be passed to
+ * MediaCodec.configure() to receive decoder output. When a frame arrives, we latch the
+ * texture with updateTexImage, then render the texture with GL to a pbuffer.
+ * <p>
+ * The no-arg constructor skips the GL preparation step and doesn't allocate a pbuffer.
+ * Instead, it just creates the Surface and SurfaceTexture, and when a frame arrives
+ * we just draw it on whatever surface is current.
+ * <p>
+ * By default, the Surface will be using a BufferQueue in asynchronous mode, so we
+ * can potentially drop frames.
+ */
+class OutputSurface implements SurfaceTexture.OnFrameAvailableListener {
+ private static final String TAG = "OutputSurface";
+ private static final boolean VERBOSE = false;
+
+ private static final int EGL_OPENGL_ES2_BIT = 4;
+
+ private EGL10 mEGL;
+ private EGLDisplay mEGLDisplay;
+ private EGLContext mEGLContext;
+ private EGLSurface mEGLSurface;
+
+ private SurfaceTexture mSurfaceTexture;
+ private Surface mSurface;
+
+ private Object mFrameSyncObject = new Object(); // guards mFrameAvailable
+ private boolean mFrameAvailable;
+
+ private TextureRender mTextureRender;
+
+ /**
+ * Creates an OutputSurface backed by a pbuffer with the specifed dimensions. The new
+ * EGL context and surface will be made current. Creates a Surface that can be passed
+ * to MediaCodec.configure().
+ */
+ public OutputSurface(int width, int height) {
+ if (width <= 0 || height <= 0) {
+ throw new IllegalArgumentException();
+ }
+
+ eglSetup(width, height);
+ makeCurrent();
+
+ setup();
+ }
+
+ /**
+ * Creates an OutputSurface using the current EGL context. Creates a Surface that can be
+ * passed to MediaCodec.configure().
+ */
+ public OutputSurface() {
+ setup();
+ }
+
+ /**
+ * Creates instances of TextureRender and SurfaceTexture, and a Surface associated
+ * with the SurfaceTexture.
+ */
+ private void setup() {
+ mTextureRender = new TextureRender();
+ mTextureRender.surfaceCreated();
+
+ // Even if we don't access the SurfaceTexture after the constructor returns, we
+ // still need to keep a reference to it. The Surface doesn't retain a reference
+ // at the Java level, so if we don't either then the object can get GCed, which
+ // causes the native finalizer to run.
+ if (VERBOSE) Log.d(TAG, "textureID=" + mTextureRender.getTextureId());
+ mSurfaceTexture = new SurfaceTexture(mTextureRender.getTextureId());
+
+ // This doesn't work if OutputSurface is created on the thread that CTS started for
+ // these test cases.
+ //
+ // The CTS-created thread has a Looper, and the SurfaceTexture constructor will
+ // create a Handler that uses it. The "frame available" message is delivered
+ // there, but since we're not a Looper-based thread we'll never see it. For
+ // this to do anything useful, OutputSurface must be created on a thread without
+ // a Looper, so that SurfaceTexture uses the main application Looper instead.
+ //
+ // Java language note: passing "this" out of a constructor is generally unwise,
+ // but we should be able to get away with it here.
+ mSurfaceTexture.setOnFrameAvailableListener(this);
+
+ mSurface = new Surface(mSurfaceTexture);
+ }
+
+ /**
+ * Prepares EGL. We want a GLES 2.0 context and a surface that supports pbuffer.
+ */
+ private void eglSetup(int width, int height) {
+ mEGL = (EGL10)EGLContext.getEGL();
+ mEGLDisplay = mEGL.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
+ if (!mEGL.eglInitialize(mEGLDisplay, null)) {
+ throw new RuntimeException("unable to initialize EGL10");
+ }
+
+ // Configure EGL for pbuffer and OpenGL ES 2.0. We want enough RGB bits
+ // to be able to tell if the frame is reasonable.
+ int[] attribList = {
+ EGL10.EGL_RED_SIZE, 8,
+ EGL10.EGL_GREEN_SIZE, 8,
+ EGL10.EGL_BLUE_SIZE, 8,
+ EGL10.EGL_SURFACE_TYPE, EGL10.EGL_PBUFFER_BIT,
+ EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+ EGL10.EGL_NONE
+ };
+ EGLConfig[] configs = new EGLConfig[1];
+ int[] numConfigs = new int[1];
+ if (!mEGL.eglChooseConfig(mEGLDisplay, attribList, configs, 1, numConfigs)) {
+ throw new RuntimeException("unable to find RGB888+pbuffer EGL config");
+ }
+
+ // Configure context for OpenGL ES 2.0.
+ int[] attrib_list = {
+ EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
+ EGL10.EGL_NONE
+ };
+ mEGLContext = mEGL.eglCreateContext(mEGLDisplay, configs[0], EGL10.EGL_NO_CONTEXT,
+ attrib_list);
+ checkEglError("eglCreateContext");
+ if (mEGLContext == null) {
+ throw new RuntimeException("null context");
+ }
+
+ // Create a pbuffer surface. By using this for output, we can use glReadPixels
+ // to test values in the output.
+ int[] surfaceAttribs = {
+ EGL10.EGL_WIDTH, width,
+ EGL10.EGL_HEIGHT, height,
+ EGL10.EGL_NONE
+ };
+ mEGLSurface = mEGL.eglCreatePbufferSurface(mEGLDisplay, configs[0], surfaceAttribs);
+ checkEglError("eglCreatePbufferSurface");
+ if (mEGLSurface == null) {
+ throw new RuntimeException("surface was null");
+ }
+ }
+
+ /**
+ * Discard all resources held by this class, notably the EGL context.
+ */
+ public void release() {
+ if (mEGL != null) {
+ if (mEGL.eglGetCurrentContext() == mEGLContext) {
+ // Clear the current context and surface to ensure they are discarded immediately.
+ mEGL.eglMakeCurrent(mEGLDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE,
+ EGL10.EGL_NO_CONTEXT);
+ }
+ mEGL.eglDestroySurface(mEGLDisplay, mEGLSurface);
+ mEGL.eglDestroyContext(mEGLDisplay, mEGLContext);
+ //mEGL.eglTerminate(mEGLDisplay);
+ }
+
+ mSurface.release();
+
+ // this causes a bunch of warnings that appear harmless but might confuse someone:
+ // W BufferQueue: [unnamed-3997-2] cancelBuffer: BufferQueue has been abandoned!
+ //mSurfaceTexture.release();
+
+ // null everything out so future attempts to use this object will cause an NPE
+ mEGLDisplay = null;
+ mEGLContext = null;
+ mEGLSurface = null;
+ mEGL = null;
+
+ mTextureRender = null;
+ mSurface = null;
+ mSurfaceTexture = null;
+ }
+
+ /**
+ * Makes our EGL context and surface current.
+ */
+ public void makeCurrent() {
+ if (mEGL == null) {
+ throw new RuntimeException("not configured for makeCurrent");
+ }
+ checkEglError("before makeCurrent");
+ if (!mEGL.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) {
+ throw new RuntimeException("eglMakeCurrent failed");
+ }
+ }
+
+ /**
+ * Returns the Surface that we draw onto.
+ */
+ public Surface getSurface() {
+ return mSurface;
+ }
+
+ /**
+ * Replaces the fragment shader.
+ */
+ public void changeFragmentShader(String fragmentShader) {
+ mTextureRender.changeFragmentShader(fragmentShader);
+ }
+
+ /**
+ * Latches the next buffer into the texture. Must be called from the thread that created
+ * the OutputSurface object, after the onFrameAvailable callback has signaled that new
+ * data is available.
+ */
+ public void awaitNewImage() {
+ final int TIMEOUT_MS = 500;
+
+ synchronized (mFrameSyncObject) {
+ while (!mFrameAvailable) {
+ try {
+ // Wait for onFrameAvailable() to signal us. Use a timeout to avoid
+ // stalling the test if it doesn't arrive.
+ mFrameSyncObject.wait(TIMEOUT_MS);
+ if (!mFrameAvailable) {
+ // TODO: if "spurious wakeup", continue while loop
+ throw new RuntimeException("Surface frame wait timed out");
+ }
+ } catch (InterruptedException ie) {
+ // shouldn't happen
+ throw new RuntimeException(ie);
+ }
+ }
+ mFrameAvailable = false;
+ }
+
+ // Latch the data.
+ mTextureRender.checkGlError("before updateTexImage");
+ mSurfaceTexture.updateTexImage();
+ }
+
+ /**
+ * Draws the data from SurfaceTexture onto the current EGL surface.
+ */
+ public void drawImage() {
+ mTextureRender.drawFrame(mSurfaceTexture);
+ }
+
+ @Override
+ public void onFrameAvailable(SurfaceTexture st) {
+ if (VERBOSE) Log.d(TAG, "new frame available");
+ synchronized (mFrameSyncObject) {
+ if (mFrameAvailable) {
+ throw new RuntimeException("mFrameAvailable already set, frame could be dropped");
+ }
+ mFrameAvailable = true;
+ mFrameSyncObject.notifyAll();
+ }
+ }
+
+ /**
+ * Checks for EGL errors.
+ */
+ private void checkEglError(String msg) {
+ boolean failed = false;
+ int error;
+ while ((error = mEGL.eglGetError()) != EGL10.EGL_SUCCESS) {
+ Log.e(TAG, msg + ": EGL error: 0x" + Integer.toHexString(error));
+ failed = true;
+ }
+ if (failed) {
+ throw new RuntimeException("EGL error encountered (see log)");
+ }
+ }
+}
diff --git a/tests/tests/media/src/android/media/cts/TextureRender.java b/tests/tests/media/src/android/media/cts/TextureRender.java
new file mode 100644
index 0000000..1ed568a
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/TextureRender.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.cts;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+import android.graphics.SurfaceTexture;
+import android.opengl.GLES11Ext;
+import android.opengl.GLES20;
+import android.opengl.GLSurfaceView;
+import android.opengl.Matrix;
+import android.util.Log;
+
+
+/**
+ * Code for rendering a texture onto a surface using OpenGL ES 2.0.
+ */
+class TextureRender {
+ private static final String TAG = "TextureRender";
+
+ private static final int FLOAT_SIZE_BYTES = 4;
+ private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES;
+ private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
+ private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
+ private final float[] mTriangleVerticesData = {
+ // X, Y, Z, U, V
+ -1.0f, -1.0f, 0, 0.f, 0.f,
+ 1.0f, -1.0f, 0, 1.f, 0.f,
+ -1.0f, 1.0f, 0, 0.f, 1.f,
+ 1.0f, 1.0f, 0, 1.f, 1.f,
+ };
+
+ private FloatBuffer mTriangleVertices;
+
+ private static final String VERTEX_SHADER =
+ "uniform mat4 uMVPMatrix;\n" +
+ "uniform mat4 uSTMatrix;\n" +
+ "attribute vec4 aPosition;\n" +
+ "attribute vec4 aTextureCoord;\n" +
+ "varying vec2 vTextureCoord;\n" +
+ "void main() {\n" +
+ " gl_Position = uMVPMatrix * aPosition;\n" +
+ " vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" +
+ "}\n";
+
+ private static final String FRAGMENT_SHADER =
+ "#extension GL_OES_EGL_image_external : require\n" +
+ "precision mediump float;\n" + // highp here doesn't seem to matter
+ "varying vec2 vTextureCoord;\n" +
+ "uniform samplerExternalOES sTexture;\n" +
+ "void main() {\n" +
+ " gl_FragColor = texture2D(sTexture, vTextureCoord);\n" +
+ "}\n";
+
+ private float[] mMVPMatrix = new float[16];
+ private float[] mSTMatrix = new float[16];
+
+ private int mProgram;
+ private int mTextureID = -12345;
+ private int muMVPMatrixHandle;
+ private int muSTMatrixHandle;
+ private int maPositionHandle;
+ private int maTextureHandle;
+
+ public TextureRender() {
+ mTriangleVertices = ByteBuffer.allocateDirect(
+ mTriangleVerticesData.length * FLOAT_SIZE_BYTES)
+ .order(ByteOrder.nativeOrder()).asFloatBuffer();
+ mTriangleVertices.put(mTriangleVerticesData).position(0);
+
+ Matrix.setIdentityM(mSTMatrix, 0);
+ }
+
+ public int getTextureId() {
+ return mTextureID;
+ }
+
+ public void drawFrame(SurfaceTexture st) {
+ checkGlError("onDrawFrame start");
+ st.getTransformMatrix(mSTMatrix);
+
+ GLES20.glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
+ GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
+
+ GLES20.glUseProgram(mProgram);
+ checkGlError("glUseProgram");
+
+ GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+ GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID);
+
+ mTriangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
+ GLES20.glVertexAttribPointer(maPositionHandle, 3, GLES20.GL_FLOAT, false,
+ TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
+ checkGlError("glVertexAttribPointer maPosition");
+ GLES20.glEnableVertexAttribArray(maPositionHandle);
+ checkGlError("glEnableVertexAttribArray maPositionHandle");
+
+ mTriangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
+ GLES20.glVertexAttribPointer(maTextureHandle, 3, GLES20.GL_FLOAT, false,
+ TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
+ checkGlError("glVertexAttribPointer maTextureHandle");
+ GLES20.glEnableVertexAttribArray(maTextureHandle);
+ checkGlError("glEnableVertexAttribArray maTextureHandle");
+
+ Matrix.setIdentityM(mMVPMatrix, 0);
+ GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mMVPMatrix, 0);
+ GLES20.glUniformMatrix4fv(muSTMatrixHandle, 1, false, mSTMatrix, 0);
+
+ GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
+ checkGlError("glDrawArrays");
+ GLES20.glFinish();
+ }
+
+ /**
+ * Initializes GL state. Call this after the EGL surface has been created and made current.
+ */
+ public void surfaceCreated() {
+ mProgram = createProgram(VERTEX_SHADER, FRAGMENT_SHADER);
+ if (mProgram == 0) {
+ throw new RuntimeException("failed creating program");
+ }
+ maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
+ checkGlError("glGetAttribLocation aPosition");
+ if (maPositionHandle == -1) {
+ throw new RuntimeException("Could not get attrib location for aPosition");
+ }
+ maTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTextureCoord");
+ checkGlError("glGetAttribLocation aTextureCoord");
+ if (maTextureHandle == -1) {
+ throw new RuntimeException("Could not get attrib location for aTextureCoord");
+ }
+
+ muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
+ checkGlError("glGetUniformLocation uMVPMatrix");
+ if (muMVPMatrixHandle == -1) {
+ throw new RuntimeException("Could not get attrib location for uMVPMatrix");
+ }
+
+ muSTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uSTMatrix");
+ checkGlError("glGetUniformLocation uSTMatrix");
+ if (muSTMatrixHandle == -1) {
+ throw new RuntimeException("Could not get attrib location for uSTMatrix");
+ }
+
+
+ int[] textures = new int[1];
+ GLES20.glGenTextures(1, textures, 0);
+
+ mTextureID = textures[0];
+ GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID);
+ checkGlError("glBindTexture mTextureID");
+
+ GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER,
+ GLES20.GL_NEAREST);
+ GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER,
+ GLES20.GL_LINEAR);
+ GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S,
+ GLES20.GL_CLAMP_TO_EDGE);
+ GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T,
+ GLES20.GL_CLAMP_TO_EDGE);
+ checkGlError("glTexParameter");
+ }
+
+ /**
+ * Replaces the fragment shader.
+ */
+ public void changeFragmentShader(String fragmentShader) {
+ GLES20.glDeleteProgram(mProgram);
+ mProgram = createProgram(VERTEX_SHADER, fragmentShader);
+ if (mProgram == 0) {
+ throw new RuntimeException("failed creating program");
+ }
+ }
+
+ private int loadShader(int shaderType, String source) {
+ int shader = GLES20.glCreateShader(shaderType);
+ checkGlError("glCreateShader type=" + shaderType);
+ GLES20.glShaderSource(shader, source);
+ GLES20.glCompileShader(shader);
+ int[] compiled = new int[1];
+ GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
+ if (compiled[0] == 0) {
+ Log.e(TAG, "Could not compile shader " + shaderType + ":");
+ Log.e(TAG, " " + GLES20.glGetShaderInfoLog(shader));
+ GLES20.glDeleteShader(shader);
+ shader = 0;
+ }
+ return shader;
+ }
+
+ private int createProgram(String vertexSource, String fragmentSource) {
+ int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
+ if (vertexShader == 0) {
+ return 0;
+ }
+ int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
+ if (pixelShader == 0) {
+ return 0;
+ }
+
+ int program = GLES20.glCreateProgram();
+ checkGlError("glCreateProgram");
+ if (program == 0) {
+ Log.e(TAG, "Could not create program");
+ }
+ GLES20.glAttachShader(program, vertexShader);
+ checkGlError("glAttachShader");
+ GLES20.glAttachShader(program, pixelShader);
+ checkGlError("glAttachShader");
+ GLES20.glLinkProgram(program);
+ int[] linkStatus = new int[1];
+ GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
+ if (linkStatus[0] != GLES20.GL_TRUE) {
+ Log.e(TAG, "Could not link program: ");
+ Log.e(TAG, GLES20.glGetProgramInfoLog(program));
+ GLES20.glDeleteProgram(program);
+ program = 0;
+ }
+ return program;
+ }
+
+ public void checkGlError(String op) {
+ int error;
+ while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
+ Log.e(TAG, op + ": glError " + error);
+ throw new RuntimeException(op + ": glError " + error);
+ }
+ }
+}
diff --git a/tests/tests/media/src/android/media/cts/VideoSurfaceView.java b/tests/tests/media/src/android/media/cts/VideoSurfaceView.java
index 943a56b..229c6f6 100644
--- a/tests/tests/media/src/android/media/cts/VideoSurfaceView.java
+++ b/tests/tests/media/src/android/media/cts/VideoSurfaceView.java
@@ -19,9 +19,6 @@
import com.android.cts.media.R;
import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.FloatBuffer;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
@@ -29,7 +26,6 @@
import android.content.Context;
import android.graphics.SurfaceTexture;
import android.media.MediaPlayer;
-import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;
import android.util.Log;
@@ -73,68 +69,22 @@
}
}
+ /**
+ * A GLSurfaceView implementation that wraps TextureRender. Used to render frames from a
+ * video decoder to a View.
+ */
private static class VideoRender
- implements GLSurfaceView.Renderer, SurfaceTexture.OnFrameAvailableListener {
+ implements GLSurfaceView.Renderer, SurfaceTexture.OnFrameAvailableListener {
private static String TAG = "VideoRender";
- private static final int FLOAT_SIZE_BYTES = 4;
- private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES;
- private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
- private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
- private final float[] mTriangleVerticesData = {
- // X, Y, Z, U, V
- -1.0f, -1.0f, 0, 0.f, 0.f,
- 1.0f, -1.0f, 0, 1.f, 0.f,
- -1.0f, 1.0f, 0, 0.f, 1.f,
- 1.0f, 1.0f, 0, 1.f, 1.f,
- };
-
- private FloatBuffer mTriangleVertices;
-
- private final String mVertexShader =
- "uniform mat4 uMVPMatrix;\n" +
- "uniform mat4 uSTMatrix;\n" +
- "attribute vec4 aPosition;\n" +
- "attribute vec4 aTextureCoord;\n" +
- "varying vec2 vTextureCoord;\n" +
- "void main() {\n" +
- " gl_Position = uMVPMatrix * aPosition;\n" +
- " vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" +
- "}\n";
-
- private final String mFragmentShader =
- "#extension GL_OES_EGL_image_external : require\n" +
- "precision mediump float;\n" +
- "varying vec2 vTextureCoord;\n" +
- "uniform samplerExternalOES sTexture;\n" +
- "void main() {\n" +
- " gl_FragColor = texture2D(sTexture, vTextureCoord);\n" +
- "}\n";
-
- private float[] mMVPMatrix = new float[16];
- private float[] mSTMatrix = new float[16];
-
- private int mProgram;
- private int mTextureID;
- private int muMVPMatrixHandle;
- private int muSTMatrixHandle;
- private int maPositionHandle;
- private int maTextureHandle;
-
- private SurfaceTexture mSurface;
+ private TextureRender mTextureRender;
+ private SurfaceTexture mSurfaceTexture;
private boolean updateSurface = false;
- private static int GL_TEXTURE_EXTERNAL_OES = 0x8D65;
-
private MediaPlayer mMediaPlayer;
public VideoRender(Context context) {
- mTriangleVertices = ByteBuffer.allocateDirect(
- mTriangleVerticesData.length * FLOAT_SIZE_BYTES)
- .order(ByteOrder.nativeOrder()).asFloatBuffer();
- mTriangleVertices.put(mTriangleVerticesData).position(0);
-
- Matrix.setIdentityM(mSTMatrix, 0);
+ mTextureRender = new TextureRender();
}
public void setMediaPlayer(MediaPlayer player) {
@@ -144,97 +94,28 @@
public void onDrawFrame(GL10 glUnused) {
synchronized(this) {
if (updateSurface) {
- mSurface.updateTexImage();
- mSurface.getTransformMatrix(mSTMatrix);
+ mSurfaceTexture.updateTexImage();
updateSurface = false;
}
}
- GLES20.glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
- GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
-
- GLES20.glUseProgram(mProgram);
- checkGlError("glUseProgram");
-
- GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
- GLES20.glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTextureID);
-
- mTriangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
- GLES20.glVertexAttribPointer(maPositionHandle, 3, GLES20.GL_FLOAT, false,
- TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
- checkGlError("glVertexAttribPointer maPosition");
- GLES20.glEnableVertexAttribArray(maPositionHandle);
- checkGlError("glEnableVertexAttribArray maPositionHandle");
-
- mTriangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
- GLES20.glVertexAttribPointer(maTextureHandle, 3, GLES20.GL_FLOAT, false,
- TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
- checkGlError("glVertexAttribPointer maTextureHandle");
- GLES20.glEnableVertexAttribArray(maTextureHandle);
- checkGlError("glEnableVertexAttribArray maTextureHandle");
-
- Matrix.setIdentityM(mMVPMatrix, 0);
- GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mMVPMatrix, 0);
- GLES20.glUniformMatrix4fv(muSTMatrixHandle, 1, false, mSTMatrix, 0);
-
- GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
- checkGlError("glDrawArrays");
- GLES20.glFinish();
-
+ mTextureRender.drawFrame(mSurfaceTexture);
}
public void onSurfaceChanged(GL10 glUnused, int width, int height) {
}
public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {
- mProgram = createProgram(mVertexShader, mFragmentShader);
- if (mProgram == 0) {
- return;
- }
- maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
- checkGlError("glGetAttribLocation aPosition");
- if (maPositionHandle == -1) {
- throw new RuntimeException("Could not get attrib location for aPosition");
- }
- maTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTextureCoord");
- checkGlError("glGetAttribLocation aTextureCoord");
- if (maTextureHandle == -1) {
- throw new RuntimeException("Could not get attrib location for aTextureCoord");
- }
-
- muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
- checkGlError("glGetUniformLocation uMVPMatrix");
- if (muMVPMatrixHandle == -1) {
- throw new RuntimeException("Could not get attrib location for uMVPMatrix");
- }
-
- muSTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uSTMatrix");
- checkGlError("glGetUniformLocation uSTMatrix");
- if (muSTMatrixHandle == -1) {
- throw new RuntimeException("Could not get attrib location for uSTMatrix");
- }
-
-
- int[] textures = new int[1];
- GLES20.glGenTextures(1, textures, 0);
-
- mTextureID = textures[0];
- GLES20.glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTextureID);
- checkGlError("glBindTexture mTextureID");
-
- GLES20.glTexParameterf(GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER,
- GLES20.GL_NEAREST);
- GLES20.glTexParameterf(GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER,
- GLES20.GL_LINEAR);
+ mTextureRender.surfaceCreated();
/*
* Create the SurfaceTexture that will feed this textureID,
* and pass it to the MediaPlayer
*/
- mSurface = new SurfaceTexture(mTextureID);
- mSurface.setOnFrameAvailableListener(this);
+ mSurfaceTexture = new SurfaceTexture(mTextureRender.getTextureId());
+ mSurfaceTexture.setOnFrameAvailableListener(this);
- Surface surface = new Surface(mSurface);
+ Surface surface = new Surface(mSurfaceTexture);
mMediaPlayer.setSurface(surface);
surface.release();
@@ -252,61 +133,6 @@
synchronized public void onFrameAvailable(SurfaceTexture surface) {
updateSurface = true;
}
-
- private int loadShader(int shaderType, String source) {
- int shader = GLES20.glCreateShader(shaderType);
- if (shader != 0) {
- GLES20.glShaderSource(shader, source);
- GLES20.glCompileShader(shader);
- int[] compiled = new int[1];
- GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
- if (compiled[0] == 0) {
- Log.e(TAG, "Could not compile shader " + shaderType + ":");
- Log.e(TAG, GLES20.glGetShaderInfoLog(shader));
- GLES20.glDeleteShader(shader);
- shader = 0;
- }
- }
- return shader;
- }
-
- private int createProgram(String vertexSource, String fragmentSource) {
- int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
- if (vertexShader == 0) {
- return 0;
- }
- int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
- if (pixelShader == 0) {
- return 0;
- }
-
- int program = GLES20.glCreateProgram();
- if (program != 0) {
- GLES20.glAttachShader(program, vertexShader);
- checkGlError("glAttachShader");
- GLES20.glAttachShader(program, pixelShader);
- checkGlError("glAttachShader");
- GLES20.glLinkProgram(program);
- int[] linkStatus = new int[1];
- GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
- if (linkStatus[0] != GLES20.GL_TRUE) {
- Log.e(TAG, "Could not link program: ");
- Log.e(TAG, GLES20.glGetProgramInfoLog(program));
- GLES20.glDeleteProgram(program);
- program = 0;
- }
- }
- return program;
- }
-
- private void checkGlError(String op) {
- int error;
- while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
- Log.e(TAG, op + ": glError " + error);
- throw new RuntimeException(op + ": glError " + error);
- }
- }
-
} // End of class VideoRender.
} // End of class VideoSurfaceView.
diff --git a/tests/tests/media/src/android/media/cts/Vp8EncoderTest.java b/tests/tests/media/src/android/media/cts/Vp8EncoderTest.java
new file mode 100644
index 0000000..308fb98
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/Vp8EncoderTest.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.cts;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaFormat;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import com.android.cts.media.R;
+
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+
+/**
+ * Basic verification test for vp8 encoder.
+ *
+ * A raw yv12 stream is encoded and written to an IVF
+ * file, which is later decoded by vp8 decoder to verify
+ * frames are at least decodable.
+ */
+public class Vp8EncoderTest extends AndroidTestCase {
+
+ private static final String TAG = "VP8EncoderTest";
+ private static final String VP8_MIME = "video/x-vnd.on2.vp8";
+ private static final String VPX_DECODER_NAME = "OMX.google.vpx.decoder";
+ private static final String VPX_ENCODER_NAME = "OMX.google.vpx.encoder";
+ private static final String BASIC_IVF = "video_176x144_vp8_basic.ivf";
+ private static final long DEFAULT_TIMEOUT_US = 5000;
+
+ private Resources mResources;
+ private MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo();
+ private ByteBuffer[] mInputBuffers;
+ private ByteBuffer[] mOutputBuffers;
+
+ @Override
+ public void setContext(Context context) {
+ super.setContext(context);
+ mResources = mContext.getResources();
+ }
+
+ /**
+ * A basic test for VP8 encoder.
+ *
+ * Encodes a raw stream with default configuration options,
+ * and then decodes it to verify the bitstream.
+ */
+ public void testBasic() throws Exception {
+ encode(BASIC_IVF,
+ R.raw.video_176x144_yv12,
+ 176, // width
+ 144, // height
+ 30); // framerate
+ decode(BASIC_IVF);
+ }
+
+
+ /**
+ * A basic check if an encoded stream is decodable.
+ *
+ * The most basic confirmation we can get about a frame
+ * being properly encoded is trying to decode it.
+ * (Especially in realtime mode encode output is non-
+ * deterministic, therefore a more thorough check like
+ * md5 sum comparison wouldn't work.)
+ *
+ * Indeed, MediaCodec will raise an IllegalStateException
+ * whenever vp8 decoder fails to decode a frame, and
+ * this test uses that fact to verify the bitstream.
+ *
+ * @param filename The name of the IVF file containing encoded bitsream.
+ */
+ private void decode(String filename) throws Exception {
+ IvfReader ivf = null;
+ try {
+ ivf = new IvfReader(filename);
+ int frameWidth = ivf.getWidth();
+ int frameHeight = ivf.getHeight();
+ int frameCount = ivf.getFrameCount();
+
+ assertTrue(frameWidth > 0);
+ assertTrue(frameHeight > 0);
+ assertTrue(frameCount > 0);
+
+ MediaFormat format = MediaFormat.createVideoFormat(VP8_MIME,
+ ivf.getWidth(),
+ ivf.getHeight());
+
+ Log.d(TAG, "Creating decoder");
+ MediaCodec decoder = MediaCodec.createByCodecName(VPX_DECODER_NAME);
+ decoder.configure(format,
+ null, // surface
+ null, // crypto
+ 0); // flags
+ decoder.start();
+
+ mInputBuffers = decoder.getInputBuffers();
+ mOutputBuffers = decoder.getOutputBuffers();
+
+ // decode loop
+ int frameIndex = 0;
+ boolean sawOutputEOS = false;
+ boolean sawInputEOS = false;
+
+ while (!sawOutputEOS) {
+ if (!sawInputEOS) {
+ int inputBufIndex = decoder.dequeueInputBuffer(DEFAULT_TIMEOUT_US);
+ if (inputBufIndex >= 0) {
+ byte[] frame = ivf.readFrame(frameIndex);
+
+ if (frameIndex == frameCount - 1) {
+ sawInputEOS = true;
+ }
+
+ mInputBuffers[inputBufIndex].clear();
+ mInputBuffers[inputBufIndex].put(frame);
+ mInputBuffers[inputBufIndex].rewind();
+
+ Log.d(TAG, "Decoding frame at index " + frameIndex);
+ try {
+ decoder.queueInputBuffer(
+ inputBufIndex,
+ 0, // offset
+ frame.length,
+ frameIndex,
+ sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
+ } catch (IllegalStateException ise) {
+ //That is all what is passed from MediaCodec in case of
+ //decode failure.
+ fail("Failed to decode frame at index " + frameIndex);
+ }
+ frameIndex++;
+ }
+ }
+
+ int result = decoder.dequeueOutputBuffer(mBufferInfo, DEFAULT_TIMEOUT_US);
+ if (result >= 0) {
+ int outputBufIndex = result;
+ if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+ sawOutputEOS = true;
+ }
+ decoder.releaseOutputBuffer(outputBufIndex, false);
+ } else if (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+ mOutputBuffers = decoder.getOutputBuffers();
+ }
+ }
+ decoder.stop();
+ decoder.release();
+ } finally {
+ if (ivf != null) {
+ ivf.close();
+ }
+ }
+ }
+
+ /**
+ * A basic vp8 encode loop.
+ *
+ * MediaCodec will raise an IllegalStateException
+ * whenever vp8 encoder fails to encode a frame.
+ *
+ * In addition to that written IVF file can be tested
+ * to be decodable in order to verify the bitstream produced.
+ *
+ * Color format of input file should be YUV420, and frameWidth,
+ * frameHeight should be supplied correctly as raw input file doesn't
+ * include any header data.
+ *
+ * @param outputFilename The name of the IVF file to write encoded bitsream
+ * @param rawInputFd File descriptor for the raw input file (YUV420)
+ * @param frameWidth Frame width of input file
+ * @param frameHeight Frame height of input file
+ * @param frameRate Frame rate of input file in frames per second
+ */
+ private void encode(String outputFilename, int rawInputFd,
+ int frameWidth, int frameHeight, int frameRate) throws Exception {
+ int frameSize = frameWidth * frameHeight * 3 / 2;
+
+
+ // Create a media format signifying desired output
+ MediaFormat format = MediaFormat.createVideoFormat(VP8_MIME, frameWidth, frameHeight);
+ format.setInteger(MediaFormat.KEY_BIT_RATE, 100000);
+ format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+ CodecCapabilities.COLOR_FormatYUV420Planar);
+ format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
+
+ Log.d(TAG, "Creating encoder");
+ MediaCodec encoder;
+ encoder = MediaCodec.createByCodecName(VPX_ENCODER_NAME);
+ encoder.configure(format,
+ null, // surface
+ null, // crypto
+ MediaCodec.CONFIGURE_FLAG_ENCODE);
+ encoder.start();
+
+ mInputBuffers = encoder.getInputBuffers();
+ mOutputBuffers = encoder.getOutputBuffers();
+
+ InputStream rawStream = null;
+ IvfWriter ivf = null;
+
+ try {
+ rawStream = mResources.openRawResource(rawInputFd);
+ ivf = new IvfWriter(outputFilename, frameWidth, frameHeight);
+ // encode loop
+ long presentationTimeUs = 0;
+ int frameIndex = 0;
+ boolean sawInputEOS = false;
+ boolean sawOutputEOS = false;
+
+ while (!sawOutputEOS) {
+ if (!sawInputEOS) {
+ int inputBufIndex = encoder.dequeueInputBuffer(DEFAULT_TIMEOUT_US);
+ if (inputBufIndex >= 0) {
+ byte[] frame = new byte[frameSize];
+ int bytesRead = rawStream.read(frame);
+
+ if (bytesRead == -1) {
+ sawInputEOS = true;
+ bytesRead = 0;
+ }
+
+ mInputBuffers[inputBufIndex].clear();
+ mInputBuffers[inputBufIndex].put(frame);
+ mInputBuffers[inputBufIndex].rewind();
+
+ presentationTimeUs = (frameIndex * 1000000) / frameRate;
+ Log.d(TAG, "Encoding frame at index " + frameIndex);
+ encoder.queueInputBuffer(
+ inputBufIndex,
+ 0, // offset
+ bytesRead, // size
+ presentationTimeUs,
+ sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
+
+ frameIndex++;
+ }
+ }
+
+ int result = encoder.dequeueOutputBuffer(mBufferInfo, DEFAULT_TIMEOUT_US);
+ if (result >= 0) {
+ int outputBufIndex = result;
+ byte[] buffer = new byte[mBufferInfo.size];
+ mOutputBuffers[outputBufIndex].rewind();
+ mOutputBuffers[outputBufIndex].get(buffer, 0, mBufferInfo.size);
+
+ if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+ sawOutputEOS = true;
+ } else {
+ ivf.writeFrame(buffer, mBufferInfo.presentationTimeUs);
+ }
+ encoder.releaseOutputBuffer(outputBufIndex,
+ false); // render
+ } else if (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+ mOutputBuffers = encoder.getOutputBuffers();
+ }
+ }
+
+ encoder.stop();
+ encoder.release();
+ } finally {
+ if (ivf != null) {
+ ivf.close();
+ }
+
+ if (rawStream != null) {
+ rawStream.close();
+ }
+ }
+ }
+}
diff --git a/tests/tests/nativeopengl/Android.mk b/tests/tests/nativeopengl/Android.mk
new file mode 100644
index 0000000..dd19548
--- /dev/null
+++ b/tests/tests/nativeopengl/Android.mk
@@ -0,0 +1,41 @@
+# Copyright 2012 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsNativeOpenGLTestCases
+
+# Don't include this package in any target.
+LOCAL_MODULE_TAGS := optional
+
+# When built, explicitly put it in the data partition.
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+# All tests should include android.test.runner.
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner ctswrappedgtest
+
+LOCAL_JNI_SHARED_LIBRARIES := libnativeopengltests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_GTEST_PACKAGE)
+
+# Include the associated library's makefile.
+include $(LOCAL_PATH)/libnativeopengltests/Android.mk
diff --git a/tests/tests/nativeopengl/AndroidManifest.xml b/tests/tests/nativeopengl/AndroidManifest.xml
new file mode 100644
index 0000000..52157b4
--- /dev/null
+++ b/tests/tests/nativeopengl/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.opengl.cts">
+
+ <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <activity android:name="com.android.opengl.cts.GLTestActivity"/>
+ </application>
+
+ <!-- This is a self-instrumenting test package. -->
+ <instrumentation android:name="GLTestInstrumentation"
+ android:targetPackage="com.android.opengl.cts"
+ android:label="Native OpenGL tests">
+ </instrumentation>
+</manifest>
diff --git a/tests/tests/nativeopengl/libnativeopengltests/Android.mk b/tests/tests/nativeopengl/libnativeopengltests/Android.mk
new file mode 100644
index 0000000..b6ca1cb
--- /dev/null
+++ b/tests/tests/nativeopengl/libnativeopengltests/Android.mk
@@ -0,0 +1,48 @@
+# Copyright 2012 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# This is the shared library included by the JNI test app.
+#
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libnativeopengltests
+
+# Don't include this package in any configuration by default.
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_C_INCLUDES := $(JNI_H_INCLUDE) \
+ bionic \
+ bionic/libstdc++/include \
+ external/gtest/include \
+ external/stlport/stlport
+
+LOCAL_SRC_FILES := \
+ register.cpp \
+ GLTestHelper.cpp \
+ android_test_wrappedgtest_WrappedGTestActivity.cpp \
+ com_android_opengl_cts_GLTestActivity.cpp \
+ tests/GLTest_test.cpp
+
+LOCAL_SHARED_LIBRARIES := libEGL \
+ libGLESv2 \
+ libstlport \
+ libandroid
+
+LOCAL_STATIC_LIBRARIES := libgtest
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/tests/tests/nativeopengl/libnativeopengltests/GLTestHelper.cpp b/tests/tests/nativeopengl/libnativeopengltests/GLTestHelper.cpp
new file mode 100644
index 0000000..ed527a6
--- /dev/null
+++ b/tests/tests/nativeopengl/libnativeopengltests/GLTestHelper.cpp
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "GLTestHelper.h"
+
+using namespace android;
+
+ANativeWindow* GLTestHelper::mWindow;
+
+ANativeWindow* GLTestHelper::getWindow() {
+ return mWindow;
+}
+
+void GLTestHelper::setWindow(ANativeWindow* value) {
+ mWindow = value;
+}
diff --git a/tests/tests/nativeopengl/libnativeopengltests/GLTestHelper.h b/tests/tests/nativeopengl/libnativeopengltests/GLTestHelper.h
new file mode 100644
index 0000000..c0775f0
--- /dev/null
+++ b/tests/tests/nativeopengl/libnativeopengltests/GLTestHelper.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_GL_TEST_HELPER_H
+#define ANDROID_GL_TEST_HELPER_H
+
+#include <stdint.h>
+#include <sys/types.h>
+#include <android/native_window.h>
+#include <utils/RefBase.h>
+
+using namespace android;
+
+class GLTestHelper
+{
+private:
+ static ANativeWindow* mWindow;
+public:
+ static ANativeWindow* getWindow();
+ static void setWindow(ANativeWindow* value);
+};
+
+
+
+#endif // ANDROID_GL_TEST_HELPER_H
diff --git a/tests/tests/nativeopengl/libnativeopengltests/android_test_wrappedgtest_WrappedGTestActivity.cpp b/tests/tests/nativeopengl/libnativeopengltests/android_test_wrappedgtest_WrappedGTestActivity.cpp
new file mode 100644
index 0000000..5e1c30e
--- /dev/null
+++ b/tests/tests/nativeopengl/libnativeopengltests/android_test_wrappedgtest_WrappedGTestActivity.cpp
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <android/log.h>
+#include <gtest/gtest.h>
+#include <jni.h>
+
+using namespace testing;
+
+class GTestListener : public EmptyTestEventListener {
+public:
+ GTestListener(JNIEnv *env, jobject activity)
+ : mActivity(activity), mEnv(env) {
+
+ jclass clazz = env->FindClass(
+ "android/test/wrappedgtest/WrappedGTestActivity");
+ mSendStatusID = env->GetMethodID(clazz, "sendStatus",
+ "(Ljava/lang/String;)V");
+ mMessageBuffer = new char[2048];
+ }
+
+ ~GTestListener() {
+ delete[] mMessageBuffer;
+ }
+
+private:
+ jobject mActivity;
+ JNIEnv * mEnv;
+ jmethodID mSendStatusID;
+ char * mMessageBuffer;
+
+ virtual void OnTestIterationStart(const UnitTest& unit_test,
+ int iteration) {
+ snprintf(mMessageBuffer, sizeof(char) * 2048,
+ "[==========] Running %i tests from %i test cases.",
+ unit_test.test_to_run_count(),
+ unit_test.test_case_to_run_count());
+
+ mEnv->CallVoidMethod(mActivity, mSendStatusID,
+ mEnv->NewStringUTF(mMessageBuffer));
+ }
+
+ virtual void OnTestStart(const TestInfo& test_info) {
+ snprintf(mMessageBuffer, sizeof(char) * 2048, "[ RUN ] %s.%s",
+ test_info.test_case_name(), test_info.name());
+
+ mEnv->CallVoidMethod(mActivity, mSendStatusID,
+ mEnv->NewStringUTF(mMessageBuffer));
+ }
+
+ virtual void OnTestPartResult(const TestPartResult& result) {
+ if (result.type() == TestPartResult::kSuccess) {
+ return;
+ }
+
+ snprintf(mMessageBuffer, sizeof(char) * 2048, "%s:%i: Failure\n%s",
+ result.file_name(), result.line_number(), result.message());
+
+ mEnv->CallVoidMethod(mActivity, mSendStatusID,
+ mEnv->NewStringUTF(mMessageBuffer));
+ }
+
+ virtual void OnTestEnd(const TestInfo& test_info) {
+ const char * result = test_info.result()->Passed() ?
+ "[ OK ] " : "[ FAILED ] ";
+
+ snprintf(mMessageBuffer, sizeof(char) * 2048, "%s%s.%s (%lli ms)",
+ result, test_info.test_case_name(), test_info.name(),
+ test_info.result()->elapsed_time());
+
+ mEnv->CallVoidMethod(mActivity, mSendStatusID,
+ mEnv->NewStringUTF(mMessageBuffer));
+ }
+
+ virtual void OnTestIterationEnd(const UnitTest& unit_test, int iteration) {
+ snprintf(mMessageBuffer, sizeof(char) * 2048,
+ "[==========] %i tests from %i test cases ran. (%lli ms total)",
+ unit_test.test_to_run_count(),
+ unit_test.test_case_to_run_count(), unit_test.elapsed_time());
+
+ mEnv->CallVoidMethod(mActivity, mSendStatusID,
+ mEnv->NewStringUTF(mMessageBuffer));
+ }
+};
+
+static jboolean WrappedGTestActivity_runTests(JNIEnv *env, jobject obj,
+ jobject activity) {
+ // init gtest with no args
+ int argc = 0;
+ InitGoogleTest(&argc, (char**)NULL);
+
+ // delete the default listener
+ TestEventListeners& listeners = UnitTest::GetInstance()->listeners();
+ delete listeners.Release(listeners.default_result_printer());
+
+ // add custom listener
+ GTestListener * listener = new GTestListener(env, activity);
+ listeners.Append(listener);
+
+ // run tests
+ int result = RUN_ALL_TESTS();
+
+ delete listener;
+ return result;
+};
+
+static JNINativeMethod methods[] = {
+ // name, signature, function
+ { "runTests", "(Landroid/test/wrappedgtest/WrappedGTestActivity;)I", (void*)WrappedGTestActivity_runTests },
+};
+
+int register_WrappedGTestActivity(JNIEnv *env) {
+ return env->RegisterNatives(
+ env->FindClass("android/test/wrappedgtest/WrappedGTestActivity"),
+ methods, sizeof(methods) / sizeof(JNINativeMethod));
+};
diff --git a/tests/tests/nativeopengl/libnativeopengltests/com_android_opengl_cts_GLTestActivity.cpp b/tests/tests/nativeopengl/libnativeopengltests/com_android_opengl_cts_GLTestActivity.cpp
new file mode 100644
index 0000000..8544f2a
--- /dev/null
+++ b/tests/tests/nativeopengl/libnativeopengltests/com_android_opengl_cts_GLTestActivity.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+#include <jni.h>
+
+#include "GLTestHelper.h"
+#include <android/native_window_jni.h>
+
+using namespace android;
+
+static void GLTestActivity_setSurface(JNIEnv *env, jobject obj,
+ jobject surface) {
+ ANativeWindow* window = ANativeWindow_fromSurface(env, surface);
+ GLTestHelper::setWindow(window);
+};
+
+static JNINativeMethod methods[] = {
+ // name, signature, function
+ { "setSurface", "(Landroid/view/Surface;)V", (void*)GLTestActivity_setSurface },
+};
+
+int register_GLTestActivity(JNIEnv *env) {
+ return env->RegisterNatives(
+ env->FindClass("com/android/opengl/cts/GLTestActivity"),
+ methods, sizeof(methods) / sizeof(JNINativeMethod));
+};
diff --git a/tests/tests/nativeopengl/libnativeopengltests/register.cpp b/tests/tests/nativeopengl/libnativeopengltests/register.cpp
new file mode 100644
index 0000000..97a8bd4
--- /dev/null
+++ b/tests/tests/nativeopengl/libnativeopengltests/register.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <jni.h>
+#include <stdlib.h>
+
+/*
+ * This function is called automatically by the system when this
+ * library is loaded. We use it to register all our native functions,
+ * which is the recommended practice for Android.
+ */
+jint JNI_OnLoad(JavaVM *vm, void *reserved) {
+ JNIEnv *env = NULL;
+
+ if (vm->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) {
+ return JNI_ERR;
+ }
+
+ extern int register_WrappedGTestActivity(JNIEnv *);
+ if (register_WrappedGTestActivity(env)) {
+ return JNI_ERR;
+ }
+
+ extern int register_GLTestActivity(JNIEnv *);
+ if (register_GLTestActivity(env)) {
+ return JNI_ERR;
+ }
+
+ return JNI_VERSION_1_4;
+}
diff --git a/tests/tests/nativeopengl/libnativeopengltests/tests/GLTest_test.cpp b/tests/tests/nativeopengl/libnativeopengltests/tests/GLTest_test.cpp
new file mode 100644
index 0000000..37bfcb5
--- /dev/null
+++ b/tests/tests/nativeopengl/libnativeopengltests/tests/GLTest_test.cpp
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#include <android/native_window.h>
+
+#include <gtest/gtest.h>
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+
+#include "GLTestHelper.h"
+
+
+namespace android {
+
+class GLTest : public ::testing::Test {
+
+protected:
+
+ GLTest():
+ mEglDisplay(EGL_NO_DISPLAY),
+ mEglSurface(EGL_NO_SURFACE),
+ mEglContext(EGL_NO_CONTEXT) {
+ }
+
+
+ virtual void SetUp() {
+ mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ ASSERT_NE(EGL_NO_DISPLAY, mEglDisplay);
+
+ EGLint majorVersion;
+ EGLint minorVersion;
+ EXPECT_TRUE(eglInitialize(mEglDisplay, &majorVersion, &minorVersion));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+
+ EGLint numConfigs = 0;
+ EXPECT_TRUE(eglChooseConfig(mEglDisplay, getConfigAttribs(), &mGlConfig,
+ 1, &numConfigs));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+
+ char* displaySecsEnv = getenv("GLTEST_DISPLAY_SECS");
+ if (displaySecsEnv != NULL) {
+ mDisplaySecs = atoi(displaySecsEnv);
+ if (mDisplaySecs < 0) {
+ mDisplaySecs = 0;
+ }
+ } else {
+ mDisplaySecs = 0;
+ }
+
+ if (mDisplaySecs > 0) {
+ mEglSurface = eglCreateWindowSurface(mEglDisplay, mGlConfig,
+ GLTestHelper::getWindow(), NULL);
+ } else {
+ EGLint pbufferAttribs[] = {
+ EGL_WIDTH, getSurfaceWidth(),
+ EGL_HEIGHT, getSurfaceHeight(),
+ EGL_NONE };
+
+ mEglSurface = eglCreatePbufferSurface(mEglDisplay, mGlConfig,
+ pbufferAttribs);
+ }
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ ASSERT_NE(EGL_NO_SURFACE, mEglSurface);
+
+ mEglContext = eglCreateContext(mEglDisplay, mGlConfig, EGL_NO_CONTEXT,
+ getContextAttribs());
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ ASSERT_NE(EGL_NO_CONTEXT, mEglContext);
+
+ EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
+ mEglContext));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+
+ EGLint w, h;
+ EXPECT_TRUE(eglQuerySurface(mEglDisplay, mEglSurface, EGL_WIDTH, &w));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ EXPECT_TRUE(eglQuerySurface(mEglDisplay, mEglSurface, EGL_HEIGHT, &h));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+
+ glViewport(0, 0, w, h);
+ ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+ }
+
+ virtual void TearDown() {
+ // Display the result
+ if (mDisplaySecs > 0 && mEglSurface != EGL_NO_SURFACE) {
+ eglSwapBuffers(mEglDisplay, mEglSurface);
+ sleep(mDisplaySecs);
+ }
+
+ if (mEglContext != EGL_NO_CONTEXT) {
+ eglDestroyContext(mEglDisplay, mEglContext);
+ }
+ if (mEglSurface != EGL_NO_SURFACE) {
+ eglDestroySurface(mEglDisplay, mEglSurface);
+ }
+ if (mEglDisplay != EGL_NO_DISPLAY) {
+ eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE,
+ EGL_NO_CONTEXT);
+ eglTerminate(mEglDisplay);
+ }
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ }
+
+ virtual EGLint const* getConfigAttribs() {
+ static EGLint sDefaultConfigAttribs[] = {
+ EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
+ EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+ EGL_RED_SIZE, 8,
+ EGL_GREEN_SIZE, 8,
+ EGL_BLUE_SIZE, 8,
+ EGL_ALPHA_SIZE, 8,
+ EGL_DEPTH_SIZE, 16,
+ EGL_STENCIL_SIZE, 8,
+ EGL_NONE };
+
+ return sDefaultConfigAttribs;
+ }
+
+ virtual EGLint const* getContextAttribs() {
+ static EGLint sDefaultContextAttribs[] = {
+ EGL_CONTEXT_CLIENT_VERSION, 2,
+ EGL_NONE };
+
+ return sDefaultContextAttribs;
+ }
+
+ virtual EGLint getSurfaceWidth() {
+ return 512;
+ }
+
+ virtual EGLint getSurfaceHeight() {
+ return 512;
+ }
+
+ bool checkPixel(GLubyte * actual, GLubyte * expected, int tolerance) {
+ for (int i = 0; i < 4; i++) {
+ if (abs(actual[i] - expected[i]) > tolerance) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ ::testing::AssertionResult AssertPixel(const char* a_expr,
+ const char* e_expr, const char* t_expr, GLubyte * actual,
+ GLubyte * expected, int tolerance) {
+
+ if (checkPixel(actual, expected, tolerance)) {
+ return ::testing::AssertionSuccess();
+ }
+
+ return ::testing::AssertionFailure()
+ << "Pixel comparison failed with tolerance " << tolerance << "\n"
+ << "Actual: r=" << (int)actual[0] << " g=" << (int)actual[1]
+ << " b=" << (int)actual[2] << " a=" << (int)actual[3] << "\n"
+ << "Expected: r=" << (int)expected[0] << " g=" << (int)expected[1]
+ << " b=" << (int)expected[2] << " a=" << (int)expected[3] << "\n";
+ }
+
+ int mDisplaySecs;
+
+ EGLDisplay mEglDisplay;
+ EGLSurface mEglSurface;
+ EGLContext mEglContext;
+ EGLConfig mGlConfig;
+};
+
+TEST_F(GLTest, ClearColorTest) {
+ glClearColor(0.2, 0.2, 0.2, 0.2);
+ glClear(GL_COLOR_BUFFER_BIT);
+ GLubyte expected[4] = { 51, 51, 51, 51 };
+ GLubyte pixel[4];
+ glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel);
+ ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+ ASSERT_PRED_FORMAT3(AssertPixel, pixel, expected, 2);
+}
+
+} // namespace android
diff --git a/tests/tests/nativeopengl/src/com/android/opengl/cts/GLTestActivity.java b/tests/tests/nativeopengl/src/com/android/opengl/cts/GLTestActivity.java
new file mode 100644
index 0000000..1633a93
--- /dev/null
+++ b/tests/tests/nativeopengl/src/com/android/opengl/cts/GLTestActivity.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.opengl.cts;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.os.Bundle;
+import android.test.wrappedgtest.WrappedGTestActivity;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.Surface;
+
+public class GLTestActivity extends WrappedGTestActivity {
+
+ private SurfaceView mSurfaceView;
+ private SurfaceHolder.Callback mHolderCallback = new SurfaceHolder.Callback() {
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+ setSurface(holder.getSurface());
+ }
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ setSurface(holder.getSurface());
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ }
+ };
+
+ public void onCreate(Bundle data) {
+ super.onCreate(data);
+ mSurfaceView = new SurfaceView(this);
+ mSurfaceView.getHolder().addCallback(mHolderCallback);
+ setContentView(mSurfaceView);
+ System.loadLibrary("nativeopengltests");
+ }
+
+ private static native void setSurface(Surface surface);
+}
diff --git a/tests/tests/nativeopengl/src/com/android/opengl/cts/GLTestInstrumentation.java b/tests/tests/nativeopengl/src/com/android/opengl/cts/GLTestInstrumentation.java
new file mode 100644
index 0000000..913f0eb
--- /dev/null
+++ b/tests/tests/nativeopengl/src/com/android/opengl/cts/GLTestInstrumentation.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.opengl.cts;
+
+import android.test.wrappedgtest.WrappedGTestInstrumentation;
+
+/**
+ * adb shell am instrument -w com.android.opengl.cts/.GLTestInstrumentation
+ */
+public class GLTestInstrumentation extends WrappedGTestInstrumentation {
+ public GLTestInstrumentation() {
+ mActivityClass = GLTestActivity.class;
+ }
+}
diff --git a/tests/tests/net/Android.mk b/tests/tests/net/Android.mk
index b327392..a6543b3 100644
--- a/tests/tests/net/Android.mk
+++ b/tests/tests/net/Android.mk
@@ -21,7 +21,9 @@
# and when built explicitly put it in the data partition
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner voip-common
+
+LOCAL_JNI_SHARED_LIBRARIES := libnativedns_jni
# include CtsTestServer as a temporary hack to free net.cts from cts.stub.
LOCAL_SRC_FILES := $(call all-java-files-under, src)
@@ -34,3 +36,5 @@
#LOCAL_SDK_VERSION := current
include $(BUILD_CTS_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/suite/pts/hostTests/ptshostutil/Android.mk b/tests/tests/net/jni/Android.mk
similarity index 64%
copy from suite/pts/hostTests/ptshostutil/Android.mk
copy to tests/tests/net/jni/Android.mk
index 14e786a..75982de 100644
--- a/suite/pts/hostTests/ptshostutil/Android.mk
+++ b/tests/tests/net/jni/Android.mk
@@ -1,4 +1,4 @@
-# Copyright (C) 2012 The Android Open Source Project
+# Copyright (C) 2013 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -12,18 +12,19 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-LOCAL_PATH := $(call my-dir)
+LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_SRC_FILES := \
- $(call all-java-files-under, src)
+LOCAL_MODULE := libnativedns_jni
-LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed-prebuilt ddmlib-prebuilt junit
-
+# Don't include this package in any configuration by default.
LOCAL_MODULE_TAGS := optional
-LOCAL_MODULE := ptshostutil
+LOCAL_SRC_FILES := NativeDnsJni.c
-include $(BUILD_HOST_JAVA_LIBRARY)
+LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
+LOCAL_SHARED_LIBRARIES := libnativehelper liblog
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/tests/tests/net/jni/NativeDnsJni.c b/tests/tests/net/jni/NativeDnsJni.c
new file mode 100644
index 0000000..de9bb67
--- /dev/null
+++ b/tests/tests/net/jni/NativeDnsJni.c
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <arpa/inet.h>
+#include <jni.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <utils/Log.h>
+
+JNIEXPORT jboolean Java_android_net_cts_DnsTest_testNativeDns(JNIEnv* env, jclass class)
+{
+ const char *node = "www.google.com";
+ char *service = NULL;
+ struct addrinfo *answer;
+
+ int res = getaddrinfo(node, service, NULL, &answer);
+ ALOGD("getaddrinfo(www.google.com) gave res=%d (%s)", res, gai_strerror(res));
+ if (res != 0) return JNI_FALSE;
+
+ // check for v4 & v6
+ {
+ int foundv4 = 0;
+ int foundv6 = 0;
+ struct addrinfo *current = answer;
+ while (current != NULL) {
+ char buf[256];
+ if (current->ai_addr->sa_family == AF_INET) {
+ inet_ntop(current->ai_family, &((struct sockaddr_in *)current->ai_addr)->sin_addr,
+ buf, sizeof(buf));
+ foundv4 = 1;
+ ALOGD(" %s", buf);
+ } else if (current->ai_addr->sa_family == AF_INET6) {
+ inet_ntop(current->ai_family, &((struct sockaddr_in6 *)current->ai_addr)->sin6_addr,
+ buf, sizeof(buf));
+ foundv6 = 1;
+ ALOGD(" %s", buf);
+ }
+ current = current->ai_next;
+ }
+
+ freeaddrinfo(answer);
+ answer = NULL;
+ if (foundv4 != 1 || foundv6 != 1) {
+ ALOGD("getaddrinfo(www.google.com) didn't find both v4 and v6");
+ return JNI_FALSE;
+ }
+ }
+
+ node = "ipv6.google.com";
+ res = getaddrinfo(node, service, NULL, &answer);
+ ALOGD("getaddrinfo(ipv6.google.com) gave res=%d", res);
+ if (res != 0) return JNI_FALSE;
+
+ {
+ int foundv4 = 0;
+ int foundv6 = 0;
+ struct addrinfo *current = answer;
+ while (current != NULL) {
+ char buf[256];
+ if (current->ai_addr->sa_family == AF_INET) {
+ inet_ntop(current->ai_family, &((struct sockaddr_in *)current->ai_addr)->sin_addr,
+ buf, sizeof(buf));
+ ALOGD(" %s", buf);
+ foundv4 = 1;
+ } else if (current->ai_addr->sa_family == AF_INET6) {
+ inet_ntop(current->ai_family, &((struct sockaddr_in6 *)current->ai_addr)->sin6_addr,
+ buf, sizeof(buf));
+ ALOGD(" %s", buf);
+ foundv6 = 1;
+ }
+ current = current->ai_next;
+ }
+
+ freeaddrinfo(answer);
+ answer = NULL;
+ if (foundv4 == 1 || foundv6 != 1) {
+ ALOGD("getaddrinfo(ipv6.google.com) didn't find only v6");
+ return JNI_FALSE;
+ }
+ }
+
+ // getnameinfo
+ struct sockaddr_in sa4;
+ sa4.sin_family = AF_INET;
+ sa4.sin_port = 0;
+ inet_pton(AF_INET, "173.252.110.27", &(sa4.sin_addr));
+
+ struct sockaddr_in6 sa6;
+ sa6.sin6_family = AF_INET6;
+ sa6.sin6_port = 0;
+ sa6.sin6_flowinfo = 0;
+ sa6.sin6_scope_id = 0;
+ inet_pton(AF_INET6, "2001:4860:4001:802::1008", &(sa6.sin6_addr));
+
+ char buf[NI_MAXHOST];
+ int flags = NI_NAMEREQD;
+
+ res = getnameinfo((const struct sockaddr*)&sa4, sizeof(sa4), buf, sizeof(buf), NULL, 0, flags);
+ if (res != 0) {
+ ALOGD("getnameinfo(173.252.110.27 (facebook) ) gave error %d (%s)", res, gai_strerror(res));
+ return JNI_FALSE;
+ }
+ if (strstr(buf, "facebook.com") == NULL) {
+ ALOGD("getnameinfo(173.252.110.27 (facebook) ) didn't return facebook.com: %s", buf);
+ return JNI_FALSE;
+ }
+
+ memset(buf, sizeof(buf), 0);
+ res = getnameinfo((const struct sockaddr*)&sa6, sizeof(sa6), buf, sizeof(buf),
+ NULL, 0, flags);
+ if (res != 0) {
+ ALOGD("getnameinfo(2a03:2880:2110:df01:face:b00c::8 (facebook) ) gave error %d (%s)",
+ res, gai_strerror(res));
+ return JNI_FALSE;
+ }
+ if (strstr(buf, "1e100.net") == NULL) {
+ ALOGD("getnameinfo(2a03:2880:2110:df01:face:b00c::8) didn't return facebook.com: %s", buf);
+ return JNI_FALSE;
+ }
+
+ // gethostbyname
+ struct hostent *my_hostent = gethostbyname("www.mit.edu");
+ if (my_hostent == NULL) {
+ ALOGD("gethostbyname(www.mit.edu) gave null response");
+ return JNI_FALSE;
+ }
+ if ((my_hostent->h_addr_list == NULL) || (*my_hostent->h_addr_list == NULL)) {
+ ALOGD("gethostbyname(www.mit.edu) gave 0 addresses");
+ return JNI_FALSE;
+ }
+ {
+ char **current = my_hostent->h_addr_list;
+ while (*current != NULL) {
+ char buf[256];
+ inet_ntop(my_hostent->h_addrtype, *current, buf, sizeof(buf));
+ ALOGD("gethostbyname(www.mit.edu) gave %s", buf);
+ current++;
+ }
+ }
+
+ // gethostbyaddr
+ char addr6[16];
+ inet_pton(AF_INET6, "2001:4b10:bbc::2", addr6);
+ my_hostent = gethostbyaddr(addr6, sizeof(addr6), AF_INET6);
+ if (my_hostent == NULL) {
+ ALOGD("gethostbyaddr(2001:4b10:bbc::2 (bbc) ) gave null response");
+ return JNI_FALSE;
+ }
+ ALOGD("gethostbyaddr(2001:4b10:bbc::2 (bbc) ) gave %s for name",
+ my_hostent->h_name ? my_hostent->h_name : "null");
+ if (my_hostent->h_name == NULL) return JNI_FALSE;
+ return JNI_TRUE;
+}
diff --git a/tests/tests/net/src/android/net/cts/DnsTest.java b/tests/tests/net/src/android/net/cts/DnsTest.java
new file mode 100644
index 0000000..cdd95aa
--- /dev/null
+++ b/tests/tests/net/src/android/net/cts/DnsTest.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.cts;
+
+import android.os.SystemClock;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+
+public class DnsTest extends AndroidTestCase {
+
+ static {
+ System.loadLibrary("nativedns_jni");
+ }
+
+ private static final boolean DBG = false;
+ private static final String TAG = "DnsTest";
+
+ /**
+ * @return true on success
+ */
+ private static native boolean testNativeDns();
+
+ /**
+ * Verify:
+ * DNS works - forwards and backwards, giving ipv4 and ipv6
+ * Test that DNS work on v4 and v6 networks
+ * Test Native dns calls (4)
+ * Todo:
+ * Cache is flushed when we change networks
+ * have per-network caches
+ * No cache when there's no network
+ * Perf - measure size of first and second tier caches and their effect
+ * Assert requires network permission
+ */
+ public void testDnsWorks() {
+ InetAddress addrs[] = {};
+ try {
+ addrs = InetAddress.getAllByName("www.google.com");
+ } catch (UnknownHostException e) {}
+ assertTrue(addrs.length != 0);
+ boolean foundV4 = false, foundV6 = false;
+ for (InetAddress addr : addrs) {
+ if (addr instanceof Inet4Address) foundV4 = true;
+ else if (addr instanceof Inet6Address) foundV6 = true;
+ if (DBG) Log.e(TAG, "www.google.com gave " + addr.toString());
+ }
+ assertTrue(foundV4);
+ assertTrue(foundV6);
+ try {
+ addrs = InetAddress.getAllByName("ipv6.google.com");
+ } catch (UnknownHostException e) {}
+ assertTrue(addrs.length != 0);
+ foundV4 = false;
+ foundV6 = false;
+ for (InetAddress addr : addrs) {
+ if (addr instanceof Inet4Address) foundV4 = true;
+ else if (addr instanceof Inet6Address) foundV6 = true;
+ if (DBG) Log.e(TAG, "ipv6.google.com gave " + addr.toString());
+ }
+ assertTrue(foundV4 == false);
+ assertTrue(foundV6 == true);
+ assertTrue(testNativeDns());
+ }
+
+ private static final String[] URLS = { "www.google.com", "ipv6.google.com", "www.yahoo.com",
+ "facebook.com", "youtube.com", "blogspot.com", "baidu.com", "wikipedia.org",
+// live.com fails rev lookup.
+ "twitter.com", "qq.com", "msn.com", "yahoo.co.jp", "linkedin.com",
+ "taobao.com", "google.co.in", "sina.com.cn", "amazon.com", "wordpress.com",
+ "google.co.uk", "ebay.com", "yandex.ru", "163.com", "google.co.jp", "google.fr",
+ "microsoft.com", "paypal.com", "google.com.br", "flickr.com",
+ "mail.ru", "craigslist.org", "fc2.com", "google.it",
+// "apple.com", fails rev lookup
+ "google.es",
+ "imdb.com", "google.ru", "soho.com", "bbc.co.uk", "vkontakte.ru", "ask.com",
+ "tumblr.com", "weibo.com", "go.com", "xvideos.com", "livejasmin.com", "cnn.com",
+ "youku.com", "blogspot.com", "soso.com", "google.ca", "aol.com", "tudou.com",
+ "xhamster.com", "megaupload.com", "ifeng.com", "zedo.com", "mediafire.com", "ameblo.jp",
+ "pornhub.com", "google.co.id", "godaddy.com", "adobe.com", "rakuten.co.jp", "about.com",
+ "espn.go.com", "4shared.com", "alibaba.com","ebay.de", "yieldmanager.com",
+ "wordpress.org", "livejournal.com", "google.com.tr", "google.com.mx", "renren.com",
+ "livedoor.com", "google.com.au", "youporn.com", "uol.com.br", "cnet.com", "conduit.com",
+ "google.pl", "myspace.com", "nytimes.com", "ebay.co.uk", "chinaz.com", "hao123.com",
+ "thepiratebay.org", "doubleclick.com", "alipay.com", "netflix.com", "cnzz.com",
+ "huffingtonpost.com", "twitpic.com", "weather.com", "babylon.com", "amazon.de",
+ "dailymotion.com", "orkut.com", "orkut.com.br", "google.com.sa", "odnoklassniki.ru",
+ "amazon.co.jp", "google.nl", "goo.ne.jp", "stumbleupon.com", "tube8.com", "tmall.com",
+ "imgur.com", "globo.com", "secureserver.net", "fileserve.com", "tianya.cn", "badoo.com",
+ "ehow.com", "photobucket.com", "imageshack.us", "xnxx.com", "deviantart.com",
+ "filestube.com", "addthis.com", "douban.com", "vimeo.com", "sogou.com",
+ "stackoverflow.com", "reddit.com", "dailymail.co.uk", "redtube.com", "megavideo.com",
+ "taringa.net", "pengyou.com", "amazon.co.uk", "fbcdn.net", "aweber.com", "spiegel.de",
+ "rapidshare.com", "mixi.jp", "360buy.com", "google.cn", "digg.com", "answers.com",
+ "bit.ly", "indiatimes.com", "skype.com", "yfrog.com", "optmd.com", "google.com.eg",
+ "google.com.pk", "58.com", "hotfile.com", "google.co.th",
+ "bankofamerica.com", "sourceforge.net", "maktoob.com", "warriorforum.com", "rediff.com",
+ "google.co.za", "56.com", "torrentz.eu", "clicksor.com", "avg.com",
+ "download.com", "ku6.com", "statcounter.com", "foxnews.com", "google.com.ar",
+ "nicovideo.jp", "reference.com", "liveinternet.ru", "ucoz.ru", "xinhuanet.com",
+ "xtendmedia.com", "naver.com", "youjizz.com", "domaintools.com", "sparkstudios.com",
+ "rambler.ru", "scribd.com", "kaixin001.com", "mashable.com", "adultfirendfinder.com",
+ "files.wordpress.com", "guardian.co.uk", "bild.de", "yelp.com", "wikimedia.org",
+ "chase.com", "onet.pl", "ameba.jp", "pconline.com.cn", "free.fr", "etsy.com",
+ "typepad.com", "youdao.com", "megaclick.com", "digitalpoint.com", "blogfa.com",
+ "salesforce.com", "adf.ly", "ganji.com", "wikia.com", "archive.org", "terra.com.br",
+ "w3schools.com", "ezinearticles.com", "wjs.com", "google.com.my", "clickbank.com",
+ "squidoo.com", "hulu.com", "repubblica.it", "google.be", "allegro.pl", "comcast.net",
+ "narod.ru", "zol.com.cn", "orange.fr", "soufun.com", "hatena.ne.jp", "google.gr",
+ "in.com", "techcrunch.com", "orkut.co.in", "xunlei.com",
+ "reuters.com", "google.com.vn", "hostgator.com", "kaskus.us", "espncricinfo.com",
+ "hootsuite.com", "qiyi.com", "gmx.net", "xing.com", "php.net", "soku.com", "web.de",
+ "libero.it", "groupon.com", "51.la", "slideshare.net", "booking.com", "seesaa.net",
+ "126.com", "telegraph.co.uk", "wretch.cc", "twimg.com", "rutracker.org", "angege.com",
+ "nba.com", "dell.com", "leboncoin.fr", "people.com", "google.com.tw", "walmart.com",
+ "daum.net", "2ch.net", "constantcontact.com", "nifty.com", "mywebsearch.com",
+ "tripadvisor.com", "google.se", "paipai.com", "google.com.ua", "ning.com", "hp.com",
+ "google.at", "joomla.org", "icio.us", "hudong.com", "csdn.net", "getfirebug.com",
+ "ups.com", "cj.com", "google.ch", "camzap.com", "wordreference.com", "tagged.com",
+ "wp.pl", "mozilla.com", "google.ru", "usps.com", "china.com", "themeforest.net",
+ "search-results.com", "tribalfusion.com", "thefreedictionary.com", "isohunt.com",
+ "linkwithin.com", "cam4.com", "plentyoffish.com", "wellsfargo.com", "metacafe.com",
+ "depositfiles.com", "freelancer.com", "opendns.com", "homeway.com", "engadget.com",
+ "10086.cn", "360.cn", "marca.com", "dropbox.com", "ign.com", "match.com", "google.pt",
+ "facemoods.com", "hardsextube.com", "google.com.ph", "lockerz.com", "istockphoto.com",
+ "partypoker.com", "netlog.com", "outbrain.com", "elpais.com", "fiverr.com",
+ "biglobe.ne.jp", "corriere.it", "love21cn.com", "yesky.com", "spankwire.com",
+ "ig.com.br", "imagevenue.com", "hubpages.com", "google.co.ve"};
+
+// TODO - this works, but is slow and cts doesn't do anything with the result.
+// Maybe require a min performance, a min cache size (detectable) and/or move
+// to perf testing
+ private static final int LOOKUP_COUNT_GOAL = URLS.length;
+ public void skiptestDnsPerf() {
+ ArrayList<String> results = new ArrayList<String>();
+ int failures = 0;
+ try {
+ for (int numberOfUrls = URLS.length; numberOfUrls > 0; numberOfUrls--) {
+ failures = 0;
+ int iterationLimit = LOOKUP_COUNT_GOAL / numberOfUrls;
+ long startTime = SystemClock.elapsedRealtimeNanos();
+ for (int iteration = 0; iteration < iterationLimit; iteration++) {
+ for (int urlIndex = 0; urlIndex < numberOfUrls; urlIndex++) {
+ try {
+ InetAddress addr = InetAddress.getByName(URLS[urlIndex]);
+ } catch (UnknownHostException e) {
+ Log.e(TAG, "failed first lookup of " + URLS[urlIndex]);
+ failures++;
+ try {
+ InetAddress addr = InetAddress.getByName(URLS[urlIndex]);
+ } catch (UnknownHostException ee) {
+ failures++;
+ Log.e(TAG, "failed SECOND lookup of " + URLS[urlIndex]);
+ }
+ }
+ }
+ }
+ long endTime = SystemClock.elapsedRealtimeNanos();
+ float nsPer = ((float)(endTime-startTime) / iterationLimit) / numberOfUrls/ 1000;
+ String thisResult = new String("getByName for " + numberOfUrls + " took " +
+ (endTime - startTime)/1000 + "(" + nsPer + ") with " +
+ failures + " failures\n");
+ Log.d(TAG, thisResult);
+ results.add(thisResult);
+ }
+ // build up a list of addresses
+ ArrayList<byte[]> addressList = new ArrayList<byte[]>();
+ for (String url : URLS) {
+ try {
+ InetAddress addr = InetAddress.getByName(url);
+ addressList.add(addr.getAddress());
+ } catch (UnknownHostException e) {
+ Log.e(TAG, "Exception making reverseDNS list: " + e.toString());
+ }
+ }
+ for (int numberOfAddrs = addressList.size(); numberOfAddrs > 0; numberOfAddrs--) {
+ int iterationLimit = LOOKUP_COUNT_GOAL / numberOfAddrs;
+ failures = 0;
+ long startTime = SystemClock.elapsedRealtimeNanos();
+ for (int iteration = 0; iteration < iterationLimit; iteration++) {
+ for (int addrIndex = 0; addrIndex < numberOfAddrs; addrIndex++) {
+ try {
+ InetAddress addr = InetAddress.getByAddress(addressList.get(addrIndex));
+ String hostname = addr.getHostName();
+ } catch (UnknownHostException e) {
+ failures++;
+ Log.e(TAG, "Failure doing reverse DNS lookup: " + e.toString());
+ try {
+ InetAddress addr =
+ InetAddress.getByAddress(addressList.get(addrIndex));
+ String hostname = addr.getHostName();
+
+ } catch (UnknownHostException ee) {
+ failures++;
+ Log.e(TAG, "Failure doing SECOND reverse DNS lookup: " +
+ ee.toString());
+ }
+ }
+ }
+ }
+ long endTime = SystemClock.elapsedRealtimeNanos();
+ float nsPer = ((endTime-startTime) / iterationLimit) / numberOfAddrs / 1000;
+ String thisResult = new String("getHostName for " + numberOfAddrs + " took " +
+ (endTime - startTime)/1000 + "(" + nsPer + ") with " +
+ failures + " failures\n");
+ Log.d(TAG, thisResult);
+ results.add(thisResult);
+ }
+ for (String result : results) Log.d(TAG, result);
+
+ InetAddress exit = InetAddress.getByName("exitrightnow.com");
+ Log.e(TAG, " exit address= "+exit.toString());
+
+ } catch (Exception e) {
+ Log.e(TAG, "bad URL in testDnsPerf: " + e.toString());
+ }
+ }
+}
diff --git a/tests/tests/net/src/android/net/cts/TrafficStatsTest.java b/tests/tests/net/src/android/net/cts/TrafficStatsTest.java
index a5bbd98..89933bf 100644
--- a/tests/tests/net/src/android/net/cts/TrafficStatsTest.java
+++ b/tests/tests/net/src/android/net/cts/TrafficStatsTest.java
@@ -42,19 +42,29 @@
TrafficStats.getMobileRxBytes() >= 0);
}
+ long tcpPacketToIpBytes(long packetCount, long bytes) {
+ // ip header + tcp header + data.
+ // Tcp header is mostly 32. Syn has different tcp options -> 40. Don't care.
+ return packetCount * (20 + 32 + bytes);
+ }
+
public void testTrafficStatsForLocalhost() throws IOException {
- long mobileTxPacketsBefore = TrafficStats.getTotalTxPackets();
- long mobileRxPacketsBefore = TrafficStats.getTotalRxPackets();
- long mobileTxBytesBefore = TrafficStats.getTotalTxBytes();
- long mobileRxBytesBefore = TrafficStats.getTotalRxBytes();
+ long mobileTxPacketsBefore = TrafficStats.getMobileTxPackets();
+ long mobileRxPacketsBefore = TrafficStats.getMobileRxPackets();
+ long mobileTxBytesBefore = TrafficStats.getMobileTxBytes();
+ long mobileRxBytesBefore = TrafficStats.getMobileRxBytes();
long totalTxPacketsBefore = TrafficStats.getTotalTxPackets();
long totalRxPacketsBefore = TrafficStats.getTotalRxPackets();
long totalTxBytesBefore = TrafficStats.getTotalTxBytes();
long totalRxBytesBefore = TrafficStats.getTotalRxBytes();
long uidTxBytesBefore = TrafficStats.getUidTxBytes(Process.myUid());
long uidRxBytesBefore = TrafficStats.getUidRxBytes(Process.myUid());
+ long uidTxPacketsBefore = TrafficStats.getUidTxPackets(Process.myUid());
+ long uidRxPacketsBefore = TrafficStats.getUidRxPackets(Process.myUid());
// Transfer 1MB of data across an explicitly localhost socket.
+ final int byteCount = 1024;
+ final int packetCount = 1024;
final ServerSocket server = new ServerSocket(0);
new Thread("TrafficStatsTest.testTrafficStatsForLocalhost") {
@@ -62,9 +72,15 @@
public void run() {
try {
Socket socket = new Socket("localhost", server.getLocalPort());
+ // Make sure that each write()+flush() turns into a packet:
+ // disable Nagle.
+ socket.setTcpNoDelay(true);
OutputStream out = socket.getOutputStream();
- byte[] buf = new byte[1024];
- for (int i = 0; i < 1024; i++) out.write(buf);
+ byte[] buf = new byte[byteCount];
+ for (int i = 0; i < packetCount; i++) {
+ out.write(buf);
+ out.flush();
+ }
out.close();
socket.close();
} catch (IOException e) {
@@ -75,9 +91,9 @@
try {
Socket socket = server.accept();
InputStream in = socket.getInputStream();
- byte[] buf = new byte[1024];
+ byte[] buf = new byte[byteCount];
int read = 0;
- while (read < 1048576) {
+ while (read < byteCount * packetCount) {
int n = in.read(buf);
assertTrue("Unexpected EOF", n > 0);
read += n;
@@ -92,50 +108,76 @@
} catch (InterruptedException e) {
}
- long mobileTxPacketsAfter = TrafficStats.getTotalTxPackets();
- long mobileRxPacketsAfter = TrafficStats.getTotalRxPackets();
- long mobileTxBytesAfter = TrafficStats.getTotalTxBytes();
- long mobileRxBytesAfter = TrafficStats.getTotalRxBytes();
+ long mobileTxPacketsAfter = TrafficStats.getMobileTxPackets();
+ long mobileRxPacketsAfter = TrafficStats.getMobileRxPackets();
+ long mobileTxBytesAfter = TrafficStats.getMobileTxBytes();
+ long mobileRxBytesAfter = TrafficStats.getMobileRxBytes();
long totalTxPacketsAfter = TrafficStats.getTotalTxPackets();
long totalRxPacketsAfter = TrafficStats.getTotalRxPackets();
long totalTxBytesAfter = TrafficStats.getTotalTxBytes();
long totalRxBytesAfter = TrafficStats.getTotalRxBytes();
long uidTxBytesAfter = TrafficStats.getUidTxBytes(Process.myUid());
long uidRxBytesAfter = TrafficStats.getUidRxBytes(Process.myUid());
-
- // Localhost traffic should *not* count against mobile or total stats.
- // There might be some other traffic, but nowhere near 1MB.
-
- assertTrue("mtxp: " + mobileTxPacketsBefore + " -> " + mobileTxPacketsAfter,
- mobileTxPacketsAfter >= mobileTxPacketsBefore &&
- mobileTxPacketsAfter <= mobileTxPacketsBefore + 500);
- assertTrue("mrxp: " + mobileRxPacketsBefore + " -> " + mobileRxPacketsAfter,
- mobileRxPacketsAfter >= mobileRxPacketsBefore &&
- mobileRxPacketsAfter <= mobileRxPacketsBefore + 500);
- assertTrue("mtxb: " + mobileTxBytesBefore + " -> " + mobileTxBytesAfter,
- mobileTxBytesAfter >= mobileTxBytesBefore &&
- mobileTxBytesAfter <= mobileTxBytesBefore + 200000);
- assertTrue("mrxb: " + mobileRxBytesBefore + " -> " + mobileRxBytesAfter,
- mobileRxBytesAfter >= mobileRxBytesBefore &&
- mobileRxBytesAfter <= mobileRxBytesBefore + 200000);
-
- assertTrue("ttxp: " + totalTxPacketsBefore + " -> " + totalTxPacketsAfter,
- totalTxPacketsAfter >= totalTxPacketsBefore &&
- totalTxPacketsAfter <= totalTxPacketsBefore + 500);
- assertTrue("trxp: " + totalRxPacketsBefore + " -> " + totalRxPacketsAfter,
- totalRxPacketsAfter >= totalRxPacketsBefore &&
- totalRxPacketsAfter <= totalRxPacketsBefore + 500);
- assertTrue("ttxb: " + totalTxBytesBefore + " -> " + totalTxBytesAfter,
- totalTxBytesAfter >= totalTxBytesBefore &&
- totalTxBytesAfter <= totalTxBytesBefore + 200000);
- assertTrue("trxb: " + totalRxBytesBefore + " -> " + totalRxBytesAfter,
- totalRxBytesAfter >= totalRxBytesBefore &&
- totalRxBytesAfter <= totalRxBytesBefore + 200000);
+ long uidTxPacketsAfter = TrafficStats.getUidTxPackets(Process.myUid());
+ long uidRxPacketsAfter = TrafficStats.getUidRxPackets(Process.myUid());
+ long uidTxDeltaBytes = uidTxBytesAfter - uidTxBytesBefore;
+ long uidTxDeltaPackets = uidTxPacketsAfter - uidTxPacketsBefore;
+ long uidRxDeltaBytes = uidRxBytesAfter - uidRxBytesBefore;
+ long uidRxDeltaPackets = uidRxPacketsAfter - uidRxPacketsBefore;
// Localhost traffic *does* count against per-UID stats.
- assertTrue("uidtxb: " + uidTxBytesBefore + " -> " + uidTxBytesAfter,
- uidTxBytesAfter >= uidTxBytesBefore + 1048576);
- assertTrue("uidrxb: " + uidRxBytesBefore + " -> " + uidRxBytesAfter,
- uidRxBytesAfter >= uidRxBytesBefore + 1048576);
+ /*
+ * Calculations:
+ * - bytes
+ * bytes is approx: packets * data + packets * acks;
+ * but sometimes there are less acks than packets, so we set a lower
+ * limit of 1 ack.
+ * - setup/teardown
+ * + 7 approx.: syn, syn-ack, ack, fin-ack, ack, fin-ack, ack;
+ * but sometimes the last find-acks just vanish, so we set a lower limit of +5.
+ */
+ assertTrue("uidtxp: " + uidTxPacketsBefore + " -> " + uidTxPacketsAfter + " delta=" + uidTxDeltaPackets,
+ uidTxDeltaPackets >= packetCount + 5 &&
+ uidTxDeltaPackets <= packetCount + packetCount + 7);
+ assertTrue("uidrxp: " + uidRxPacketsBefore + " -> " + uidRxPacketsAfter + " delta=" + uidRxDeltaPackets,
+ uidRxDeltaPackets >= packetCount + 5 &&
+ uidRxDeltaPackets <= packetCount + packetCount + 7);
+ assertTrue("uidtxb: " + uidTxBytesBefore + " -> " + uidTxBytesAfter + " delta=" + uidTxDeltaBytes,
+ uidTxDeltaBytes >= tcpPacketToIpBytes(packetCount, byteCount) + tcpPacketToIpBytes(5, 0) &&
+ uidTxDeltaBytes <= tcpPacketToIpBytes(packetCount, byteCount) + tcpPacketToIpBytes(packetCount + 7, 0));
+ assertTrue("uidrxb: " + uidRxBytesBefore + " -> " + uidRxBytesAfter + " delta=" + uidRxDeltaBytes,
+ uidRxDeltaBytes >= tcpPacketToIpBytes(packetCount, byteCount) + tcpPacketToIpBytes(5, 0) &&
+ uidRxDeltaBytes <= tcpPacketToIpBytes(packetCount, byteCount) + tcpPacketToIpBytes(packetCount + 7, 0));
+
+ // Localhost traffic *does* count against total stats.
+ // Fudge by 132 packets of 1500 bytes not related to the test.
+ assertTrue("ttxp: " + totalTxPacketsBefore + " -> " + totalTxPacketsAfter,
+ totalTxPacketsAfter >= totalTxPacketsBefore + uidTxDeltaPackets &&
+ totalTxPacketsAfter <= totalTxPacketsBefore + uidTxDeltaPackets + 132);
+ assertTrue("trxp: " + totalRxPacketsBefore + " -> " + totalRxPacketsAfter,
+ totalRxPacketsAfter >= totalRxPacketsBefore + uidRxDeltaPackets &&
+ totalRxPacketsAfter <= totalRxPacketsBefore + uidRxDeltaPackets + 132);
+ assertTrue("ttxb: " + totalTxBytesBefore + " -> " + totalTxBytesAfter,
+ totalTxBytesAfter >= totalTxBytesBefore + uidTxDeltaBytes &&
+ totalTxBytesAfter <= totalTxBytesBefore + uidTxDeltaBytes + 132 * 1500);
+ assertTrue("trxb: " + totalRxBytesBefore + " -> " + totalRxBytesAfter,
+ totalRxBytesAfter >= totalRxBytesBefore + uidRxDeltaBytes &&
+ totalRxBytesAfter <= totalRxBytesBefore + uidRxDeltaBytes + 132 * 1500);
+
+ // Localhost traffic should *not* count against mobile stats,
+ // There might be some other traffic, but nowhere near 1MB.
+ assertTrue("mtxp: " + mobileTxPacketsBefore + " -> " + mobileTxPacketsAfter,
+ mobileTxPacketsAfter >= mobileTxPacketsBefore &&
+ mobileTxPacketsAfter <= mobileTxPacketsBefore + 500);
+ assertTrue("mrxp: " + mobileRxPacketsBefore + " -> " + mobileRxPacketsAfter,
+ mobileRxPacketsAfter >= mobileRxPacketsBefore &&
+ mobileRxPacketsAfter <= mobileRxPacketsBefore + 500);
+ assertTrue("mtxb: " + mobileTxBytesBefore + " -> " + mobileTxBytesAfter,
+ mobileTxBytesAfter >= mobileTxBytesBefore &&
+ mobileTxBytesAfter <= mobileTxBytesBefore + 200000);
+ assertTrue("mrxb: " + mobileRxBytesBefore + " -> " + mobileRxBytesAfter,
+ mobileRxBytesAfter >= mobileRxBytesBefore &&
+ mobileRxBytesAfter <= mobileRxBytesBefore + 200000);
+
}
}
diff --git a/tests/tests/openglperf/Android.mk b/tests/tests/openglperf/Android.mk
index 2d1767e..e10d1c7 100644
--- a/tests/tests/openglperf/Android.mk
+++ b/tests/tests/openglperf/Android.mk
@@ -23,7 +23,9 @@
LOCAL_JAVA_LIBRARIES := android.test.runner
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner ctsutil
+
+LOCAL_JNI_SHARED_LIBRARIES := libctsopenglperf_jni
LOCAL_SRC_FILES := $(call all-java-files-under, src)
@@ -35,4 +37,4 @@
include $(BUILD_CTS_PACKAGE)
-#include $(call all-makefiles-under,$(LOCAL_PATH))
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/openglperf/AndroidManifest.xml b/tests/tests/openglperf/AndroidManifest.xml
index 540f190..84bfea4 100644
--- a/tests/tests/openglperf/AndroidManifest.xml
+++ b/tests/tests/openglperf/AndroidManifest.xml
@@ -32,12 +32,9 @@
android:targetPackage="com.android.cts.openglperf"
android:name="android.test.InstrumentationCtsTestRunner" />
- <application
- android:label="@string/app_name" >
+ <application>
<uses-library android:name="android.test.runner" />
- <activity
- android:name="android.openglperf.cts.GlPlanetsActivity"
- android:label="@string/app_name" >
+ <activity android:name="android.openglperf.cts.GlPlanetsActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
diff --git a/suite/pts/hostTests/ptshostutil/Android.mk b/tests/tests/openglperf/jni/Android.mk
similarity index 67%
copy from suite/pts/hostTests/ptshostutil/Android.mk
copy to tests/tests/openglperf/jni/Android.mk
index 14e786a..77db1df 100644
--- a/suite/pts/hostTests/ptshostutil/Android.mk
+++ b/tests/tests/openglperf/jni/Android.mk
@@ -11,19 +11,21 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-
+#
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_SRC_FILES := \
- $(call all-java-files-under, src)
-
-LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed-prebuilt ddmlib-prebuilt junit
+LOCAL_MODULE := libctsopenglperf_jni
LOCAL_MODULE_TAGS := optional
-LOCAL_MODULE := ptshostutil
+LOCAL_SRC_FILES := OpenGlPerfNativeJni.cpp
-include $(BUILD_HOST_JAVA_LIBRARY)
+LOCAL_C_INCLUDES := $(JNI_H_INCLUDE) $(call include-path-for, system-core) frameworks/native/opengl/include
+LOCAL_SHARED_LIBRARIES := libnativehelper libcutils libGLES_trace libEGL
+
+LOCAL_SDK_VERSION := 14
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/tests/tests/openglperf/jni/OpenGlPerfNativeJni.cpp b/tests/tests/openglperf/jni/OpenGlPerfNativeJni.cpp
new file mode 100644
index 0000000..63311eb
--- /dev/null
+++ b/tests/tests/openglperf/jni/OpenGlPerfNativeJni.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <EGL/egl.h>
+#define EGL_EGLEXT_PROTOTYPES // for egl*Sync*
+#include <EGL/eglext.h>
+#include <cutils/log.h>
+#include <jni.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+
+typedef EGLSyncKHR EGLAPIENTRY (*TypeEglCreateSyncKHR)(EGLDisplay dpy, \
+ EGLenum type, const EGLint *attrib_list);
+typedef EGLBoolean EGLAPIENTRY (*TypeEglDestroySyncKHR)(EGLDisplay dpy, \
+ EGLSyncKHR sync);
+typedef EGLint EGLAPIENTRY (*TypeEglClientWaitSyncKHR)(EGLDisplay dpy, \
+ EGLSyncKHR sync, EGLint flags, EGLTimeKHR timeout);
+
+static TypeEglCreateSyncKHR mEglCreateSyncKHR = NULL;
+static TypeEglClientWaitSyncKHR mEglClientWaitSyncKHR = NULL;
+static TypeEglDestroySyncKHR mEglDestroySyncKHR = NULL;
+static bool mInitialized = false;
+static bool mEglKhrFenceSyncSupported = false;
+
+bool IsEglKHRFenceSyncSupported(EGLDisplay& display)
+{
+ if (!mInitialized) {
+ const char* eglExtensions = eglQueryString(display, EGL_EXTENSIONS);
+ if (eglExtensions && strstr(eglExtensions, "EGL_KHR_fence_sync")) {
+ mEglCreateSyncKHR = (TypeEglCreateSyncKHR) eglGetProcAddress("eglCreateSyncKHR");
+ mEglClientWaitSyncKHR =
+ (TypeEglClientWaitSyncKHR) eglGetProcAddress("eglClientWaitSyncKHR");
+ mEglDestroySyncKHR = (TypeEglDestroySyncKHR) eglGetProcAddress("eglDestroySyncKHR");
+ if (mEglCreateSyncKHR != NULL && mEglClientWaitSyncKHR != NULL
+ && mEglDestroySyncKHR != NULL) {
+ mEglKhrFenceSyncSupported = true;
+ }
+ }
+ mInitialized = true;
+ }
+ return mEglKhrFenceSyncSupported;
+}
+
+extern "C" JNIEXPORT \
+jboolean JNICALL Java_android_openglperf_cts_OpenGlPerfNative_waitForEglCompletion(JNIEnv* env,
+ jclass clazz, jlong waitTimeInNs)
+{
+ EGLDisplay dpy = eglGetCurrentDisplay();
+ if (!IsEglKHRFenceSyncSupported(dpy)) {
+ return JNI_FALSE;
+ }
+ EGLSyncKHR sync = mEglCreateSyncKHR(dpy, EGL_SYNC_FENCE_KHR, NULL);
+ if (sync == EGL_NO_SYNC_KHR) {
+ return JNI_FALSE;
+ }
+ jboolean res = JNI_TRUE;
+ EGLint result = mEglClientWaitSyncKHR(dpy, sync, 0, waitTimeInNs);
+ if (result == EGL_FALSE) {
+ ALOGE("FrameCompletion: error waiting for fence: %#x", eglGetError());
+ res = JNI_FALSE;
+ } else if (result == EGL_TIMEOUT_EXPIRED_KHR) {
+ ALOGE("FrameCompletion: timeout waiting for fence");
+ res = JNI_FALSE;
+ }
+ mEglDestroySyncKHR(dpy, sync);
+ return res;
+}
diff --git a/tests/tests/openglperf/src/android/openglperf/cts/GLSurfaceViewCustom.java b/tests/tests/openglperf/src/android/openglperf/cts/GLSurfaceViewCustom.java
new file mode 100644
index 0000000..e1a2292
--- /dev/null
+++ b/tests/tests/openglperf/src/android/openglperf/cts/GLSurfaceViewCustom.java
@@ -0,0 +1,1943 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.openglperf.cts;
+
+// This is a copy of f/b/opengl/java/android/opengl/GlSurfaceView.
+// Most of the code is used as it is, but there was a need to add a hook
+// after eglSwapBuffers is called. Also classes not available outside framework is commented out
+// or replaced.
+
+
+import java.io.Writer;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGL11;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLContext;
+import javax.microedition.khronos.egl.EGLDisplay;
+import javax.microedition.khronos.egl.EGLSurface;
+import javax.microedition.khronos.opengles.GL;
+import javax.microedition.khronos.opengles.GL10;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+
+/**
+ * An implementation of SurfaceView that uses the dedicated surface for
+ * displaying OpenGL rendering.
+ * <p>
+ * A GLSurfaceView provides the following features:
+ * <p>
+ * <ul>
+ * <li>Manages a surface, which is a special piece of memory that can be
+ * composited into the Android view system.
+ * <li>Manages an EGL display, which enables OpenGL to render into a surface.
+ * <li>Accepts a user-provided Renderer object that does the actual rendering.
+ * <li>Renders on a dedicated thread to decouple rendering performance from the
+ * UI thread.
+ * <li>Supports both on-demand and continuous rendering.
+ * <li>Optionally wraps, traces, and/or error-checks the renderer's OpenGL calls.
+ * </ul>
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about how to use OpenGL, read the
+ * <a href="{@docRoot}guide/topics/graphics/opengl.html">OpenGL</a> developer guide.</p>
+ * </div>
+ *
+ * <h3>Using GLSurfaceView</h3>
+ * <p>
+ * Typically you use GLSurfaceView by subclassing it and overriding one or more of the
+ * View system input event methods. If your application does not need to override event
+ * methods then GLSurfaceView can be used as-is. For the most part
+ * GLSurfaceView behavior is customized by calling "set" methods rather than by subclassing.
+ * For example, unlike a regular View, drawing is delegated to a separate Renderer object which
+ * is registered with the GLSurfaceView
+ * using the {@link #setRenderer(Renderer)} call.
+ * <p>
+ * <h3>Initializing GLSurfaceView</h3>
+ * All you have to do to initialize a GLSurfaceView is call {@link #setRenderer(Renderer)}.
+ * However, if desired, you can modify the default behavior of GLSurfaceView by calling one or
+ * more of these methods before calling setRenderer:
+ * <ul>
+ * <li>{@link #setDebugFlags(int)}
+ * <li>{@link #setEGLConfigChooser(boolean)}
+ * <li>{@link #setEGLConfigChooser(EGLConfigChooser)}
+ * <li>{@link #setEGLConfigChooser(int, int, int, int, int, int)}
+ * <li>{@link #setGLWrapper(GLWrapper)}
+ * </ul>
+ * <p>
+ * <h4>Specifying the android.view.Surface</h4>
+ * By default GLSurfaceView will create a PixelFormat.RGB_888 format surface. If a translucent
+ * surface is required, call getHolder().setFormat(PixelFormat.TRANSLUCENT).
+ * The exact format of a TRANSLUCENT surface is device dependent, but it will be
+ * a 32-bit-per-pixel surface with 8 bits per component.
+ * <p>
+ * <h4>Choosing an EGL Configuration</h4>
+ * A given Android device may support multiple EGLConfig rendering configurations.
+ * The available configurations may differ in how may channels of data are present, as
+ * well as how many bits are allocated to each channel. Therefore, the first thing
+ * GLSurfaceView has to do when starting to render is choose what EGLConfig to use.
+ * <p>
+ * By default GLSurfaceView chooses a EGLConfig that has an RGB_888 pixel format,
+ * with at least a 16-bit depth buffer and no stencil.
+ * <p>
+ * If you would prefer a different EGLConfig
+ * you can override the default behavior by calling one of the
+ * setEGLConfigChooser methods.
+ * <p>
+ * <h4>Debug Behavior</h4>
+ * You can optionally modify the behavior of GLSurfaceView by calling
+ * one or more of the debugging methods {@link #setDebugFlags(int)},
+ * and {@link #setGLWrapper}. These methods may be called before and/or after setRenderer, but
+ * typically they are called before setRenderer so that they take effect immediately.
+ * <p>
+ * <h4>Setting a Renderer</h4>
+ * Finally, you must call {@link #setRenderer} to register a {@link Renderer}.
+ * The renderer is
+ * responsible for doing the actual OpenGL rendering.
+ * <p>
+ * <h3>Rendering Mode</h3>
+ * Once the renderer is set, you can control whether the renderer draws
+ * continuously or on-demand by calling
+ * {@link #setRenderMode}. The default is continuous rendering.
+ * <p>
+ * <h3>Activity Life-cycle</h3>
+ * A GLSurfaceView must be notified when the activity is paused and resumed. GLSurfaceView clients
+ * are required to call {@link #onPause()} when the activity pauses and
+ * {@link #onResume()} when the activity resumes. These calls allow GLSurfaceView to
+ * pause and resume the rendering thread, and also allow GLSurfaceView to release and recreate
+ * the OpenGL display.
+ * <p>
+ * <h3>Handling events</h3>
+ * <p>
+ * To handle an event you will typically subclass GLSurfaceView and override the
+ * appropriate method, just as you would with any other View. However, when handling
+ * the event, you may need to communicate with the Renderer object
+ * that's running in the rendering thread. You can do this using any
+ * standard Java cross-thread communication mechanism. In addition,
+ * one relatively easy way to communicate with your renderer is
+ * to call
+ * {@link #queueEvent(Runnable)}. For example:
+ * <pre class="prettyprint">
+ * class MyGLSurfaceView extends GLSurfaceView {
+ *
+ * private MyRenderer mMyRenderer;
+ *
+ * public void start() {
+ * mMyRenderer = ...;
+ * setRenderer(mMyRenderer);
+ * }
+ *
+ * public boolean onKeyDown(int keyCode, KeyEvent event) {
+ * if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
+ * queueEvent(new Runnable() {
+ * // This method will be called on the rendering
+ * // thread:
+ * public void run() {
+ * mMyRenderer.handleDpadCenter();
+ * }});
+ * return true;
+ * }
+ * return super.onKeyDown(keyCode, event);
+ * }
+ * }
+ * </pre>
+ *
+ */
+public class GLSurfaceViewCustom extends SurfaceView implements SurfaceHolder.Callback {
+ private final static String TAG = "GLSurfaceView";
+ private final static boolean LOG_ATTACH_DETACH = false;
+ private final static boolean LOG_THREADS = false;
+ private final static boolean LOG_PAUSE_RESUME = false;
+ private final static boolean LOG_SURFACE = false;
+ private final static boolean LOG_RENDERER = false;
+ private final static boolean LOG_RENDERER_DRAW_FRAME = false;
+ private final static boolean LOG_EGL = false;
+ /**
+ * The renderer only renders
+ * when the surface is created, or when {@link #requestRender} is called.
+ *
+ * @see #getRenderMode()
+ * @see #setRenderMode(int)
+ * @see #requestRender()
+ */
+ public final static int RENDERMODE_WHEN_DIRTY = 0;
+ /**
+ * The renderer is called
+ * continuously to re-render the scene.
+ *
+ * @see #getRenderMode()
+ * @see #setRenderMode(int)
+ */
+ public final static int RENDERMODE_CONTINUOUSLY = 1;
+
+ /**
+ * Check glError() after every GL call and throw an exception if glError indicates
+ * that an error has occurred. This can be used to help track down which OpenGL ES call
+ * is causing an error.
+ *
+ * @see #getDebugFlags
+ * @see #setDebugFlags
+ */
+ public final static int DEBUG_CHECK_GL_ERROR = 1;
+
+ /**
+ * Log GL calls to the system log at "verbose" level with tag "GLSurfaceView".
+ *
+ * @see #getDebugFlags
+ * @see #setDebugFlags
+ */
+ public final static int DEBUG_LOG_GL_CALLS = 2;
+
+ /**
+ * Standard View constructor. In order to render something, you
+ * must call {@link #setRenderer} to register a renderer.
+ */
+ public GLSurfaceViewCustom(Context context) {
+ super(context);
+ init();
+ }
+
+ /**
+ * Standard View constructor. In order to render something, you
+ * must call {@link #setRenderer} to register a renderer.
+ */
+ public GLSurfaceViewCustom(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (mGLThread != null) {
+ // GLThread may still be running if this view was never
+ // attached to a window.
+ mGLThread.requestExitAndWait();
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+
+ private void init() {
+ // Install a SurfaceHolder.Callback so we get notified when the
+ // underlying surface is created and destroyed
+ SurfaceHolder holder = getHolder();
+ holder.addCallback(this);
+ // setFormat is done by SurfaceView in SDK 2.3 and newer. Uncomment
+ // this statement if back-porting to 2.2 or older:
+ // holder.setFormat(PixelFormat.RGB_565);
+ //
+ // setType is not needed for SDK 2.0 or newer. Uncomment this
+ // statement if back-porting this code to older SDKs.
+ // holder.setType(SurfaceHolder.SURFACE_TYPE_GPU);
+ }
+
+ /**
+ * Set the glWrapper. If the glWrapper is not null, its
+ * {@link GLWrapper#wrap(GL)} method is called
+ * whenever a surface is created. A GLWrapper can be used to wrap
+ * the GL object that's passed to the renderer. Wrapping a GL
+ * object enables examining and modifying the behavior of the
+ * GL calls made by the renderer.
+ * <p>
+ * Wrapping is typically used for debugging purposes.
+ * <p>
+ * The default value is null.
+ * @param glWrapper the new GLWrapper
+ */
+ public void setGLWrapper(GLWrapper glWrapper) {
+ mGLWrapper = glWrapper;
+ }
+
+ /**
+ * Set the debug flags to a new value. The value is
+ * constructed by OR-together zero or more
+ * of the DEBUG_CHECK_* constants. The debug flags take effect
+ * whenever a surface is created. The default value is zero.
+ * @param debugFlags the new debug flags
+ * @see #DEBUG_CHECK_GL_ERROR
+ * @see #DEBUG_LOG_GL_CALLS
+ */
+ public void setDebugFlags(int debugFlags) {
+ mDebugFlags = debugFlags;
+ }
+
+ /**
+ * Get the current value of the debug flags.
+ * @return the current value of the debug flags.
+ */
+ public int getDebugFlags() {
+ return mDebugFlags;
+ }
+
+ /**
+ * Control whether the EGL context is preserved when the GLSurfaceView is paused and
+ * resumed.
+ * <p>
+ * If set to true, then the EGL context may be preserved when the GLSurfaceView is paused.
+ * Whether the EGL context is actually preserved or not depends upon whether the
+ * Android device that the program is running on can support an arbitrary number of EGL
+ * contexts or not. Devices that can only support a limited number of EGL contexts must
+ * release the EGL context in order to allow multiple applications to share the GPU.
+ * <p>
+ * If set to false, the EGL context will be released when the GLSurfaceView is paused,
+ * and recreated when the GLSurfaceView is resumed.
+ * <p>
+ *
+ * The default is false.
+ *
+ * @param preserveOnPause preserve the EGL context when paused
+ */
+ public void setPreserveEGLContextOnPause(boolean preserveOnPause) {
+ mPreserveEGLContextOnPause = preserveOnPause;
+ }
+
+ /**
+ * @return true if the EGL context will be preserved when paused
+ */
+ public boolean getPreserveEGLContextOnPause() {
+ return mPreserveEGLContextOnPause;
+ }
+
+ /**
+ * Set the renderer associated with this view. Also starts the thread that
+ * will call the renderer, which in turn causes the rendering to start.
+ * <p>This method should be called once and only once in the life-cycle of
+ * a GLSurfaceView.
+ * <p>The following GLSurfaceView methods can only be called <em>before</em>
+ * setRenderer is called:
+ * <ul>
+ * <li>{@link #setEGLConfigChooser(boolean)}
+ * <li>{@link #setEGLConfigChooser(EGLConfigChooser)}
+ * <li>{@link #setEGLConfigChooser(int, int, int, int, int, int)}
+ * </ul>
+ * <p>
+ * The following GLSurfaceView methods can only be called <em>after</em>
+ * setRenderer is called:
+ * <ul>
+ * <li>{@link #getRenderMode()}
+ * <li>{@link #onPause()}
+ * <li>{@link #onResume()}
+ * <li>{@link #queueEvent(Runnable)}
+ * <li>{@link #requestRender()}
+ * <li>{@link #setRenderMode(int)}
+ * </ul>
+ *
+ * @param renderer the renderer to use to perform OpenGL drawing.
+ */
+ public void setRenderer(Renderer renderer) {
+ checkRenderThreadState();
+ if (mEGLConfigChooser == null) {
+ mEGLConfigChooser = new SimpleEGLConfigChooser(true);
+ }
+ if (mEGLContextFactory == null) {
+ mEGLContextFactory = new DefaultContextFactory();
+ }
+ if (mEGLWindowSurfaceFactory == null) {
+ mEGLWindowSurfaceFactory = new DefaultWindowSurfaceFactory();
+ }
+ mRenderer = renderer;
+ mGLThread = new GLThread(mThisWeakRef);
+ mGLThread.start();
+ }
+
+ /**
+ * Install a custom EGLContextFactory.
+ * <p>If this method is
+ * called, it must be called before {@link #setRenderer(Renderer)}
+ * is called.
+ * <p>
+ * If this method is not called, then by default
+ * a context will be created with no shared context and
+ * with a null attribute list.
+ */
+ public void setEGLContextFactory(EGLContextFactory factory) {
+ checkRenderThreadState();
+ mEGLContextFactory = factory;
+ }
+
+ /**
+ * Install a custom EGLWindowSurfaceFactory.
+ * <p>If this method is
+ * called, it must be called before {@link #setRenderer(Renderer)}
+ * is called.
+ * <p>
+ * If this method is not called, then by default
+ * a window surface will be created with a null attribute list.
+ */
+ public void setEGLWindowSurfaceFactory(EGLWindowSurfaceFactory factory) {
+ checkRenderThreadState();
+ mEGLWindowSurfaceFactory = factory;
+ }
+
+ /**
+ * Install a custom EGLConfigChooser.
+ * <p>If this method is
+ * called, it must be called before {@link #setRenderer(Renderer)}
+ * is called.
+ * <p>
+ * If no setEGLConfigChooser method is called, then by default the
+ * view will choose an EGLConfig that is compatible with the current
+ * android.view.Surface, with a depth buffer depth of
+ * at least 16 bits.
+ * @param configChooser
+ */
+ public void setEGLConfigChooser(EGLConfigChooser configChooser) {
+ checkRenderThreadState();
+ mEGLConfigChooser = configChooser;
+ }
+
+ /**
+ * Install a config chooser which will choose a config
+ * as close to 16-bit RGB as possible, with or without an optional depth
+ * buffer as close to 16-bits as possible.
+ * <p>If this method is
+ * called, it must be called before {@link #setRenderer(Renderer)}
+ * is called.
+ * <p>
+ * If no setEGLConfigChooser method is called, then by default the
+ * view will choose an RGB_888 surface with a depth buffer depth of
+ * at least 16 bits.
+ *
+ * @param needDepth
+ */
+ public void setEGLConfigChooser(boolean needDepth) {
+ setEGLConfigChooser(new SimpleEGLConfigChooser(needDepth));
+ }
+
+ /**
+ * Install a config chooser which will choose a config
+ * with at least the specified depthSize and stencilSize,
+ * and exactly the specified redSize, greenSize, blueSize and alphaSize.
+ * <p>If this method is
+ * called, it must be called before {@link #setRenderer(Renderer)}
+ * is called.
+ * <p>
+ * If no setEGLConfigChooser method is called, then by default the
+ * view will choose an RGB_888 surface with a depth buffer depth of
+ * at least 16 bits.
+ *
+ */
+ public void setEGLConfigChooser(int redSize, int greenSize, int blueSize,
+ int alphaSize, int depthSize, int stencilSize) {
+ setEGLConfigChooser(new ComponentSizeChooser(redSize, greenSize,
+ blueSize, alphaSize, depthSize, stencilSize));
+ }
+
+ /**
+ * Inform the default EGLContextFactory and default EGLConfigChooser
+ * which EGLContext client version to pick.
+ * <p>Use this method to create an OpenGL ES 2.0-compatible context.
+ * Example:
+ * <pre class="prettyprint">
+ * public MyView(Context context) {
+ * super(context);
+ * setEGLContextClientVersion(2); // Pick an OpenGL ES 2.0 context.
+ * setRenderer(new MyRenderer());
+ * }
+ * </pre>
+ * <p>Note: Activities which require OpenGL ES 2.0 should indicate this by
+ * setting @lt;uses-feature android:glEsVersion="0x00020000" /> in the activity's
+ * AndroidManifest.xml file.
+ * <p>If this method is called, it must be called before {@link #setRenderer(Renderer)}
+ * is called.
+ * <p>This method only affects the behavior of the default EGLContexFactory and the
+ * default EGLConfigChooser. If
+ * {@link #setEGLContextFactory(EGLContextFactory)} has been called, then the supplied
+ * EGLContextFactory is responsible for creating an OpenGL ES 2.0-compatible context.
+ * If
+ * {@link #setEGLConfigChooser(EGLConfigChooser)} has been called, then the supplied
+ * EGLConfigChooser is responsible for choosing an OpenGL ES 2.0-compatible config.
+ * @param version The EGLContext client version to choose. Use 2 for OpenGL ES 2.0
+ */
+ public void setEGLContextClientVersion(int version) {
+ checkRenderThreadState();
+ mEGLContextClientVersion = version;
+ }
+
+ /**
+ * Set the rendering mode. When renderMode is
+ * RENDERMODE_CONTINUOUSLY, the renderer is called
+ * repeatedly to re-render the scene. When renderMode
+ * is RENDERMODE_WHEN_DIRTY, the renderer only rendered when the surface
+ * is created, or when {@link #requestRender} is called. Defaults to RENDERMODE_CONTINUOUSLY.
+ * <p>
+ * Using RENDERMODE_WHEN_DIRTY can improve battery life and overall system performance
+ * by allowing the GPU and CPU to idle when the view does not need to be updated.
+ * <p>
+ * This method can only be called after {@link #setRenderer(Renderer)}
+ *
+ * @param renderMode one of the RENDERMODE_X constants
+ * @see #RENDERMODE_CONTINUOUSLY
+ * @see #RENDERMODE_WHEN_DIRTY
+ */
+ public void setRenderMode(int renderMode) {
+ mGLThread.setRenderMode(renderMode);
+ }
+
+ /**
+ * Get the current rendering mode. May be called
+ * from any thread. Must not be called before a renderer has been set.
+ * @return the current rendering mode.
+ * @see #RENDERMODE_CONTINUOUSLY
+ * @see #RENDERMODE_WHEN_DIRTY
+ */
+ public int getRenderMode() {
+ return mGLThread.getRenderMode();
+ }
+
+ /**
+ * Request that the renderer render a frame.
+ * This method is typically used when the render mode has been set to
+ * {@link #RENDERMODE_WHEN_DIRTY}, so that frames are only rendered on demand.
+ * May be called
+ * from any thread. Must not be called before a renderer has been set.
+ */
+ public void requestRender() {
+ mGLThread.requestRender();
+ }
+
+ /**
+ * This method is part of the SurfaceHolder.Callback interface, and is
+ * not normally called or subclassed by clients of GLSurfaceView.
+ */
+ public void surfaceCreated(SurfaceHolder holder) {
+ mGLThread.surfaceCreated();
+ }
+
+ /**
+ * This method is part of the SurfaceHolder.Callback interface, and is
+ * not normally called or subclassed by clients of GLSurfaceView.
+ */
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ // Surface will be destroyed when we return
+ mGLThread.surfaceDestroyed();
+ }
+
+ /**
+ * This method is part of the SurfaceHolder.Callback interface, and is
+ * not normally called or subclassed by clients of GLSurfaceView.
+ */
+ public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
+ mGLThread.onWindowResize(w, h);
+ }
+
+ /**
+ * Inform the view that the activity is paused. The owner of this view must
+ * call this method when the activity is paused. Calling this method will
+ * pause the rendering thread.
+ * Must not be called before a renderer has been set.
+ */
+ public void onPause() {
+ mGLThread.onPause();
+ }
+
+ /**
+ * Inform the view that the activity is resumed. The owner of this view must
+ * call this method when the activity is resumed. Calling this method will
+ * recreate the OpenGL display and resume the rendering
+ * thread.
+ * Must not be called before a renderer has been set.
+ */
+ public void onResume() {
+ mGLThread.onResume();
+ }
+
+ /**
+ * Queue a runnable to be run on the GL rendering thread. This can be used
+ * to communicate with the Renderer on the rendering thread.
+ * Must not be called before a renderer has been set.
+ * @param r the runnable to be run on the GL rendering thread.
+ */
+ public void queueEvent(Runnable r) {
+ mGLThread.queueEvent(r);
+ }
+
+ /**
+ * This method is used as part of the View class and is not normally
+ * called or subclassed by clients of GLSurfaceView.
+ */
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ if (LOG_ATTACH_DETACH) {
+ Log.d(TAG, "onAttachedToWindow reattach =" + mDetached);
+ }
+ if (mDetached && (mRenderer != null)) {
+ int renderMode = RENDERMODE_CONTINUOUSLY;
+ if (mGLThread != null) {
+ renderMode = mGLThread.getRenderMode();
+ }
+ mGLThread = new GLThread(mThisWeakRef);
+ if (renderMode != RENDERMODE_CONTINUOUSLY) {
+ mGLThread.setRenderMode(renderMode);
+ }
+ mGLThread.start();
+ }
+ mDetached = false;
+ }
+
+ /**
+ * This method is used as part of the View class and is not normally
+ * called or subclassed by clients of GLSurfaceView.
+ * Must not be called before a renderer has been set.
+ */
+ @Override
+ protected void onDetachedFromWindow() {
+ if (LOG_ATTACH_DETACH) {
+ Log.d(TAG, "onDetachedFromWindow");
+ }
+ if (mGLThread != null) {
+ mGLThread.requestExitAndWait();
+ }
+ mDetached = true;
+ super.onDetachedFromWindow();
+ }
+
+ // ----------------------------------------------------------------------
+
+ /**
+ * An interface used to wrap a GL interface.
+ * <p>Typically
+ * used for implementing debugging and tracing on top of the default
+ * GL interface. You would typically use this by creating your own class
+ * that implemented all the GL methods by delegating to another GL instance.
+ * Then you could add your own behavior before or after calling the
+ * delegate. All the GLWrapper would do was instantiate and return the
+ * wrapper GL instance:
+ * <pre class="prettyprint">
+ * class MyGLWrapper implements GLWrapper {
+ * GL wrap(GL gl) {
+ * return new MyGLImplementation(gl);
+ * }
+ * static class MyGLImplementation implements GL,GL10,GL11,... {
+ * ...
+ * }
+ * }
+ * </pre>
+ * @see #setGLWrapper(GLWrapper)
+ */
+ public interface GLWrapper {
+ /**
+ * Wraps a gl interface in another gl interface.
+ * @param gl a GL interface that is to be wrapped.
+ * @return either the input argument or another GL object that wraps the input argument.
+ */
+ GL wrap(GL gl);
+ }
+
+ /**
+ * A generic renderer interface.
+ * <p>
+ * The renderer is responsible for making OpenGL calls to render a frame.
+ * <p>
+ * GLSurfaceView clients typically create their own classes that implement
+ * this interface, and then call {@link GLSurfaceView#setRenderer} to
+ * register the renderer with the GLSurfaceView.
+ * <p>
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about how to use OpenGL, read the
+ * <a href="{@docRoot}guide/topics/graphics/opengl.html">OpenGL</a> developer guide.</p>
+ * </div>
+ *
+ * <h3>Threading</h3>
+ * The renderer will be called on a separate thread, so that rendering
+ * performance is decoupled from the UI thread. Clients typically need to
+ * communicate with the renderer from the UI thread, because that's where
+ * input events are received. Clients can communicate using any of the
+ * standard Java techniques for cross-thread communication, or they can
+ * use the {@link GLSurfaceView#queueEvent(Runnable)} convenience method.
+ * <p>
+ * <h3>EGL Context Lost</h3>
+ * There are situations where the EGL rendering context will be lost. This
+ * typically happens when device wakes up after going to sleep. When
+ * the EGL context is lost, all OpenGL resources (such as textures) that are
+ * associated with that context will be automatically deleted. In order to
+ * keep rendering correctly, a renderer must recreate any lost resources
+ * that it still needs. The {@link #onSurfaceCreated(GL10, EGLConfig)} method
+ * is a convenient place to do this.
+ *
+ *
+ * @see #setRenderer(Renderer)
+ */
+ public interface Renderer {
+ /**
+ * Called when the surface is created or recreated.
+ * <p>
+ * Called when the rendering thread
+ * starts and whenever the EGL context is lost. The EGL context will typically
+ * be lost when the Android device awakes after going to sleep.
+ * <p>
+ * Since this method is called at the beginning of rendering, as well as
+ * every time the EGL context is lost, this method is a convenient place to put
+ * code to create resources that need to be created when the rendering
+ * starts, and that need to be recreated when the EGL context is lost.
+ * Textures are an example of a resource that you might want to create
+ * here.
+ * <p>
+ * Note that when the EGL context is lost, all OpenGL resources associated
+ * with that context will be automatically deleted. You do not need to call
+ * the corresponding "glDelete" methods such as glDeleteTextures to
+ * manually delete these lost resources.
+ * <p>
+ * @param gl the GL interface. Use <code>instanceof</code> to
+ * test if the interface supports GL11 or higher interfaces.
+ * @param config the EGLConfig of the created surface. Can be used
+ * to create matching pbuffers.
+ */
+ void onSurfaceCreated(GL10 gl, EGLConfig config);
+
+ /**
+ * Called when the surface changed size.
+ * <p>
+ * Called after the surface is created and whenever
+ * the OpenGL ES surface size changes.
+ * <p>
+ * Typically you will set your viewport here. If your camera
+ * is fixed then you could also set your projection matrix here:
+ * <pre class="prettyprint">
+ * void onSurfaceChanged(GL10 gl, int width, int height) {
+ * gl.glViewport(0, 0, width, height);
+ * // for a fixed camera, set the projection too
+ * float ratio = (float) width / height;
+ * gl.glMatrixMode(GL10.GL_PROJECTION);
+ * gl.glLoadIdentity();
+ * gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);
+ * }
+ * </pre>
+ * @param gl the GL interface. Use <code>instanceof</code> to
+ * test if the interface supports GL11 or higher interfaces.
+ * @param width
+ * @param height
+ */
+ void onSurfaceChanged(GL10 gl, int width, int height);
+
+ /**
+ * Called to draw the current frame.
+ * <p>
+ * This method is responsible for drawing the current frame.
+ * <p>
+ * The implementation of this method typically looks like this:
+ * <pre class="prettyprint">
+ * void onDrawFrame(GL10 gl) {
+ * gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
+ * //... other gl calls to render the scene ...
+ * }
+ * </pre>
+ * @param gl the GL interface. Use <code>instanceof</code> to
+ * test if the interface supports GL11 or higher interfaces.
+ */
+ void onDrawFrame(GL10 gl);
+
+ /**
+ * called after eglSwapBuffers() is called.
+ * can be used for custom synchronization / measurement.
+ */
+ void onEglSwapBuffers();
+ }
+
+ /**
+ * An interface for customizing the eglCreateContext and eglDestroyContext calls.
+ * <p>
+ * This interface must be implemented by clients wishing to call
+ * {@link GLSurfaceView#setEGLContextFactory(EGLContextFactory)}
+ */
+ public interface EGLContextFactory {
+ EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig);
+ void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context);
+ }
+
+ private class DefaultContextFactory implements EGLContextFactory {
+ private int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
+
+ public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig config) {
+ int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, mEGLContextClientVersion,
+ EGL10.EGL_NONE };
+
+ return egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT,
+ mEGLContextClientVersion != 0 ? attrib_list : null);
+ }
+
+ public void destroyContext(EGL10 egl, EGLDisplay display,
+ EGLContext context) {
+ if (!egl.eglDestroyContext(display, context)) {
+ Log.e("DefaultContextFactory", "display:" + display + " context: " + context);
+ if (LOG_THREADS) {
+ Log.i("DefaultContextFactory", "tid=" + Thread.currentThread().getId());
+ }
+ EglHelper.throwEglException("eglDestroyContex", egl.eglGetError());
+ }
+ }
+ }
+
+ /**
+ * An interface for customizing the eglCreateWindowSurface and eglDestroySurface calls.
+ * <p>
+ * This interface must be implemented by clients wishing to call
+ * {@link GLSurfaceView#setEGLWindowSurfaceFactory(EGLWindowSurfaceFactory)}
+ */
+ public interface EGLWindowSurfaceFactory {
+ /**
+ * @return null if the surface cannot be constructed.
+ */
+ EGLSurface createWindowSurface(EGL10 egl, EGLDisplay display, EGLConfig config,
+ Object nativeWindow);
+ void destroySurface(EGL10 egl, EGLDisplay display, EGLSurface surface);
+ }
+
+ private static class DefaultWindowSurfaceFactory implements EGLWindowSurfaceFactory {
+
+ public EGLSurface createWindowSurface(EGL10 egl, EGLDisplay display,
+ EGLConfig config, Object nativeWindow) {
+ EGLSurface result = null;
+ try {
+ result = egl.eglCreateWindowSurface(display, config, nativeWindow, null);
+ } catch (IllegalArgumentException e) {
+ // This exception indicates that the surface flinger surface
+ // is not valid. This can happen if the surface flinger surface has
+ // been torn down, but the application has not yet been
+ // notified via SurfaceHolder.Callback.surfaceDestroyed.
+ // In theory the application should be notified first,
+ // but in practice sometimes it is not. See b/4588890
+ Log.e(TAG, "eglCreateWindowSurface", e);
+ }
+ return result;
+ }
+
+ public void destroySurface(EGL10 egl, EGLDisplay display,
+ EGLSurface surface) {
+ egl.eglDestroySurface(display, surface);
+ }
+ }
+
+ /**
+ * An interface for choosing an EGLConfig configuration from a list of
+ * potential configurations.
+ * <p>
+ * This interface must be implemented by clients wishing to call
+ * {@link GLSurfaceView#setEGLConfigChooser(EGLConfigChooser)}
+ */
+ public interface EGLConfigChooser {
+ /**
+ * Choose a configuration from the list. Implementors typically
+ * implement this method by calling
+ * {@link EGL10#eglChooseConfig} and iterating through the results. Please consult the
+ * EGL specification available from The Khronos Group to learn how to call eglChooseConfig.
+ * @param egl the EGL10 for the current display.
+ * @param display the current display.
+ * @return the chosen configuration.
+ */
+ EGLConfig chooseConfig(EGL10 egl, EGLDisplay display);
+ }
+
+ private abstract class BaseConfigChooser
+ implements EGLConfigChooser {
+ public BaseConfigChooser(int[] configSpec) {
+ mConfigSpec = filterConfigSpec(configSpec);
+ }
+
+ public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
+ int[] num_config = new int[1];
+ if (!egl.eglChooseConfig(display, mConfigSpec, null, 0,
+ num_config)) {
+ throw new IllegalArgumentException("eglChooseConfig failed");
+ }
+
+ int numConfigs = num_config[0];
+
+ if (numConfigs <= 0) {
+ throw new IllegalArgumentException(
+ "No configs match configSpec");
+ }
+
+ EGLConfig[] configs = new EGLConfig[numConfigs];
+ if (!egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs,
+ num_config)) {
+ throw new IllegalArgumentException("eglChooseConfig#2 failed");
+ }
+ EGLConfig config = chooseConfig(egl, display, configs);
+ if (config == null) {
+ throw new IllegalArgumentException("No config chosen");
+ }
+ return config;
+ }
+
+ abstract EGLConfig chooseConfig(EGL10 egl, EGLDisplay display,
+ EGLConfig[] configs);
+
+ protected int[] mConfigSpec;
+
+ private int[] filterConfigSpec(int[] configSpec) {
+ if (mEGLContextClientVersion != 2) {
+ return configSpec;
+ }
+ /* We know none of the subclasses define EGL_RENDERABLE_TYPE.
+ * And we know the configSpec is well formed.
+ */
+ int len = configSpec.length;
+ int[] newConfigSpec = new int[len + 2];
+ System.arraycopy(configSpec, 0, newConfigSpec, 0, len-1);
+ newConfigSpec[len-1] = EGL10.EGL_RENDERABLE_TYPE;
+ newConfigSpec[len] = 4; /* EGL_OPENGL_ES2_BIT */
+ newConfigSpec[len+1] = EGL10.EGL_NONE;
+ return newConfigSpec;
+ }
+ }
+
+ /**
+ * Choose a configuration with exactly the specified r,g,b,a sizes,
+ * and at least the specified depth and stencil sizes.
+ */
+ private class ComponentSizeChooser extends BaseConfigChooser {
+ public ComponentSizeChooser(int redSize, int greenSize, int blueSize,
+ int alphaSize, int depthSize, int stencilSize) {
+ super(new int[] {
+ EGL10.EGL_RED_SIZE, redSize,
+ EGL10.EGL_GREEN_SIZE, greenSize,
+ EGL10.EGL_BLUE_SIZE, blueSize,
+ EGL10.EGL_ALPHA_SIZE, alphaSize,
+ EGL10.EGL_DEPTH_SIZE, depthSize,
+ EGL10.EGL_STENCIL_SIZE, stencilSize,
+ EGL10.EGL_NONE});
+ mValue = new int[1];
+ mRedSize = redSize;
+ mGreenSize = greenSize;
+ mBlueSize = blueSize;
+ mAlphaSize = alphaSize;
+ mDepthSize = depthSize;
+ mStencilSize = stencilSize;
+ }
+
+ @Override
+ public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display,
+ EGLConfig[] configs) {
+ for (EGLConfig config : configs) {
+ int d = findConfigAttrib(egl, display, config,
+ EGL10.EGL_DEPTH_SIZE, 0);
+ int s = findConfigAttrib(egl, display, config,
+ EGL10.EGL_STENCIL_SIZE, 0);
+ if ((d >= mDepthSize) && (s >= mStencilSize)) {
+ int r = findConfigAttrib(egl, display, config,
+ EGL10.EGL_RED_SIZE, 0);
+ int g = findConfigAttrib(egl, display, config,
+ EGL10.EGL_GREEN_SIZE, 0);
+ int b = findConfigAttrib(egl, display, config,
+ EGL10.EGL_BLUE_SIZE, 0);
+ int a = findConfigAttrib(egl, display, config,
+ EGL10.EGL_ALPHA_SIZE, 0);
+ if ((r == mRedSize) && (g == mGreenSize)
+ && (b == mBlueSize) && (a == mAlphaSize)) {
+ return config;
+ }
+ }
+ }
+ return null;
+ }
+
+ private int findConfigAttrib(EGL10 egl, EGLDisplay display,
+ EGLConfig config, int attribute, int defaultValue) {
+
+ if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) {
+ return mValue[0];
+ }
+ return defaultValue;
+ }
+
+ private int[] mValue;
+ // Subclasses can adjust these values:
+ protected int mRedSize;
+ protected int mGreenSize;
+ protected int mBlueSize;
+ protected int mAlphaSize;
+ protected int mDepthSize;
+ protected int mStencilSize;
+ }
+
+ /**
+ * This class will choose a RGB_888 surface with
+ * or without a depth buffer.
+ *
+ */
+ private class SimpleEGLConfigChooser extends ComponentSizeChooser {
+ public SimpleEGLConfigChooser(boolean withDepthBuffer) {
+ super(8, 8, 8, 0, withDepthBuffer ? 16 : 0, 0);
+ }
+ }
+
+ /**
+ * An EGL helper class.
+ */
+
+ private static class EglHelper {
+ public EglHelper(WeakReference<GLSurfaceViewCustom> glSurfaceViewWeakRef) {
+ mGLSurfaceViewWeakRef = glSurfaceViewWeakRef;
+ }
+
+ /**
+ * Initialize EGL for a given configuration spec.
+ * @param configSpec
+ */
+ public void start() {
+ if (LOG_EGL) {
+ Log.w("EglHelper", "start() tid=" + Thread.currentThread().getId());
+ }
+ /*
+ * Get an EGL instance
+ */
+ mEgl = (EGL10) EGLContext.getEGL();
+
+ /*
+ * Get to the default display.
+ */
+ mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
+
+ if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
+ throw new RuntimeException("eglGetDisplay failed");
+ }
+
+ /*
+ * We can now initialize EGL for that display
+ */
+ int[] version = new int[2];
+ if(!mEgl.eglInitialize(mEglDisplay, version)) {
+ throw new RuntimeException("eglInitialize failed");
+ }
+ GLSurfaceViewCustom view = mGLSurfaceViewWeakRef.get();
+ if (view == null) {
+ mEglConfig = null;
+ mEglContext = null;
+ } else {
+ mEglConfig = view.mEGLConfigChooser.chooseConfig(mEgl, mEglDisplay);
+
+ /*
+ * Create an EGL context. We want to do this as rarely as we can, because an
+ * EGL context is a somewhat heavy object.
+ */
+ mEglContext = view.mEGLContextFactory.createContext(mEgl, mEglDisplay, mEglConfig);
+ }
+ if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) {
+ mEglContext = null;
+ throwEglException("createContext");
+ }
+ if (LOG_EGL) {
+ Log.w("EglHelper", "createContext " + mEglContext + " tid=" + Thread.currentThread().getId());
+ }
+
+ mEglSurface = null;
+ }
+
+ /**
+ * Create an egl surface for the current SurfaceHolder surface. If a surface
+ * already exists, destroy it before creating the new surface.
+ *
+ * @return true if the surface was created successfully.
+ */
+ public boolean createSurface() {
+ if (LOG_EGL) {
+ Log.w("EglHelper", "createSurface() tid=" + Thread.currentThread().getId());
+ }
+ /*
+ * Check preconditions.
+ */
+ if (mEgl == null) {
+ throw new RuntimeException("egl not initialized");
+ }
+ if (mEglDisplay == null) {
+ throw new RuntimeException("eglDisplay not initialized");
+ }
+ if (mEglConfig == null) {
+ throw new RuntimeException("mEglConfig not initialized");
+ }
+
+ /*
+ * The window size has changed, so we need to create a new
+ * surface.
+ */
+ destroySurfaceImp();
+
+ /*
+ * Create an EGL surface we can render into.
+ */
+ GLSurfaceViewCustom view = mGLSurfaceViewWeakRef.get();
+ if (view != null) {
+ mEglSurface = view.mEGLWindowSurfaceFactory.createWindowSurface(mEgl,
+ mEglDisplay, mEglConfig, view.getHolder());
+ } else {
+ mEglSurface = null;
+ }
+
+ if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
+ int error = mEgl.eglGetError();
+ if (error == EGL10.EGL_BAD_NATIVE_WINDOW) {
+ Log.e("EglHelper", "createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
+ }
+ return false;
+ }
+
+ /*
+ * Before we can issue GL commands, we need to make sure
+ * the context is current and bound to a surface.
+ */
+ if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
+ /*
+ * Could not make the context current, probably because the underlying
+ * SurfaceView surface has been destroyed.
+ */
+ logEglErrorAsWarning("EGLHelper", "eglMakeCurrent", mEgl.eglGetError());
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Create a GL object for the current EGL context.
+ * @return
+ */
+ GL createGL() {
+
+ GL gl = mEglContext.getGL();
+ GLSurfaceViewCustom view = mGLSurfaceViewWeakRef.get();
+ if (view != null) {
+ if (view.mGLWrapper != null) {
+ gl = view.mGLWrapper.wrap(gl);
+ }
+
+/* if ((view.mDebugFlags & (DEBUG_CHECK_GL_ERROR | DEBUG_LOG_GL_CALLS)) != 0) {
+ int configFlags = 0;
+ Writer log = null;
+ if ((view.mDebugFlags & DEBUG_CHECK_GL_ERROR) != 0) {
+ configFlags |= GLDebugHelper.CONFIG_CHECK_GL_ERROR;
+ }
+ if ((view.mDebugFlags & DEBUG_LOG_GL_CALLS) != 0) {
+ log = new LogWriter();
+ }
+ gl = GLDebugHelper.wrap(gl, configFlags, log);
+ }*/
+ }
+ return gl;
+ }
+
+ /**
+ * Display the current render surface.
+ * @return the EGL error code from eglSwapBuffers.
+ */
+ public int swap() {
+ if (! mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
+ return mEgl.eglGetError();
+ }
+ return EGL10.EGL_SUCCESS;
+ }
+
+ public void destroySurface() {
+ if (LOG_EGL) {
+ Log.w("EglHelper", "destroySurface() tid=" + Thread.currentThread().getId());
+ }
+ destroySurfaceImp();
+ }
+
+ private void destroySurfaceImp() {
+ if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) {
+ mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE,
+ EGL10.EGL_NO_SURFACE,
+ EGL10.EGL_NO_CONTEXT);
+ GLSurfaceViewCustom view = mGLSurfaceViewWeakRef.get();
+ if (view != null) {
+ view.mEGLWindowSurfaceFactory.destroySurface(mEgl, mEglDisplay, mEglSurface);
+ }
+ mEglSurface = null;
+ }
+ }
+
+ public void finish() {
+ if (LOG_EGL) {
+ Log.w("EglHelper", "finish() tid=" + Thread.currentThread().getId());
+ }
+ if (mEglContext != null) {
+ GLSurfaceViewCustom view = mGLSurfaceViewWeakRef.get();
+ if (view != null) {
+ view.mEGLContextFactory.destroyContext(mEgl, mEglDisplay, mEglContext);
+ }
+ mEglContext = null;
+ }
+ if (mEglDisplay != null) {
+ mEgl.eglTerminate(mEglDisplay);
+ mEglDisplay = null;
+ }
+ }
+
+ private void throwEglException(String function) {
+ throwEglException(function, mEgl.eglGetError());
+ }
+
+ public static void throwEglException(String function, int error) {
+ String message = formatEglError(function, error);
+ if (LOG_THREADS) {
+ Log.e("EglHelper", "throwEglException tid=" + Thread.currentThread().getId() + " "
+ + message);
+ }
+ throw new RuntimeException(message);
+ }
+
+ public static void logEglErrorAsWarning(String tag, String function, int error) {
+ Log.w(tag, formatEglError(function, error));
+ }
+
+ public static String formatEglError(String function, int error) {
+ return function + " failed: " + error;
+ }
+
+ private WeakReference<GLSurfaceViewCustom> mGLSurfaceViewWeakRef;
+ EGL10 mEgl;
+ EGLDisplay mEglDisplay;
+ EGLSurface mEglSurface;
+ EGLConfig mEglConfig;
+ EGLContext mEglContext;
+
+ }
+
+ /**
+ * A generic GL Thread. Takes care of initializing EGL and GL. Delegates
+ * to a Renderer instance to do the actual drawing. Can be configured to
+ * render continuously or on request.
+ *
+ * All potentially blocking synchronization is done through the
+ * sGLThreadManager object. This avoids multiple-lock ordering issues.
+ *
+ */
+ static class GLThread extends Thread {
+ GLThread(WeakReference<GLSurfaceViewCustom> glSurfaceViewWeakRef) {
+ super();
+ mWidth = 0;
+ mHeight = 0;
+ mRequestRender = true;
+ mRenderMode = RENDERMODE_CONTINUOUSLY;
+ mGLSurfaceViewWeakRef = glSurfaceViewWeakRef;
+ }
+
+ @Override
+ public void run() {
+ setName("GLThread " + getId());
+ if (LOG_THREADS) {
+ Log.i("GLThread", "starting tid=" + getId());
+ }
+
+ try {
+ guardedRun();
+ } catch (InterruptedException e) {
+ // fall thru and exit normally
+ } finally {
+ sGLThreadManager.threadExiting(this);
+ }
+ }
+
+ /*
+ * This private method should only be called inside a
+ * synchronized(sGLThreadManager) block.
+ */
+ private void stopEglSurfaceLocked() {
+ if (mHaveEglSurface) {
+ mHaveEglSurface = false;
+ mEglHelper.destroySurface();
+ }
+ }
+
+ /*
+ * This private method should only be called inside a
+ * synchronized(sGLThreadManager) block.
+ */
+ private void stopEglContextLocked() {
+ if (mHaveEglContext) {
+ mEglHelper.finish();
+ mHaveEglContext = false;
+ sGLThreadManager.releaseEglContextLocked(this);
+ }
+ }
+ private void guardedRun() throws InterruptedException {
+ mEglHelper = new EglHelper(mGLSurfaceViewWeakRef);
+ mHaveEglContext = false;
+ mHaveEglSurface = false;
+ try {
+ GL10 gl = null;
+ boolean createEglContext = false;
+ boolean createEglSurface = false;
+ boolean createGlInterface = false;
+ boolean lostEglContext = false;
+ boolean sizeChanged = false;
+ boolean wantRenderNotification = false;
+ boolean doRenderNotification = false;
+ boolean askedToReleaseEglContext = false;
+ int w = 0;
+ int h = 0;
+ Runnable event = null;
+
+ while (true) {
+ synchronized (sGLThreadManager) {
+ while (true) {
+ if (mShouldExit) {
+ return;
+ }
+
+ if (! mEventQueue.isEmpty()) {
+ event = mEventQueue.remove(0);
+ break;
+ }
+
+ // Update the pause state.
+ boolean pausing = false;
+ if (mPaused != mRequestPaused) {
+ pausing = mRequestPaused;
+ mPaused = mRequestPaused;
+ sGLThreadManager.notifyAll();
+ if (LOG_PAUSE_RESUME) {
+ Log.i("GLThread", "mPaused is now " + mPaused + " tid=" + getId());
+ }
+ }
+
+ // Do we need to give up the EGL context?
+ if (mShouldReleaseEglContext) {
+ if (LOG_SURFACE) {
+ Log.i("GLThread", "releasing EGL context because asked to tid=" + getId());
+ }
+ stopEglSurfaceLocked();
+ stopEglContextLocked();
+ mShouldReleaseEglContext = false;
+ askedToReleaseEglContext = true;
+ }
+
+ // Have we lost the EGL context?
+ if (lostEglContext) {
+ stopEglSurfaceLocked();
+ stopEglContextLocked();
+ lostEglContext = false;
+ }
+
+ // When pausing, release the EGL surface:
+ if (pausing && mHaveEglSurface) {
+ if (LOG_SURFACE) {
+ Log.i("GLThread", "releasing EGL surface because paused tid=" + getId());
+ }
+ stopEglSurfaceLocked();
+ }
+
+ // When pausing, optionally release the EGL Context:
+ if (pausing && mHaveEglContext) {
+ GLSurfaceViewCustom view = mGLSurfaceViewWeakRef.get();
+ boolean preserveEglContextOnPause = view == null ?
+ false : view.mPreserveEGLContextOnPause;
+ if (!preserveEglContextOnPause || sGLThreadManager.shouldReleaseEGLContextWhenPausing()) {
+ stopEglContextLocked();
+ if (LOG_SURFACE) {
+ Log.i("GLThread", "releasing EGL context because paused tid=" + getId());
+ }
+ }
+ }
+
+ // When pausing, optionally terminate EGL:
+ if (pausing) {
+ if (sGLThreadManager.shouldTerminateEGLWhenPausing()) {
+ mEglHelper.finish();
+ if (LOG_SURFACE) {
+ Log.i("GLThread", "terminating EGL because paused tid=" + getId());
+ }
+ }
+ }
+
+ // Have we lost the SurfaceView surface?
+ if ((! mHasSurface) && (! mWaitingForSurface)) {
+ if (LOG_SURFACE) {
+ Log.i("GLThread", "noticed surfaceView surface lost tid=" + getId());
+ }
+ if (mHaveEglSurface) {
+ stopEglSurfaceLocked();
+ }
+ mWaitingForSurface = true;
+ mSurfaceIsBad = false;
+ sGLThreadManager.notifyAll();
+ }
+
+ // Have we acquired the surface view surface?
+ if (mHasSurface && mWaitingForSurface) {
+ if (LOG_SURFACE) {
+ Log.i("GLThread", "noticed surfaceView surface acquired tid=" + getId());
+ }
+ mWaitingForSurface = false;
+ sGLThreadManager.notifyAll();
+ }
+
+ if (doRenderNotification) {
+ if (LOG_SURFACE) {
+ Log.i("GLThread", "sending render notification tid=" + getId());
+ }
+ wantRenderNotification = false;
+ doRenderNotification = false;
+ mRenderComplete = true;
+ sGLThreadManager.notifyAll();
+ }
+
+ // Ready to draw?
+ if (readyToDraw()) {
+
+ // If we don't have an EGL context, try to acquire one.
+ if (! mHaveEglContext) {
+ if (askedToReleaseEglContext) {
+ askedToReleaseEglContext = false;
+ } else if (sGLThreadManager.tryAcquireEglContextLocked(this)) {
+ try {
+ mEglHelper.start();
+ } catch (RuntimeException t) {
+ sGLThreadManager.releaseEglContextLocked(this);
+ throw t;
+ }
+ mHaveEglContext = true;
+ createEglContext = true;
+
+ sGLThreadManager.notifyAll();
+ }
+ }
+
+ if (mHaveEglContext && !mHaveEglSurface) {
+ mHaveEglSurface = true;
+ createEglSurface = true;
+ createGlInterface = true;
+ sizeChanged = true;
+ }
+
+ if (mHaveEglSurface) {
+ if (mSizeChanged) {
+ sizeChanged = true;
+ w = mWidth;
+ h = mHeight;
+ wantRenderNotification = true;
+ if (LOG_SURFACE) {
+ Log.i("GLThread",
+ "noticing that we want render notification tid="
+ + getId());
+ }
+
+ // Destroy and recreate the EGL surface.
+ createEglSurface = true;
+
+ mSizeChanged = false;
+ }
+ mRequestRender = false;
+ sGLThreadManager.notifyAll();
+ break;
+ }
+ }
+
+ // By design, this is the only place in a GLThread thread where we wait().
+ if (LOG_THREADS) {
+ Log.i("GLThread", "waiting tid=" + getId()
+ + " mHaveEglContext: " + mHaveEglContext
+ + " mHaveEglSurface: " + mHaveEglSurface
+ + " mPaused: " + mPaused
+ + " mHasSurface: " + mHasSurface
+ + " mSurfaceIsBad: " + mSurfaceIsBad
+ + " mWaitingForSurface: " + mWaitingForSurface
+ + " mWidth: " + mWidth
+ + " mHeight: " + mHeight
+ + " mRequestRender: " + mRequestRender
+ + " mRenderMode: " + mRenderMode);
+ }
+ sGLThreadManager.wait();
+ }
+ } // end of synchronized(sGLThreadManager)
+
+ if (event != null) {
+ event.run();
+ event = null;
+ continue;
+ }
+
+ if (createEglSurface) {
+ if (LOG_SURFACE) {
+ Log.w("GLThread", "egl createSurface");
+ }
+ if (!mEglHelper.createSurface()) {
+ synchronized(sGLThreadManager) {
+ mSurfaceIsBad = true;
+ sGLThreadManager.notifyAll();
+ }
+ continue;
+ }
+ createEglSurface = false;
+ }
+
+ if (createGlInterface) {
+ gl = (GL10) mEglHelper.createGL();
+
+ sGLThreadManager.checkGLDriver(gl);
+ createGlInterface = false;
+ }
+
+ if (createEglContext) {
+ if (LOG_RENDERER) {
+ Log.w("GLThread", "onSurfaceCreated");
+ }
+ GLSurfaceViewCustom view = mGLSurfaceViewWeakRef.get();
+ if (view != null) {
+ view.mRenderer.onSurfaceCreated(gl, mEglHelper.mEglConfig);
+ }
+ createEglContext = false;
+ }
+
+ if (sizeChanged) {
+ if (LOG_RENDERER) {
+ Log.w("GLThread", "onSurfaceChanged(" + w + ", " + h + ")");
+ }
+ GLSurfaceViewCustom view = mGLSurfaceViewWeakRef.get();
+ if (view != null) {
+ view.mRenderer.onSurfaceChanged(gl, w, h);
+ }
+ sizeChanged = false;
+ }
+
+ if (LOG_RENDERER_DRAW_FRAME) {
+ Log.w("GLThread", "onDrawFrame tid=" + getId());
+ }
+ {
+ GLSurfaceViewCustom view = mGLSurfaceViewWeakRef.get();
+ if (view != null) {
+ view.mRenderer.onDrawFrame(gl);
+ }
+ }
+ int swapError = mEglHelper.swap();
+ switch (swapError) {
+ case EGL10.EGL_SUCCESS:
+ GLSurfaceViewCustom view = mGLSurfaceViewWeakRef.get();
+ if (view != null) {
+ view.mRenderer.onEglSwapBuffers();
+ }
+ break;
+ case EGL11.EGL_CONTEXT_LOST:
+ if (LOG_SURFACE) {
+ Log.i("GLThread", "egl context lost tid=" + getId());
+ }
+ lostEglContext = true;
+ break;
+ default:
+ // Other errors typically mean that the current surface is bad,
+ // probably because the SurfaceView surface has been destroyed,
+ // but we haven't been notified yet.
+ // Log the error to help developers understand why rendering stopped.
+ EglHelper.logEglErrorAsWarning("GLThread", "eglSwapBuffers", swapError);
+
+ synchronized(sGLThreadManager) {
+ mSurfaceIsBad = true;
+ sGLThreadManager.notifyAll();
+ }
+ break;
+ }
+
+ if (wantRenderNotification) {
+ doRenderNotification = true;
+ }
+ }
+
+ } finally {
+ /*
+ * clean-up everything...
+ */
+ synchronized (sGLThreadManager) {
+ stopEglSurfaceLocked();
+ stopEglContextLocked();
+ }
+ }
+ }
+
+ public boolean ableToDraw() {
+ return mHaveEglContext && mHaveEglSurface && readyToDraw();
+ }
+
+ private boolean readyToDraw() {
+ return (!mPaused) && mHasSurface && (!mSurfaceIsBad)
+ && (mWidth > 0) && (mHeight > 0)
+ && (mRequestRender || (mRenderMode == RENDERMODE_CONTINUOUSLY));
+ }
+
+ public void setRenderMode(int renderMode) {
+ if ( !((RENDERMODE_WHEN_DIRTY <= renderMode) && (renderMode <= RENDERMODE_CONTINUOUSLY)) ) {
+ throw new IllegalArgumentException("renderMode");
+ }
+ synchronized(sGLThreadManager) {
+ mRenderMode = renderMode;
+ sGLThreadManager.notifyAll();
+ }
+ }
+
+ public int getRenderMode() {
+ synchronized(sGLThreadManager) {
+ return mRenderMode;
+ }
+ }
+
+ public void requestRender() {
+ synchronized(sGLThreadManager) {
+ mRequestRender = true;
+ sGLThreadManager.notifyAll();
+ }
+ }
+
+ public void surfaceCreated() {
+ synchronized(sGLThreadManager) {
+ if (LOG_THREADS) {
+ Log.i("GLThread", "surfaceCreated tid=" + getId());
+ }
+ mHasSurface = true;
+ sGLThreadManager.notifyAll();
+ while((mWaitingForSurface) && (!mExited)) {
+ try {
+ sGLThreadManager.wait();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+ }
+
+ public void surfaceDestroyed() {
+ synchronized(sGLThreadManager) {
+ if (LOG_THREADS) {
+ Log.i("GLThread", "surfaceDestroyed tid=" + getId());
+ }
+ mHasSurface = false;
+ sGLThreadManager.notifyAll();
+ while((!mWaitingForSurface) && (!mExited)) {
+ try {
+ sGLThreadManager.wait();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+ }
+
+ public void onPause() {
+ synchronized (sGLThreadManager) {
+ if (LOG_PAUSE_RESUME) {
+ Log.i("GLThread", "onPause tid=" + getId());
+ }
+ mRequestPaused = true;
+ sGLThreadManager.notifyAll();
+ while ((! mExited) && (! mPaused)) {
+ if (LOG_PAUSE_RESUME) {
+ Log.i("Main thread", "onPause waiting for mPaused.");
+ }
+ try {
+ sGLThreadManager.wait();
+ } catch (InterruptedException ex) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+ }
+
+ public void onResume() {
+ synchronized (sGLThreadManager) {
+ if (LOG_PAUSE_RESUME) {
+ Log.i("GLThread", "onResume tid=" + getId());
+ }
+ mRequestPaused = false;
+ mRequestRender = true;
+ mRenderComplete = false;
+ sGLThreadManager.notifyAll();
+ while ((! mExited) && mPaused && (!mRenderComplete)) {
+ if (LOG_PAUSE_RESUME) {
+ Log.i("Main thread", "onResume waiting for !mPaused.");
+ }
+ try {
+ sGLThreadManager.wait();
+ } catch (InterruptedException ex) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+ }
+
+ public void onWindowResize(int w, int h) {
+ synchronized (sGLThreadManager) {
+ mWidth = w;
+ mHeight = h;
+ mSizeChanged = true;
+ mRequestRender = true;
+ mRenderComplete = false;
+ sGLThreadManager.notifyAll();
+
+ // Wait for thread to react to resize and render a frame
+ while (! mExited && !mPaused && !mRenderComplete
+ && ableToDraw()) {
+ if (LOG_SURFACE) {
+ Log.i("Main thread", "onWindowResize waiting for render complete from tid=" + getId());
+ }
+ try {
+ sGLThreadManager.wait();
+ } catch (InterruptedException ex) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+ }
+
+ public void requestExitAndWait() {
+ // don't call this from GLThread thread or it is a guaranteed
+ // deadlock!
+ synchronized(sGLThreadManager) {
+ mShouldExit = true;
+ sGLThreadManager.notifyAll();
+ while (! mExited) {
+ try {
+ sGLThreadManager.wait();
+ } catch (InterruptedException ex) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+ }
+
+ public void requestReleaseEglContextLocked() {
+ mShouldReleaseEglContext = true;
+ sGLThreadManager.notifyAll();
+ }
+
+ /**
+ * Queue an "event" to be run on the GL rendering thread.
+ * @param r the runnable to be run on the GL rendering thread.
+ */
+ public void queueEvent(Runnable r) {
+ if (r == null) {
+ throw new IllegalArgumentException("r must not be null");
+ }
+ synchronized(sGLThreadManager) {
+ mEventQueue.add(r);
+ sGLThreadManager.notifyAll();
+ }
+ }
+
+ // Once the thread is started, all accesses to the following member
+ // variables are protected by the sGLThreadManager monitor
+ private boolean mShouldExit;
+ private boolean mExited;
+ private boolean mRequestPaused;
+ private boolean mPaused;
+ private boolean mHasSurface;
+ private boolean mSurfaceIsBad;
+ private boolean mWaitingForSurface;
+ private boolean mHaveEglContext;
+ private boolean mHaveEglSurface;
+ private boolean mShouldReleaseEglContext;
+ private int mWidth;
+ private int mHeight;
+ private int mRenderMode;
+ private boolean mRequestRender;
+ private boolean mRenderComplete;
+ private ArrayList<Runnable> mEventQueue = new ArrayList<Runnable>();
+ private boolean mSizeChanged = true;
+
+ // End of member variables protected by the sGLThreadManager monitor.
+
+ private EglHelper mEglHelper;
+
+ /**
+ * Set once at thread construction time, nulled out when the parent view is garbage
+ * called. This weak reference allows the GLSurfaceView to be garbage collected while
+ * the GLThread is still alive.
+ */
+ private WeakReference<GLSurfaceViewCustom> mGLSurfaceViewWeakRef;
+
+ }
+
+ static class LogWriter extends Writer {
+
+ @Override public void close() {
+ flushBuilder();
+ }
+
+ @Override public void flush() {
+ flushBuilder();
+ }
+
+ @Override public void write(char[] buf, int offset, int count) {
+ for(int i = 0; i < count; i++) {
+ char c = buf[offset + i];
+ if ( c == '\n') {
+ flushBuilder();
+ }
+ else {
+ mBuilder.append(c);
+ }
+ }
+ }
+
+ private void flushBuilder() {
+ if (mBuilder.length() > 0) {
+ Log.v("GLSurfaceViewCustom", mBuilder.toString());
+ mBuilder.delete(0, mBuilder.length());
+ }
+ }
+
+ private StringBuilder mBuilder = new StringBuilder();
+ }
+
+
+ private void checkRenderThreadState() {
+ if (mGLThread != null) {
+ throw new IllegalStateException(
+ "setRenderer has already been called for this instance.");
+ }
+ }
+
+ private static class GLThreadManager {
+ private static String TAG = "GLThreadManager";
+
+ public synchronized void threadExiting(GLThread thread) {
+ if (LOG_THREADS) {
+ Log.i("GLThread", "exiting tid=" + thread.getId());
+ }
+ thread.mExited = true;
+ if (mEglOwner == thread) {
+ mEglOwner = null;
+ }
+ notifyAll();
+ }
+
+ /*
+ * Tries once to acquire the right to use an EGL
+ * context. Does not block. Requires that we are already
+ * in the sGLThreadManager monitor when this is called.
+ *
+ * @return true if the right to use an EGL context was acquired.
+ */
+ public boolean tryAcquireEglContextLocked(GLThread thread) {
+ if (mEglOwner == thread || mEglOwner == null) {
+ mEglOwner = thread;
+ notifyAll();
+ return true;
+ }
+ checkGLESVersion();
+ if (mMultipleGLESContextsAllowed) {
+ return true;
+ }
+ // Notify the owning thread that it should release the context.
+ // TODO: implement a fairness policy. Currently
+ // if the owning thread is drawing continuously it will just
+ // reacquire the EGL context.
+ if (mEglOwner != null) {
+ mEglOwner.requestReleaseEglContextLocked();
+ }
+ return false;
+ }
+
+ /*
+ * Releases the EGL context. Requires that we are already in the
+ * sGLThreadManager monitor when this is called.
+ */
+ public void releaseEglContextLocked(GLThread thread) {
+ if (mEglOwner == thread) {
+ mEglOwner = null;
+ }
+ notifyAll();
+ }
+
+ public synchronized boolean shouldReleaseEGLContextWhenPausing() {
+ // Release the EGL context when pausing even if
+ // the hardware supports multiple EGL contexts.
+ // Otherwise the device could run out of EGL contexts.
+ return mLimitedGLESContexts;
+ }
+
+ public synchronized boolean shouldTerminateEGLWhenPausing() {
+ checkGLESVersion();
+ return !mMultipleGLESContextsAllowed;
+ }
+
+ public synchronized void checkGLDriver(GL10 gl) {
+ if (! mGLESDriverCheckComplete) {
+ checkGLESVersion();
+ String renderer = gl.glGetString(GL10.GL_RENDERER);
+ if (mGLESVersion < kGLES_20) {
+ mMultipleGLESContextsAllowed =
+ ! renderer.startsWith(kMSM7K_RENDERER_PREFIX);
+ notifyAll();
+ }
+ mLimitedGLESContexts = !mMultipleGLESContextsAllowed;
+ if (LOG_SURFACE) {
+ Log.w(TAG, "checkGLDriver renderer = \"" + renderer + "\" multipleContextsAllowed = "
+ + mMultipleGLESContextsAllowed
+ + " mLimitedGLESContexts = " + mLimitedGLESContexts);
+ }
+ mGLESDriverCheckComplete = true;
+ }
+ }
+
+ private void checkGLESVersion() {
+ if (! mGLESVersionCheckComplete) {
+ mGLESVersion = kGLES_20; /*SystemProperties.getInt(
+ "ro.opengles.version",
+ ConfigurationInfo.GL_ES_VERSION_UNDEFINED); */
+ if (mGLESVersion >= kGLES_20) {
+ mMultipleGLESContextsAllowed = true;
+ }
+ if (LOG_SURFACE) {
+ Log.w(TAG, "checkGLESVersion mGLESVersion =" +
+ " " + mGLESVersion + " mMultipleGLESContextsAllowed = " + mMultipleGLESContextsAllowed);
+ }
+ mGLESVersionCheckComplete = true;
+ }
+ }
+
+ /**
+ * This check was required for some pre-Android-3.0 hardware. Android 3.0 provides
+ * support for hardware-accelerated views, therefore multiple EGL contexts are
+ * supported on all Android 3.0+ EGL drivers.
+ */
+ private boolean mGLESVersionCheckComplete;
+ private int mGLESVersion;
+ private boolean mGLESDriverCheckComplete;
+ private boolean mMultipleGLESContextsAllowed;
+ private boolean mLimitedGLESContexts;
+ private static final int kGLES_20 = 0x20000;
+ private static final String kMSM7K_RENDERER_PREFIX =
+ "Q3Dimension MSM7500 ";
+ private GLThread mEglOwner;
+ }
+
+ private static final GLThreadManager sGLThreadManager = new GLThreadManager();
+
+ private final WeakReference<GLSurfaceViewCustom> mThisWeakRef =
+ new WeakReference<GLSurfaceViewCustom>(this);
+ private GLThread mGLThread;
+ private Renderer mRenderer;
+ private boolean mDetached;
+ private EGLConfigChooser mEGLConfigChooser;
+ private EGLContextFactory mEGLContextFactory;
+ private EGLWindowSurfaceFactory mEGLWindowSurfaceFactory;
+ private GLWrapper mGLWrapper;
+ private int mDebugFlags;
+ private int mEGLContextClientVersion;
+ private boolean mPreserveEGLContextOnPause;
+}
diff --git a/tests/tests/openglperf/src/android/openglperf/cts/GlPlanetsActivity.java b/tests/tests/openglperf/src/android/openglperf/cts/GlPlanetsActivity.java
index 5a8c0a8..7aa1d21 100644
--- a/tests/tests/openglperf/src/android/openglperf/cts/GlPlanetsActivity.java
+++ b/tests/tests/openglperf/src/android/openglperf/cts/GlPlanetsActivity.java
@@ -34,14 +34,15 @@
public static final String INTENT_EXTRA_USE_VBO_VERTICES = "useVboVertices";
public static final String INTENT_EXTRA_USE_VBO_INDICES = "useVboIndiices";
public static final String INTENT_EXTRA_NUM_FRAMES = "numFrames";
- public static final String INTENT_EXTRA_INDICES_PER_VERTEX = "numIndicesPerVertex";
+ public static final String INTENT_EXTRA_NUM_INDEX_BUFFERS = "numIndexBuffers";
public static final String INTENT_RESULT_FPS = "fps";
public static final String INTENT_RESULT_NUM_TRIANGLES = "numTrigngles";
private final Semaphore mSem = new Semaphore(0);
- private float mFps;
+ private float mAverageFps;
private int mNumTriangles;
+ private int[] mFrameInterval;
private PlanetsSurfaceView mView;
@@ -50,14 +51,23 @@
return mSem.tryAcquire(timeoutInSecs, TimeUnit.SECONDS);
}
- public float getFps() {
- return mFps;
+ public float getAverageFps() {
+ return mAverageFps;
}
public int getNumTriangles() {
return mNumTriangles;
}
+ /**
+ * Time interval between each frame's rendering in ms.
+ * The first value will be invalid, so client should discard them.
+ * @return can return null if INTENT_EXTRA_NUM_FRAMES was not set in intent.
+ */
+ public int[] getFrameInterval() {
+ return mFrameInterval;
+ }
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -70,16 +80,17 @@
param.mUseVboForIndices = intent.getBooleanExtra(INTENT_EXTRA_USE_VBO_INDICES,
param.mUseVboForIndices);
param.mNumFrames = intent.getIntExtra(INTENT_EXTRA_NUM_FRAMES, param.mNumFrames);
- param.mNumIndicesPerVertex = intent.getIntExtra(INTENT_EXTRA_INDICES_PER_VERTEX,
+ param.mNumIndicesPerVertex = intent.getIntExtra(INTENT_EXTRA_NUM_INDEX_BUFFERS,
param.mNumIndicesPerVertex);
mView = new PlanetsSurfaceView(this, param, this);
setContentView(mView);
}
@Override
- public void onRenderCompletion(float fps, int numTriangles) {
- mFps = fps;
+ public void onRenderCompletion(float averageFps, int numTriangles, int[] frameInterval) {
+ mAverageFps = averageFps;
mNumTriangles = numTriangles;
+ mFrameInterval = frameInterval;
mSem.release();
}
diff --git a/tests/tests/openglperf/src/android/openglperf/cts/GlVboPerfTest.java b/tests/tests/openglperf/src/android/openglperf/cts/GlVboPerfTest.java
index afd435c..2e9e10b 100644
--- a/tests/tests/openglperf/src/android/openglperf/cts/GlVboPerfTest.java
+++ b/tests/tests/openglperf/src/android/openglperf/cts/GlVboPerfTest.java
@@ -87,14 +87,14 @@
}
private void runRendering(int numPlanets, boolean useVboVertex, boolean useVboIndex,
- int indicesPerVertex) throws Exception {
+ int numIndexBuffers) throws Exception {
Intent intent = new Intent();
intent.putExtra(GlPlanetsActivity.INTENT_EXTRA_NUM_FRAMES,
NUM_FRAMES_TO_RENDER);
intent.putExtra(GlPlanetsActivity.INTENT_EXTRA_NUM_PLANETS, numPlanets);
intent.putExtra(GlPlanetsActivity.INTENT_EXTRA_USE_VBO_VERTICES, useVboVertex);
intent.putExtra(GlPlanetsActivity.INTENT_EXTRA_USE_VBO_INDICES, useVboIndex);
- intent.putExtra(GlPlanetsActivity.INTENT_EXTRA_INDICES_PER_VERTEX, indicesPerVertex);
+ intent.putExtra(GlPlanetsActivity.INTENT_EXTRA_NUM_INDEX_BUFFERS, numIndexBuffers);
setActivityIntent(intent);
final GlPlanetsActivity activity = getActivity();
@@ -102,7 +102,7 @@
.waitForGlPlanetsCompletionWithTimeout(RENDERING_TIMEOUT);
assertTrue("timeout while waiting for rendering completion", waitResult);
- mFps = activity.getFps();
+ mFps = activity.getAverageFps();
mNumTriangles = activity.getNumTriangles();
cleanUpActivity();
diff --git a/tests/tests/openglperf/src/android/openglperf/cts/OpenGlPerfNative.java b/tests/tests/openglperf/src/android/openglperf/cts/OpenGlPerfNative.java
new file mode 100644
index 0000000..d63a640
--- /dev/null
+++ b/tests/tests/openglperf/src/android/openglperf/cts/OpenGlPerfNative.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.openglperf.cts;
+
+public class OpenGlPerfNative {
+ static {
+ System.loadLibrary("ctsopenglperf_jni");
+ }
+ /**
+ * wait for the completion of the pending Egl/Gl operation using eglClientWaitSyncKHR.
+ * @param waitTimeInNs maximum time to wait in nano secs
+ * @return true for success, false for timeout
+ */
+ public static native boolean waitForEglCompletion(long waitTimeInNs);
+}
diff --git a/tests/tests/openglperf/src/android/openglperf/cts/PlanetsRenderer.java b/tests/tests/openglperf/src/android/openglperf/cts/PlanetsRenderer.java
index 5b90089..b286e46 100644
--- a/tests/tests/openglperf/src/android/openglperf/cts/PlanetsRenderer.java
+++ b/tests/tests/openglperf/src/android/openglperf/cts/PlanetsRenderer.java
@@ -17,10 +17,10 @@
package android.openglperf.cts;
import android.content.Context;
+import android.cts.util.WatchDog;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLES20;
-import android.opengl.GLSurfaceView;
import android.opengl.GLUtils;
import android.opengl.Matrix;
import android.util.Log;
@@ -34,18 +34,21 @@
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
-
-public class PlanetsRenderer implements GLSurfaceView.Renderer {
+/**
+ * OpenGl renderer rendering given number of planets with different GL configuration.
+ */
+public class PlanetsRenderer implements GLSurfaceViewCustom.Renderer {
private static final String TAG = "PlanetsRenderer";
// texture is from
// http://en.wikipedia.org/wiki/File:Mercator_projection_SW.jpg
private static final String TEXTURE_FILE = "world_512_512.jpg";
+ private static final long EGL_SWAP_BUFFERS_WAIT_TIME_IN_NS = 100 * 1000 * 1000 * 1000L;
private final Context mContext;
private final PlanetsRenderingParam mParam;
private final RenderCompletionListener mListener;
- private final RenderingWatchDog mWatchDog;
+ private final WatchDog mWatchDog;
private final Sphere[] mSpheres;
private final int mNumSpheres;
@@ -65,9 +68,11 @@
private int mFrameCount = 0;
private static final int FPS_DISPLAY_INTERVAL = 50;
private long mLastFPSTime;
+ private long mLastRenderingTime;
// for total FPS measurement
private long mRenderingStartTime;
private long mMeasurementStartTime;
+ private int[] mFrameInterval = null;
private int mProgram; // shader program
private int mMVPMatrixHandle;
@@ -95,7 +100,7 @@
* @param listener
*/
public PlanetsRenderer(Context context, PlanetsRenderingParam param,
- RenderCompletionListener listener, RenderingWatchDog watchDog) {
+ RenderCompletionListener listener, WatchDog watchDog) {
resetTimer();
mContext = context;
mParam = param;
@@ -104,6 +109,9 @@
mNumIndices = mNumSpheres * mParam.mNumIndicesPerVertex;
mSpheres = new Sphere[mNumSpheres];
+ if (mParam.mNumFrames > 0) {
+ mFrameInterval = new int[mParam.mNumFrames];
+ }
printParams();
// for big model, this construction phase takes time...
@@ -121,6 +129,7 @@
measureTime("construction");
}
+ @Override
public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {
mProgram = createProgram(getVertexShader(), getFragmentShader());
if (mProgram == 0) {
@@ -137,31 +146,12 @@
mTextureId = createTexture2D();
}
+ @Override
public void onDrawFrame(GL10 glUnused) {
mWatchDog.reset();
long currentTime = System.currentTimeMillis();
-
mFrameCount++;
- if ((mFrameCount % FPS_DISPLAY_INTERVAL == 0) && (mFrameCount != 0)) {
- float fps = (((float) FPS_DISPLAY_INTERVAL)
- / ((float) (currentTime - mLastFPSTime)) * 1000.0f);
- // FPS is not correct if activity is paused/resumed.
- Log.i(TAG, "FPS " + fps);
- mLastFPSTime = currentTime;
- }
-
- if ((mFrameCount == mParam.mNumFrames) && (mParam.mNumFrames > 0)) {
- long timePassed = currentTime - mRenderingStartTime;
- float fps = ((float) mParam.mNumFrames) / ((float) timePassed) * 1000.0f;
- printGlInfos();
- printParams();
- int numTriangles = mNumSpheres * mSpheres[0].getTotalIndices() / 3;
- Log.i(TAG, "Final FPS " + fps + " Num triangles " + numTriangles);
- if (mListener != null) {
- mListener.onRenderCompletion(fps, numTriangles);
- return;
- }
- }
+ mLastRenderingTime = currentTime;
float angle = 0.090f * ((int) (currentTime % 4000L));
Matrix.setRotateM(mMMatrix, 0, angle, 0, 0, 1.0f);
@@ -232,6 +222,36 @@
}
}
+ @Override
+ public void onEglSwapBuffers() {
+ if (!OpenGlPerfNative.waitForEglCompletion(EGL_SWAP_BUFFERS_WAIT_TIME_IN_NS)) {
+ Log.w(TAG, "time-out or error while waiting for eglSwapBuffers completion");
+ }
+ long currentTime = System.currentTimeMillis();
+ if (mFrameCount == 0) {
+ mRenderingStartTime = currentTime;
+ }
+ if (mFrameCount < mParam.mNumFrames) {
+ mFrameInterval[mFrameCount] = (int)(currentTime - mLastRenderingTime);
+ }
+
+ if ((mFrameCount == mParam.mNumFrames) && (mParam.mNumFrames > 0)) {
+ long timePassed = currentTime - mRenderingStartTime;
+ float fps = ((float) mParam.mNumFrames) / ((float) timePassed) * 1000.0f;
+ printGlInfos();
+ printParams();
+ int numTriangles = mNumSpheres * mSpheres[0].getTotalIndices() / 3;
+ Log.i(TAG, "Final FPS " + fps + " Num triangles " + numTriangles + " start time " +
+ mRenderingStartTime + " finish time " + currentTime);
+ if (mListener != null) {
+ mListener.onRenderCompletion(fps, numTriangles, mFrameInterval);
+ mFrameCount++; // to prevent entering here again
+ return;
+ }
+ }
+ }
+
+ @Override
public void onSurfaceChanged(GL10 glUnused, int width, int height) {
mWidth = width;
mHeight = height;
diff --git a/tests/tests/openglperf/src/android/openglperf/cts/PlanetsSurfaceView.java b/tests/tests/openglperf/src/android/openglperf/cts/PlanetsSurfaceView.java
index 2bfd001..072988d 100644
--- a/tests/tests/openglperf/src/android/openglperf/cts/PlanetsSurfaceView.java
+++ b/tests/tests/openglperf/src/android/openglperf/cts/PlanetsSurfaceView.java
@@ -17,11 +17,11 @@
package android.openglperf.cts;
import android.content.Context;
-import android.opengl.GLSurfaceView;
+import android.cts.util.WatchDog;
-class PlanetsSurfaceView extends GLSurfaceView {
+class PlanetsSurfaceView extends GLSurfaceViewCustom {
private final long RENDERING_TIMEOUT = 1900; // in msec, close to 2 secs
- private final RenderingWatchDog mWatchDog = new RenderingWatchDog(RENDERING_TIMEOUT);
+ private final WatchDog mWatchDog = new WatchDog(RENDERING_TIMEOUT);
public PlanetsSurfaceView(Context context, PlanetsRenderingParam param,
RenderCompletionListener listener) {
@@ -44,4 +44,4 @@
setRenderMode(RENDERMODE_CONTINUOUSLY);
super.onResume();
}
-}
\ No newline at end of file
+}
diff --git a/tests/tests/openglperf/src/android/openglperf/cts/RenderCompletionListener.java b/tests/tests/openglperf/src/android/openglperf/cts/RenderCompletionListener.java
index 1643087..a5bbfa2 100644
--- a/tests/tests/openglperf/src/android/openglperf/cts/RenderCompletionListener.java
+++ b/tests/tests/openglperf/src/android/openglperf/cts/RenderCompletionListener.java
@@ -16,11 +16,15 @@
package android.openglperf.cts;
+/**
+ * Interface used to notify the completion of requested rendering.
+ */
public interface RenderCompletionListener {
/**
- * @param fps total frame rate
+ * @param averageFps average of total frames
* @param numTriangles Number of triangles in geometric model
+ * @param frameInterval interval for each frame in ms. Do not use the first one and the last one.
*/
- void onRenderCompletion(float fps, int numTriangles);
+ void onRenderCompletion(float averageFps, int numTriangles, int[] frameInterval);
}
diff --git a/tests/tests/openglperf/src/android/openglperf/cts/Sphere.java b/tests/tests/openglperf/src/android/openglperf/cts/Sphere.java
index 026ba97..8020db6 100644
--- a/tests/tests/openglperf/src/android/openglperf/cts/Sphere.java
+++ b/tests/tests/openglperf/src/android/openglperf/cts/Sphere.java
@@ -49,7 +49,7 @@
* @param x,y,z the origin of the sphere
* @param r the radius of the sphere
*/
- public Sphere(int nSlices, float x, float y, float z, float r, int indicesPerVertex) {
+ public Sphere(int nSlices, float x, float y, float z, float r, int numIndexBuffers) {
int iMax = nSlices + 1;
int nVertices = iMax * iMax;
@@ -64,17 +64,17 @@
// 3 vertex coords + 2 texture coords
mVertices = ByteBuffer.allocateDirect(nVertices * 5 * FLOAT_SIZE)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
- mIndices = new ShortBuffer[indicesPerVertex];
- mNumIndices = new int[indicesPerVertex];
+ mIndices = new ShortBuffer[numIndexBuffers];
+ mNumIndices = new int[numIndexBuffers];
// first evenly distribute to n-1 buffers, then put remaining ones to the last one.
- int noIndicesPerBuffer = (mTotalIndices / indicesPerVertex / 6) * 6;
- for (int i = 0; i < indicesPerVertex - 1; i++) {
+ int noIndicesPerBuffer = (mTotalIndices / numIndexBuffers / 6) * 6;
+ for (int i = 0; i < numIndexBuffers - 1; i++) {
mNumIndices[i] = noIndicesPerBuffer;
}
- mNumIndices[indicesPerVertex - 1] = mTotalIndices - noIndicesPerBuffer *
- (indicesPerVertex - 1);
+ mNumIndices[numIndexBuffers - 1] = mTotalIndices - noIndicesPerBuffer *
+ (numIndexBuffers - 1);
- for (int i = 0; i < indicesPerVertex; i++) {
+ for (int i = 0; i < numIndexBuffers; i++) {
mIndices[i] = ByteBuffer.allocateDirect(mNumIndices[i] * SHORT_SIZE)
.order(ByteOrder.nativeOrder()).asShortBuffer();
}
@@ -123,7 +123,7 @@
mIndices[bufferNum].put(indexBuffer, 0, mNumIndices[bufferNum]);
mVertices.position(0);
- for (int i = 0; i < indicesPerVertex; i++) {
+ for (int i = 0; i < numIndexBuffers; i++) {
mIndices[i].position(0);
}
}
diff --git a/tests/tests/os/src/android/os/cts/UsbDebuggingTest.java b/tests/tests/os/src/android/os/cts/UsbDebuggingTest.java
new file mode 100644
index 0000000..60583e7
--- /dev/null
+++ b/tests/tests/os/src/android/os/cts/UsbDebuggingTest.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.cts;
+
+import android.os.Build;
+import android.os.SystemProperties;
+import android.test.AndroidTestCase;
+import java.io.File;
+
+public class UsbDebuggingTest extends AndroidTestCase {
+
+ public void testUsbDebugging() {
+ // Secure USB debugging must be enabled
+ assertEquals("1", SystemProperties.get("ro.adb.secure"));
+
+ // Don't ship vendor keys in user build
+ if ("user".equals(Build.TYPE)) {
+ File keys = new File("/adb_keys");
+ assertFalse(keys.exists());
+ }
+ }
+
+}
diff --git a/tests/tests/os/src/android/os/cts/WorkSourceTest.java b/tests/tests/os/src/android/os/cts/WorkSourceTest.java
new file mode 100644
index 0000000..f3986b1
--- /dev/null
+++ b/tests/tests/os/src/android/os/cts/WorkSourceTest.java
@@ -0,0 +1,557 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.cts;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import android.os.WorkSource;
+import android.test.AndroidTestCase;
+
+public class WorkSourceTest extends AndroidTestCase {
+ private Constructor<WorkSource> mConstructWS;
+ private Object[] mConstructWSArgs = new Object[1];
+ private Method mAddUid;
+ private Object[] mAddUidArgs = new Object[1];
+ private Method mAddUidName;
+ private Object[] mAddUidNameArgs = new Object[2];
+ private Method mAddReturningNewbs;
+ private Object[] mAddReturningNewbsArgs = new Object[1];
+ private Method mSetReturningDiffs;
+ private Object[] mSetReturningDiffsArgs = new Object[1];
+ private Method mStripNames;
+ private Object[] mStripNamesArgs = new Object[0];
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mConstructWS = WorkSource.class.getConstructor(new Class[] { int.class });
+ mAddUid = WorkSource.class.getMethod("add", new Class[] { int.class });
+ mAddUidName = WorkSource.class.getMethod("add", new Class[] { int.class, String.class });
+ mAddReturningNewbs = WorkSource.class.getMethod("addReturningNewbs", new Class[] { WorkSource.class });
+ mSetReturningDiffs = WorkSource.class.getMethod("setReturningDiffs", new Class[] { WorkSource.class });
+ mStripNames = WorkSource.class.getMethod("stripNames", new Class[] { });
+ }
+
+ private WorkSource wsNew(int uid) throws IllegalArgumentException,
+ InstantiationException, IllegalAccessException, InvocationTargetException {
+ mConstructWSArgs[0] = uid;
+ return mConstructWS.newInstance(mConstructWSArgs);
+ }
+
+ private WorkSource wsNew(int[] uids) throws IllegalArgumentException,
+ InstantiationException, IllegalAccessException, InvocationTargetException {
+ WorkSource ws = new WorkSource();
+ for (int i=0; i<uids.length; i++) {
+ wsAdd(ws, uids[i]);
+ }
+ checkWorkSource("Constructed", ws, uids);
+ return ws;
+ }
+
+ private WorkSource wsNew(int[] uids, String[] names) throws IllegalArgumentException,
+ InstantiationException, IllegalAccessException, InvocationTargetException {
+ WorkSource ws = new WorkSource();
+ for (int i=0; i<uids.length; i++) {
+ wsAdd(ws, uids[i], names[i]);
+ }
+ checkWorkSource("Constructed", ws, uids, names);
+ return ws;
+ }
+
+ private boolean wsAdd(WorkSource ws, int uid) throws IllegalArgumentException,
+ InstantiationException, IllegalAccessException, InvocationTargetException {
+ mAddUidArgs[0] = uid;
+ return (Boolean)mAddUid.invoke(ws, mAddUidArgs);
+ }
+
+ private boolean wsAdd(WorkSource ws, int uid, String name) throws IllegalArgumentException,
+ InstantiationException, IllegalAccessException, InvocationTargetException {
+ mAddUidNameArgs[0] = uid;
+ mAddUidNameArgs[1] = name;
+ return (Boolean)mAddUidName.invoke(ws, mAddUidNameArgs);
+ }
+
+ private WorkSource wsAddReturningNewbs(WorkSource ws, WorkSource other) throws IllegalArgumentException,
+ InstantiationException, IllegalAccessException, InvocationTargetException {
+ mAddReturningNewbsArgs[0] = other;
+ return (WorkSource)mAddReturningNewbs.invoke(ws, mAddReturningNewbsArgs);
+ }
+
+ private WorkSource[] wsSetReturningDiffs(WorkSource ws, WorkSource other) throws IllegalArgumentException,
+ InstantiationException, IllegalAccessException, InvocationTargetException {
+ mSetReturningDiffsArgs[0] = other;
+ return (WorkSource[])mSetReturningDiffs.invoke(ws, mSetReturningDiffsArgs);
+ }
+
+ private WorkSource wsStripNames(WorkSource ws) throws IllegalArgumentException,
+ InstantiationException, IllegalAccessException, InvocationTargetException {
+ return (WorkSource)mStripNames.invoke(ws);
+ }
+
+ private void printArrays(StringBuilder sb, int[] uids, String[] names) {
+ sb.append("{ ");
+ for (int i=0; i<uids.length; i++) {
+ if (i > 0) sb.append(", ");
+ sb.append(uids[i]);
+ if (names != null) {
+ sb.append(" ");
+ sb.append(names[i]);
+ }
+ }
+ sb.append(" }");
+ }
+
+ private void failWorkSource(String op, WorkSource ws, int[] uids) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(op);
+ sb.append(": Expected: ");
+ printArrays(sb, uids, null);
+ sb.append(", got: ");
+ sb.append(ws);
+ fail(sb.toString());
+ }
+
+ private void failWorkSource(String op, WorkSource ws, int[] uids, String[] names) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(op);
+ sb.append(": Expected: ");
+ printArrays(sb, uids, names);
+ sb.append(", got: ");
+ sb.append(ws);
+ fail(sb.toString());
+ }
+
+ private void checkWorkSource(String op, WorkSource ws, int[] uids) {
+ if (ws == null || uids == null) {
+ if (ws != null) {
+ fail(op + ": WorkSource is not null " + ws +", but expected null");
+ }
+ if (uids != null) {
+ fail(op + "WorkSource is null, but expected non-null: " + uids);
+ }
+ return;
+ }
+ if (ws.size() != uids.length) {
+ failWorkSource(op, ws, uids);
+ }
+ for (int i=0; i<uids.length; i++) {
+ if (uids[i] != ws.get(i)) {
+ failWorkSource(op, ws, uids);
+ }
+ }
+ }
+
+ private void checkWorkSource(String op, WorkSource ws, int[] uids, String[] names) {
+ if (ws == null || uids == null) {
+ if (ws != null) {
+ fail(op + ": WorkSource is not null " + ws +", but expected null");
+ }
+ if (uids != null) {
+ fail(op + "WorkSource is null, but expected non-null: " + uids);
+ }
+ return;
+ }
+ if (ws.size() != uids.length) {
+ failWorkSource(op, ws, uids, names);
+ }
+ for (int i=0; i<uids.length; i++) {
+ if (uids[i] != ws.get(i) || !names[i].equals(ws.getName(i))) {
+ failWorkSource(op, ws, uids, names);
+ }
+ }
+ }
+
+ public void testConstructEmpty() {
+ checkWorkSource("Empty", new WorkSource(), new int[] { });
+ }
+
+ public void testConstructSingle() throws Exception {
+ checkWorkSource("Single 1", wsNew(1), new int[] { 1 });
+ }
+
+ public void testAddRawOrdered() throws Exception {
+ WorkSource ws = wsNew(1);
+ wsAdd(ws, 2);
+ checkWorkSource("First", ws, new int[] { 1 , 2 });
+ wsAdd(ws, 20);
+ checkWorkSource("Second", ws, new int[] { 1 , 2, 20 });
+ wsAdd(ws, 100);
+ checkWorkSource("Third", ws, new int[] { 1, 2, 20, 100 });
+ }
+
+ public void testAddRawRevOrdered() throws Exception {
+ WorkSource ws = wsNew(100);
+ wsAdd(ws, 20);
+ checkWorkSource("First", ws, new int[] { 20, 100 });
+ wsAdd(ws, 2);
+ checkWorkSource("Second", ws, new int[] { 2, 20, 100 });
+ wsAdd(ws, 1);
+ checkWorkSource("Third", ws, new int[] { 1, 2, 20, 100 });
+ }
+
+ public void testAddRawUnordered() throws Exception {
+ WorkSource ws = wsNew(10);
+ wsAdd(ws, 2);
+ checkWorkSource("First", ws, new int[] { 2, 10 });
+ wsAdd(ws, 5);
+ checkWorkSource("Second", ws, new int[] { 2, 5, 10 });
+ wsAdd(ws, 1);
+ checkWorkSource("Third", ws, new int[] { 1, 2, 5, 10 });
+ wsAdd(ws, 100);
+ checkWorkSource("Fourth", ws, new int[] { 1, 2, 5, 10, 100 });
+ }
+
+ public void testAddWsOrdered() throws Exception {
+ WorkSource ws = wsNew(1);
+ ws.add(wsNew(2));
+ checkWorkSource("First", ws, new int[] { 1 , 2 });
+ ws.add(wsNew(20));
+ checkWorkSource("Second", ws, new int[] { 1 , 2, 20 });
+ ws.add(wsNew(100));
+ checkWorkSource("Third", ws, new int[] { 1 , 2, 20, 100 });
+ }
+
+ public void testAddWsRevOrdered() throws Exception {
+ WorkSource ws = wsNew(100);
+ ws.add(wsNew(20));
+ checkWorkSource("First", ws, new int[] { 20, 100 });
+ ws.add(wsNew(2));
+ checkWorkSource("Second", ws, new int[] { 2, 20, 100 });
+ ws.add(wsNew(1));
+ checkWorkSource("Third", ws, new int[] { 1, 2, 20, 100 });
+ }
+
+ public void testAddWsUnordered() throws Exception {
+ WorkSource ws = wsNew(10);
+ ws.add(wsNew(2));
+ checkWorkSource("First", ws, new int[] { 2, 10 });
+ ws.add(wsNew(5));
+ checkWorkSource("Second", ws, new int[] { 2, 5, 10 });
+ ws.add(wsNew(1));
+ checkWorkSource("Third", ws, new int[] { 1, 2, 5, 10 });
+ ws.add(wsNew(100));
+ checkWorkSource("Fourth", ws, new int[] { 1, 2, 5, 10, 100 });
+ }
+
+ private void doTestCombineTwoUids(int[] lhs, int[] rhs, int[] expected, int[] newbs,
+ int[] gones) throws Exception {
+ WorkSource ws1 = wsNew(lhs);
+ WorkSource ws2 = wsNew(rhs);
+ ws1.add(ws2);
+ checkWorkSource("Add result", ws1, expected);
+ ws1 = wsNew(lhs);
+ WorkSource wsNewbs = wsAddReturningNewbs(ws1, ws2);
+ checkWorkSource("AddReturning result", ws1, expected);
+ checkWorkSource("Newbs", wsNewbs, newbs);
+ ws1 = wsNew(lhs);
+ WorkSource[] res = wsSetReturningDiffs(ws1, ws2);
+ checkWorkSource("SetReturning result", ws1, rhs);
+ checkWorkSource("Newbs", res[0], newbs);
+ checkWorkSource("Gones", res[1], gones);
+ }
+
+ private int[] makeRepeatingIntArray(String[] stringarray, int value) {
+ if (stringarray == null) {
+ return null;
+ }
+ int[] res = new int[stringarray.length];
+ for (int i=0; i<stringarray.length; i++) {
+ res[i] = value;
+ }
+ return res;
+ }
+
+ private void doTestCombineTwoNames(String[] lhsnames, String[] rhsnames,
+ String[] expectednames, String[] newbnames,
+ String[] gonenames) throws Exception {
+ int[] lhs = makeRepeatingIntArray(lhsnames, 0);
+ int[] rhs = makeRepeatingIntArray(rhsnames, 0);
+ int[] expected = makeRepeatingIntArray(expectednames, 0);
+ int[] newbs = makeRepeatingIntArray(newbnames, 0);
+ int[] gones = makeRepeatingIntArray(gonenames, 0);
+ doTestCombineTwoUidsNames(lhs, lhsnames, rhs, rhsnames, expected, expectednames,
+ newbs, newbnames, gones, gonenames);
+ }
+
+ private void doTestCombineTwoUidsNames(int[] lhs, String[] lhsnames, int[] rhs, String[] rhsnames,
+ int[] expected, String[] expectednames, int[] newbs, String[] newbnames,
+ int[] gones, String[] gonenames) throws Exception {
+ WorkSource ws1 = wsNew(lhs, lhsnames);
+ WorkSource ws2 = wsNew(rhs, rhsnames);
+ ws1.add(ws2);
+ checkWorkSource("Add result", ws1, expected, expectednames);
+ ws1 = wsNew(lhs, lhsnames);
+ WorkSource wsNewbs = wsAddReturningNewbs(ws1, ws2);
+ checkWorkSource("AddReturning result", ws1, expected, expectednames);
+ checkWorkSource("Newbs", wsNewbs, newbs, newbnames);
+ ws1 = wsNew(lhs, lhsnames);
+ WorkSource[] res = wsSetReturningDiffs(ws1, ws2);
+ checkWorkSource("SetReturning result", ws1, rhs, rhsnames);
+ checkWorkSource("Newbs", res[0], newbs, newbnames);
+ checkWorkSource("Gones", res[1], gones, gonenames);
+ }
+
+ private String[] makeRepeatingStringArray(int[] intarray, String value) {
+ if (intarray == null) {
+ return null;
+ }
+ String[] res = new String[intarray.length];
+ for (int i=0; i<intarray.length; i++) {
+ res[i] = value;
+ }
+ return res;
+ }
+
+ private String[] makeStringArray(int[] intarray) {
+ if (intarray == null) {
+ return null;
+ }
+ String[] res = new String[intarray.length];
+ for (int i=0; i<intarray.length; i++) {
+ res[i] = Character.toString((char)('A' + intarray[i]));
+ }
+ return res;
+ }
+
+ private void doTestCombineTwo(int[] lhs, int[] rhs, int[] expected, int[] newbs,
+ int[] gones) throws Exception {
+ doTestCombineTwoUids(lhs, rhs, expected, newbs, gones);
+ doTestCombineTwoUidsNames(lhs, makeRepeatingStringArray(lhs, "A"),
+ rhs, makeRepeatingStringArray(rhs, "A"),
+ expected, makeRepeatingStringArray(expected, "A"),
+ newbs, makeRepeatingStringArray(newbs, "A"),
+ gones, makeRepeatingStringArray(gones, "A"));
+ doTestCombineTwoNames(makeStringArray(lhs), makeStringArray(rhs),
+ makeStringArray(expected), makeStringArray(newbs), makeStringArray(gones));
+ }
+
+ public void testCombineMultiFront() throws Exception {
+ doTestCombineTwo(
+ new int[] { 10, 20, 30, 40 },
+ new int[] { 1, 2, 15, 16 },
+ new int[] { 1, 2, 10, 15, 16, 20, 30, 40 },
+ new int[] { 1, 2, 15, 16 },
+ new int[] { 10, 20, 30, 40 });
+ }
+
+ public void testCombineMultiMiddle() throws Exception {
+ doTestCombineTwo(
+ new int[] { 10, 20, 30, 40 },
+ new int[] { 12, 14, 22 },
+ new int[] { 10, 12, 14, 20, 22, 30, 40 },
+ new int[] { 12, 14, 22 },
+ new int[] { 10, 20, 30, 40 });
+ }
+
+ public void testCombineMultiEnd() throws Exception {
+ doTestCombineTwo(
+ new int[] { 10, 20, 30, 40 },
+ new int[] { 35, 45, 50 },
+ new int[] { 10, 20, 30, 35, 40, 45, 50 },
+ new int[] { 35, 45, 50 },
+ new int[] { 10, 20, 30, 40 });
+ }
+
+ public void testCombineMultiFull() throws Exception {
+ doTestCombineTwo(
+ new int[] { 10, 20, 30, 40 },
+ new int[] { 1, 2, 15, 35, 50 },
+ new int[] { 1, 2, 10, 15, 20, 30, 35, 40, 50 },
+ new int[] { 1, 2, 15, 35, 50 },
+ new int[] { 10, 20, 30, 40 });
+ }
+
+ public void testCombineMultiSame() throws Exception {
+ doTestCombineTwo(
+ new int[] { 10, 20, 30, 40 },
+ new int[] { 10, 20, 30 },
+ new int[] { 10, 20, 30, 40 },
+ null,
+ new int[] { 40 });
+ }
+
+ public void testCombineMultiSomeSame() throws Exception {
+ doTestCombineTwo(
+ new int[] { 10, 20, 30, 40 },
+ new int[] { 1, 30, 40 },
+ new int[] { 1, 10, 20, 30, 40 },
+ new int[] { 1 },
+ new int[] { 10, 20 });
+ }
+
+ public void testCombineMultiSomeSameUidsNames() throws Exception {
+ doTestCombineTwoUidsNames(
+ new int[] { 10, 10, 20, 30, 30, 30, 40 },
+ new String[] { "A", "B", "A", "A", "B", "C", "A" },
+ new int[] { 1, 30, 40, 50 },
+ new String[] { "A", "A", "B", "A" },
+ new int[] { 1, 10, 10, 20, 30, 30, 30, 40, 40, 50 },
+ new String[] { "A", "A", "B", "A", "A", "B", "C", "A", "B", "A" },
+ new int[] { 1, 40, 50 },
+ new String[] { "A", "B", "A" },
+ new int[] { 10, 10, 20, 30, 30, 40 },
+ new String[] { "A", "B", "A", "B", "C", "A" });
+ }
+
+ private void doTestRemoveUids(int[] lhs, int[] rhs, int[] result, boolean diff) throws Exception {
+ WorkSource ws1 = wsNew(lhs);
+ WorkSource ws2 = wsNew(rhs);
+ boolean diffres = ws1.remove(ws2);
+ if (diffres != diff) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Expected diff ");
+ sb.append(diff);
+ sb.append(" but got ");
+ sb.append(diffres);
+ sb.append(" when removing ");
+ sb.append(ws2);
+ sb.append(" from ");
+ sb.append(ws1);
+ fail(sb.toString());
+ }
+ checkWorkSource("Remove", ws1, result);
+ }
+
+ private void doTestRemoveNames(String[] lhsnames, String[] rhsnames,
+ String[] resultnames, boolean diff) throws Exception {
+ int[] lhs = makeRepeatingIntArray(lhsnames, 0);
+ int[] rhs = makeRepeatingIntArray(rhsnames, 0);
+ int[] result = makeRepeatingIntArray(resultnames, 0);
+ WorkSource ws1 = wsNew(lhs, lhsnames);
+ WorkSource ws2 = wsNew(rhs, rhsnames);
+ boolean diffres = ws1.remove(ws2);
+ if (diffres != diff) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Expected diff ");
+ sb.append(diff);
+ sb.append(" but got ");
+ sb.append(diffres);
+ sb.append(" when removing ");
+ sb.append(ws2);
+ sb.append(" from ");
+ sb.append(ws1);
+ fail(sb.toString());
+ }
+ checkWorkSource("Remove", ws1, result, resultnames);
+ }
+
+ private void doTestRemoveUidsNames(int[] lhs, String[] lhsnames, int[] rhs, String[] rhsnames,
+ int[] result, String[] resultnames, boolean diff) throws Exception {
+ WorkSource ws1 = wsNew(lhs, lhsnames);
+ WorkSource ws2 = wsNew(rhs, rhsnames);
+ boolean diffres = ws1.remove(ws2);
+ if (diffres != diff) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Expected diff ");
+ sb.append(diff);
+ sb.append(" but got ");
+ sb.append(diffres);
+ sb.append(" when removing ");
+ sb.append(ws2);
+ sb.append(" from ");
+ sb.append(ws1);
+ fail(sb.toString());
+ }
+ checkWorkSource("Remove", ws1, result, resultnames);
+ }
+
+ private void doTestRemove(int[] lhs, int[] rhs, int[] result, boolean diff) throws Exception {
+ doTestRemoveUids(lhs, rhs, result, diff);
+ doTestRemoveUidsNames(lhs, makeRepeatingStringArray(lhs, "A"),
+ rhs, makeRepeatingStringArray(rhs, "A"),
+ result, makeRepeatingStringArray(result, "A"),
+ diff);
+ doTestRemoveNames(makeStringArray(lhs), makeStringArray(rhs),
+ makeStringArray(result), diff);
+ }
+
+ public void testRemoveNone() throws Exception {
+ doTestRemove(
+ new int[] { 10, 20, 30, 40 },
+ new int[] { 1, 2, 35, 50 },
+ new int[] { 10, 20, 30, 40 },
+ false);
+ }
+
+ public void testRemoveMultiFront() throws Exception {
+ doTestRemove(
+ new int[] { 10, 20, 30, 40 },
+ new int[] { 1, 2, 10, 30 },
+ new int[] { 20, 40 },
+ true);
+ }
+
+ public void testRemoveMultiMiddle() throws Exception {
+ doTestRemove(
+ new int[] { 10, 20, 30, 40 },
+ new int[] { 20, 30 },
+ new int[] { 10, 40 },
+ true);
+ }
+
+ public void testRemoveMultiEnd() throws Exception {
+ doTestRemove(
+ new int[] { 10, 20, 30, 40 },
+ new int[] { 30, 40, 50 },
+ new int[] { 10, 20 },
+ true);
+ }
+
+ public void testRemoveMultiFull() throws Exception {
+ doTestRemove(
+ new int[] { 10, 20, 30, 40 },
+ new int[] { 1, 2, 20, 25, 35, 40 },
+ new int[] { 10, 30 },
+ true);
+ }
+
+ public void testRemoveMultiAll() throws Exception {
+ doTestRemove(
+ new int[] { 10, 20, 30, 40 },
+ new int[] { 10, 20, 30, 40 },
+ new int[] { },
+ true);
+ }
+
+ private void doTestStripNames(int[] uids, String[] names, int[] expected) throws Exception {
+ WorkSource ws1 = wsNew(uids, names);
+ WorkSource res = wsStripNames(ws1);
+ checkWorkSource("StripNames", res, expected);
+ }
+
+ public void testStripNamesSimple() throws Exception {
+ doTestStripNames(
+ new int[] { 10, 20, 30, 40 },
+ new String[] { "A", "A", "A", "A" },
+ new int[] { 10, 20, 30, 40 });
+ }
+
+ public void testStripNamesFull() throws Exception {
+ doTestStripNames(
+ new int[] { 10, 10, 10, 10 },
+ new String[] { "A", "B", "C", "D" },
+ new int[] { 10 });
+ }
+
+ public void testStripNamesComplex() throws Exception {
+ doTestStripNames(
+ new int[] { 10, 20, 20, 30, 40, 40 },
+ new String[] { "A", "A", "B", "A", "A", "B" },
+ new int[] { 10, 20, 30, 40 });
+ }
+}
diff --git a/tests/tests/permission/src/android/permission/cts/NoLocationPermissionTest.java b/tests/tests/permission/src/android/permission/cts/NoLocationPermissionTest.java
index 80dffba..da9057e 100644
--- a/tests/tests/permission/src/android/permission/cts/NoLocationPermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/NoLocationPermissionTest.java
@@ -110,33 +110,6 @@
}
/**
- * Verify that get cell location requires permissions.
- * <p>
- * Requires Permission: {@link
- * android.Manifest.permission#ACCESS_COARSE_LOCATION.}
- */
- @SmallTest
- public void testListenCellLocation2() {
- TelephonyManager telephonyManager = (TelephonyManager) getContext().getSystemService(
- Context.TELEPHONY_SERVICE);
- PhoneStateListener phoneStateListener = new PhoneStateListener();
-
- try {
- telephonyManager.getNeighboringCellInfo();
- fail("TelephonyManager.getNeighbouringCellInfo did not throw SecurityException as expected");
- } catch (SecurityException e) {
- // expected
- }
-
- try {
- telephonyManager.getAllCellInfo();
- fail("TelephonyManager.getAllCellInfo did not throw SecurityException as expected");
- } catch (SecurityException e) {
- // expected
- }
- }
-
- /**
* Helper method to verify that calling requestLocationUpdates with given
* provider throws SecurityException.
*
diff --git a/tests/tests/permission2/src/android/permission2/cts/NoReceiveGsmSmsPermissionTest.java b/tests/tests/permission2/src/android/permission2/cts/NoReceiveGsmSmsPermissionTest.java
deleted file mode 100644
index 9b20d1d..0000000
--- a/tests/tests/permission2/src/android/permission2/cts/NoReceiveGsmSmsPermissionTest.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.permission2.cts;
-
-import android.app.PendingIntent;
-import android.telephony.gsm.SmsManager;
-
-/**
- * Verify Sms and Mms cannot be received without required permissions.
- * Uses {@link android.telephony.gsm.SmsManager}.
- */
-@SuppressWarnings("deprecation")
-public class NoReceiveGsmSmsPermissionTest extends NoReceiveSmsPermissionTest {
-
- protected void sendSms(PendingIntent sentIntent, PendingIntent deliveryIntent,
- String currentNumber) {
- SmsManager.getDefault().sendTextMessage(currentNumber, null, "test message",
- sentIntent, deliveryIntent);
- }
-}
diff --git a/tests/tests/permission2/src/android/permission2/cts/ProtectedBroadcastsTest.java b/tests/tests/permission2/src/android/permission2/cts/ProtectedBroadcastsTest.java
index 877a89e..f7e5443 100755
--- a/tests/tests/permission2/src/android/permission2/cts/ProtectedBroadcastsTest.java
+++ b/tests/tests/permission2/src/android/permission2/cts/ProtectedBroadcastsTest.java
@@ -80,7 +80,8 @@
"android.net.wifi.p2p.CONNECTION_STATE_CHANGE",
"android.net.wifi.p2p.PERSISTENT_GROUPS_CHANGED",
"android.net.conn.TETHER_STATE_CHANGED",
- "android.net.conn.INET_CONDITION_ACTION"
+ "android.net.conn.INET_CONDITION_ACTION",
+ "android.net.conn.CAPTIVE_PORTAL_TEST_COMPLETED"
};
/**
diff --git a/tests/tests/provider/src/android/provider/cts/CalendarTest.java b/tests/tests/provider/src/android/provider/cts/CalendarTest.java
index e460453..3c10d0c 100644
--- a/tests/tests/provider/src/android/provider/cts/CalendarTest.java
+++ b/tests/tests/provider/src/android/provider/cts/CalendarTest.java
@@ -1769,15 +1769,34 @@
// Test that inserting a valid color index works
ev = EventHelper.getNewEventValues(account, seed++, cal_id, false);
- ev.put(Events.EVENT_COLOR_KEY, ColorHelper.DEFAULT_INDICES[ColorHelper.E_COLOR_0]);
+ final String defaultColorIndex = ColorHelper.DEFAULT_INDICES[ColorHelper.E_COLOR_0];
+ ev.put(Events.EVENT_COLOR_KEY, defaultColorIndex);
Uri uri = mContentResolver.insert(Events.CONTENT_URI, ev);
long eventId2 = ContentUris.parseId(uri);
assertTrue(eventId2 >= 0);
// And updates the event's color to the one in the table
- ev.put(Events.EVENT_COLOR, ColorHelper.DEFAULT_COLORS[ColorHelper.E_COLOR_0]);
+ final int expectedColor = ColorHelper.DEFAULT_COLORS[ColorHelper.E_COLOR_0];
+ ev.put(Events.EVENT_COLOR, expectedColor);
verifyEvent(ev, eventId2);
+ // Test that event iterator has COLOR columns
+ final EntityIterator iterator = EventsEntity.newEntityIterator(mContentResolver.query(
+ ContentUris.withAppendedId(EventsEntity.CONTENT_URI, eventId2),
+ null, null, null, null), mContentResolver);
+ assertTrue("Empty Iterator", iterator.hasNext());
+ final Entity entity = iterator.next();
+ final ContentValues values = entity.getEntityValues();
+ assertTrue("Missing EVENT_COLOR", values.containsKey(EventsEntity.EVENT_COLOR));
+ assertEquals("Wrong EVENT_COLOR",
+ expectedColor,
+ (int) values.getAsInteger(EventsEntity.EVENT_COLOR));
+ assertTrue("Missing EVENT_COLOR_KEY", values.containsKey(EventsEntity.EVENT_COLOR_KEY));
+ assertEquals("Wrong EVENT_COLOR_KEY",
+ defaultColorIndex,
+ values.getAsString(EventsEntity.EVENT_COLOR_KEY));
+ iterator.close();
+
// Test that updating a valid color index also updates the color in an
// event
ev.clear();
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_AlbumsTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_AlbumsTest.java
index 100d9e1..b265cbf 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_AlbumsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_AlbumsTest.java
@@ -45,12 +45,15 @@
}
public void testGetContentUri() {
- assertNotNull(mContentResolver.query(
+ Cursor c = null;
+ assertNotNull(c = mContentResolver.query(
Albums.getContentUri(MediaStoreAudioTestHelper.INTERNAL_VOLUME_NAME), null, null,
null, null));
- assertNotNull(mContentResolver.query(
+ c.close();
+ assertNotNull(c = mContentResolver.query(
Albums.getContentUri(MediaStoreAudioTestHelper.EXTERNAL_VOLUME_NAME), null, null,
null, null));
+ c.close();
// can not accept any other volume names
String volume = "fakeVolume";
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_ArtistsTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_ArtistsTest.java
index 2e3cc1b..9fe31c5 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_ArtistsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_ArtistsTest.java
@@ -37,12 +37,15 @@
}
public void testGetContentUri() {
- assertNotNull(mContentResolver.query(
+ Cursor c = null;
+ assertNotNull(c = mContentResolver.query(
Artists.getContentUri(MediaStoreAudioTestHelper.INTERNAL_VOLUME_NAME), null, null,
null, null));
- assertNotNull(mContentResolver.query(
+ c.close();
+ assertNotNull(c = mContentResolver.query(
Artists.getContentUri(MediaStoreAudioTestHelper.INTERNAL_VOLUME_NAME), null, null,
null, null));
+ c.close();
// can not accept any other volume names
String volume = "fakeVolume";
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_Artists_AlbumsTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_Artists_AlbumsTest.java
index fdba619..72d9067 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_Artists_AlbumsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_Artists_AlbumsTest.java
@@ -39,12 +39,16 @@
}
public void testGetContentUri() {
+ Cursor c = null;
Uri contentUri = MediaStore.Audio.Artists.Albums.getContentUri(
MediaStoreAudioTestHelper.INTERNAL_VOLUME_NAME, 1);
- assertNotNull(mContentResolver.query(contentUri, null, null, null, null));
+ assertNotNull(c = mContentResolver.query(contentUri, null, null, null, null));
+ c.close();
+
contentUri = MediaStore.Audio.Artists.Albums.getContentUri(
MediaStoreAudioTestHelper.EXTERNAL_VOLUME_NAME, 1);
- assertNotNull(mContentResolver.query(contentUri, null, null, null, null));
+ assertNotNull(c = mContentResolver.query(contentUri, null, null, null, null));
+ c.close();
// can not accept any other volume names
String volume = "fakeVolume";
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_GenresTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_GenresTest.java
index 1138233..8d41b38 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_GenresTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_GenresTest.java
@@ -39,14 +39,16 @@
}
public void testGetContentUri() {
- assertNotNull(mContentResolver.query(
+ Cursor c = null;
+ assertNotNull(c = mContentResolver.query(
Genres.getContentUri(MediaStoreAudioTestHelper.EXTERNAL_VOLUME_NAME), null, null,
null, null));
-
+ c.close();
try {
- assertNotNull(mContentResolver.query(
+ assertNotNull(c = mContentResolver.query(
Genres.getContentUri(MediaStoreAudioTestHelper.INTERNAL_VOLUME_NAME), null,
null, null, null));
+ c.close();
fail("Should throw SQLException as the internal datatbase has no genre");
} catch (SQLException e) {
// expected
@@ -98,6 +100,7 @@
// Insert an audio file into the content provider.
ContentValues values = Audio1.getInstance().getContentValues(true);
Uri audioUri = mContentResolver.insert(Media.EXTERNAL_CONTENT_URI, values);
+ assertNotNull(audioUri);
long audioId = ContentUris.parseId(audioUri);
assertTrue(audioId != -1);
@@ -105,6 +108,7 @@
values.clear();
values.put(Genres.NAME, "Soda Pop");
Uri genreUri = mContentResolver.insert(Genres.EXTERNAL_CONTENT_URI, values);
+ assertNotNull(genreUri);
long genreId = ContentUris.parseId(genreUri);
assertTrue(genreId != -1);
@@ -131,6 +135,8 @@
if (cursor != null) {
cursor.close();
}
+ assertEquals(1, mContentResolver.delete(audioUri, null, null));
+ assertEquals(1, mContentResolver.delete(genreUri, null, null));
}
}
}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_Genres_MembersTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_Genres_MembersTest.java
index 2e389b6..7310fa1 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_Genres_MembersTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_Genres_MembersTest.java
@@ -65,14 +65,17 @@
}
public void testGetContentUri() {
- assertNotNull(mContentResolver.query(
+ Cursor c = null;
+ assertNotNull(c = mContentResolver.query(
Members.getContentUri(MediaStoreAudioTestHelper.EXTERNAL_VOLUME_NAME, 1), null,
null, null, null));
+ c.close();
try {
- assertNotNull(mContentResolver.query(
+ assertNotNull(c = mContentResolver.query(
Members.getContentUri(MediaStoreAudioTestHelper.INTERNAL_VOLUME_NAME, 1), null,
null, null, null));
+ c.close();
fail("Should throw SQLException as the internal datatbase has no genre");
} catch (SQLException e) {
// expected
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_MediaTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_MediaTest.java
index 8a4e3ee..82c2342 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_MediaTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_MediaTest.java
@@ -38,12 +38,15 @@
}
public void testGetContentUri() {
- assertNotNull(mContentResolver.query(
+ Cursor c = null;
+ assertNotNull(c = mContentResolver.query(
Media.getContentUri(MediaStoreAudioTestHelper.INTERNAL_VOLUME_NAME), null, null,
null, null));
- assertNotNull(mContentResolver.query(
+ c.close();
+ assertNotNull(c = mContentResolver.query(
Media.getContentUri(MediaStoreAudioTestHelper.EXTERNAL_VOLUME_NAME), null, null,
null, null));
+ c.close();
// can not accept any other volume names
String volume = "faveVolume";
@@ -51,14 +54,17 @@
}
public void testGetContentUriForPath() {
+ Cursor c = null;
String externalPath = Environment.getExternalStorageDirectory().getPath();
- assertNotNull(mContentResolver.query(Media.getContentUriForPath(externalPath), null, null,
+ assertNotNull(c = mContentResolver.query(Media.getContentUriForPath(externalPath), null, null,
null, null));
+ c.close();
String internalPath =
getInstrumentation().getTargetContext().getFilesDir().getAbsolutePath();
- assertNotNull(mContentResolver.query(Media.getContentUriForPath(internalPath), null, null,
+ assertNotNull(c = mContentResolver.query(Media.getContentUriForPath(internalPath), null, null,
null, null));
+ c.close();
}
public void testStoreAudioMediaInternal() {
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_PlaylistsTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_PlaylistsTest.java
index 0d36212..03a81b8 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_PlaylistsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_PlaylistsTest.java
@@ -39,15 +39,18 @@
}
public void testGetContentUri() {
- assertNotNull(mContentResolver.query(
+ Cursor c = null;
+ assertNotNull(c = mContentResolver.query(
Playlists.getContentUri(MediaStoreAudioTestHelper.EXTERNAL_VOLUME_NAME), null, null,
null, null));
+ c.close();
// can not accept any other volume names
try {
- assertNotNull(mContentResolver.query(
+ assertNotNull(c = mContentResolver.query(
Playlists.getContentUri(MediaStoreAudioTestHelper.INTERNAL_VOLUME_NAME), null,
null, null, null));
+ c.close();
fail("Should throw SQLException as the internal datatbase has no playlist");
} catch (SQLException e) {
// expected
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_Playlists_MembersTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_Playlists_MembersTest.java
index aa524aa..bf97848 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_Playlists_MembersTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_Playlists_MembersTest.java
@@ -27,6 +27,9 @@
import android.provider.MediaStore.Audio.Playlists.Members;
import android.provider.cts.MediaStoreAudioTestHelper.Audio1;
import android.provider.cts.MediaStoreAudioTestHelper.Audio2;
+import android.provider.cts.MediaStoreAudioTestHelper.Audio3;
+import android.provider.cts.MediaStoreAudioTestHelper.Audio4;
+import android.provider.cts.MediaStoreAudioTestHelper.Audio5;
import android.provider.cts.MediaStoreAudioTestHelper.MockAudioMediaInfo;
import android.test.InstrumentationTestCase;
@@ -114,9 +117,9 @@
mContentResolver = getInstrumentation().getContext().getContentResolver();
mIdOfAudio1 = insertAudioItem(Audio1.getInstance());
mIdOfAudio2 = insertAudioItem(Audio2.getInstance());
- mIdOfAudio3 = insertAudioItem(Audio1.getInstance());
- mIdOfAudio4 = insertAudioItem(Audio1.getInstance());
- mIdOfAudio5 = insertAudioItem(Audio1.getInstance());
+ mIdOfAudio3 = insertAudioItem(Audio3.getInstance());
+ mIdOfAudio4 = insertAudioItem(Audio4.getInstance());
+ mIdOfAudio5 = insertAudioItem(Audio5.getInstance());
}
@Override
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_FilesTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_FilesTest.java
index 1e8a4ac..79b2e57 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_FilesTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_FilesTest.java
@@ -84,6 +84,10 @@
assertEquals(1, mResolver.update(fileUri, values, null, null));
assertStringColumn(fileUri, MediaColumns.DATA, updatedPath);
+ // check that inserting a duplicate entry fails
+ Uri foo = mResolver.insert(allFilesUri, values);
+ assertNull(foo);
+
// Delete the file and observe that the file count decreased.
assertEquals(1, mResolver.delete(fileUri, null, null));
assertEquals(fileCount, getFileCount(allFilesUri));
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Images_MediaTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Images_MediaTest.java
index fa269e4..740e31b 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Images_MediaTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_Images_MediaTest.java
@@ -178,10 +178,13 @@
}
public void testGetContentUri() {
- assertNotNull(mContentResolver.query(Media.getContentUri("internal"), null, null, null,
+ Cursor c = null;
+ assertNotNull(c = mContentResolver.query(Media.getContentUri("internal"), null, null, null,
null));
- assertNotNull(mContentResolver.query(Media.getContentUri("external"), null, null, null,
+ c.close();
+ assertNotNull(c = mContentResolver.query(Media.getContentUri("external"), null, null, null,
null));
+ c.close();
// can not accept any other volume names
String volume = "fakeVolume";
@@ -294,6 +297,7 @@
} finally {
// delete
assertEquals(1, mContentResolver.delete(uri, null, null));
+ new File(externalPath).delete();
}
}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Images_ThumbnailsTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Images_ThumbnailsTest.java
index 8ecea7b..e8a13a9 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Images_ThumbnailsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_Images_ThumbnailsTest.java
@@ -211,10 +211,13 @@
}
public void testGetContentUri() {
- assertNotNull(mContentResolver.query(Thumbnails.getContentUri("internal"), null, null,
+ Cursor c = null;
+ assertNotNull(c = mContentResolver.query(Thumbnails.getContentUri("internal"), null, null,
null, null));
- assertNotNull(mContentResolver.query(Thumbnails.getContentUri("external"), null, null,
+ c.close();
+ assertNotNull(c = mContentResolver.query(Thumbnails.getContentUri("external"), null, null,
null, null));
+ c.close();
// can not accept any other volume names
String volume = "fakeVolume";
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Video_MediaTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Video_MediaTest.java
index f4d01e3..c3eb0b8 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Video_MediaTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_Video_MediaTest.java
@@ -45,10 +45,13 @@
}
public void testGetContentUri() {
- assertNotNull(mContentResolver.query(Media.getContentUri("internal"), null, null, null,
+ Cursor c = null;
+ assertNotNull(c = mContentResolver.query(Media.getContentUri("internal"), null, null, null,
null));
- assertNotNull(mContentResolver.query(Media.getContentUri("external"), null, null, null,
+ c.close();
+ assertNotNull(c = mContentResolver.query(Media.getContentUri("external"), null, null, null,
null));
+ c.close();
// can not accept any other volume names
String volume = "fakeVolume";
@@ -180,6 +183,7 @@
} finally {
// delete
assertEquals(1, mContentResolver.delete(uri, null, null));
+ new File(externalVideoPath).delete();
}
// check that the video file is removed when deleting the database entry
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Video_ThumbnailsTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Video_ThumbnailsTest.java
index 2a4b46c..8f6f729 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Video_ThumbnailsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_Video_ThumbnailsTest.java
@@ -99,6 +99,8 @@
assertFalse("thumbnail file should no longer exist", new File(path).exists());
}
c.close();
+
+ assertEquals(1, mResolver.delete(videoUri, null, null));
}
private Uri insertVideo() throws IOException {
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/AllocationTest.java b/tests/tests/renderscript/src/android/renderscript/cts/AllocationTest.java
index feac159..4a1533c 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/AllocationTest.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/AllocationTest.java
@@ -28,6 +28,8 @@
import android.renderscript.Type.Builder;
import android.renderscript.Type.CubemapFace;
+import android.util.Log;
+
public class AllocationTest extends RSBaseGraphics {
// Test power of two and non power of two, equal and non-equal sizes
@@ -188,10 +190,10 @@
void helperCreateFromBitmap(Bitmap B,
Allocation.MipmapControl mc) {
- for (int i = 0; i < 1; i++) {
- for (int j = 0; j < 1; j++) {
- for (int k = 0; k < 1; k++) {
- for (int l = 0; l < 1; l++) {
+ for (int i = 0; i <= 1; i++) {
+ for (int j = 0; j <= 1; j++) {
+ for (int k = 0; k <= 1; k++) {
+ for (int l = 0; l <= 1; l++) {
int u = 0;
u |= (i * Allocation.USAGE_SCRIPT);
u |= (j * Allocation.USAGE_GRAPHICS_TEXTURE);
@@ -684,6 +686,101 @@
}
}
}
+
+ public void testCopyFromAllocation() {
+ int nElemsX = 256;
+ int nElemsY = 32;
+ Type.Builder b = new Type.Builder(mRS, Element.I8(mRS));
+ Type.Builder b2 = new Type.Builder(mRS, Element.I32(mRS));
+ Allocation srcA = Allocation.createTyped(mRS, b.setX(nElemsX).setY(nElemsY).create());
+ Allocation dstA = Allocation.createTyped(mRS, b.setX(nElemsX).setY(nElemsY).create());
+
+ // wrong dimensionality
+ Allocation srcB_bad = Allocation.createTyped(mRS, b.setX(nElemsX).setY(1).create());
+ Allocation srcC_bad = Allocation.createTyped(mRS, b.setX(nElemsY).setY(nElemsX).create());
+ Allocation srcD_bad = Allocation.createTyped(mRS, b.setX(nElemsX*2).setY(nElemsY*2).create());
+
+ // wrong element type
+ Allocation srcE_bad = Allocation.createTyped(mRS, b2.setX(nElemsX).setY(nElemsY).create());
+
+ try {
+ dstA.copyFrom(srcB_bad);
+ fail("should throw RSIllegalArgumentException");
+ } catch (RSIllegalArgumentException e) {
+ }
+
+ try {
+ dstA.copyFrom(srcC_bad);
+ fail("should throw RSIllegalArgumentException");
+ } catch (RSIllegalArgumentException e) {
+ }
+
+ try {
+ dstA.copyFrom(srcD_bad);
+ fail("should throw RSIllegalArgumentException");
+ } catch (RSIllegalArgumentException e) {
+ }
+
+ try {
+ dstA.copyFrom(srcE_bad);
+ fail("should throw RSIllegalArgumentException");
+ } catch (RSIllegalArgumentException e) {
+ }
+
+ dstA.copyFrom(srcA);
+
+ }
+
+ public void testSetElementAt() {
+ Type.Builder b = new Type.Builder(mRS, Element.I32(mRS));
+ Allocation largeArray = Allocation.createTyped(mRS, b.setX(48).create());
+ Allocation singleElement = Allocation.createTyped(mRS, b.setX(1).create());
+
+ ScriptC_setelementat script = new ScriptC_setelementat(mRS, mRes, R.raw.setelementat);
+
+ script.set_memset_toValue(1);
+ script.forEach_memset(singleElement);
+
+ script.set_dimX(48);
+ script.set_array(largeArray);
+
+ script.forEach_setLargeArray(singleElement);
+
+ int[] result = new int[1];
+
+ script.set_compare_value(10);
+ script.forEach_compare(largeArray);
+ script.forEach_getCompareResult(singleElement);
+ singleElement.copyTo(result);
+ assertTrue(result[0] == 2);
+ }
+
+ public void testSetElementAt2D() {
+ Type.Builder b = new Type.Builder(mRS, Element.I32(mRS));
+
+ Allocation singleElement = Allocation.createTyped(mRS, b.setX(1).create());
+ Allocation largeArray = Allocation.createTyped(mRS, b.setX(48).setY(16).create());
+
+ ScriptC_setelementat script = new ScriptC_setelementat(mRS, mRes, R.raw.setelementat);
+
+ script.set_memset_toValue(1);
+ script.forEach_memset(singleElement);
+
+ script.set_dimX(48);
+ script.set_dimY(16);
+ script.set_array(largeArray);
+
+ script.forEach_setLargeArray2D(singleElement);
+
+ int[] result = new int[1];
+
+ script.set_compare_value(10);
+ script.forEach_compare(largeArray);
+ script.forEach_getCompareResult(singleElement);
+ singleElement.copyTo(result);
+ assertTrue(result[0] == 2);
+ }
+
}
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/ComputeTest.java b/tests/tests/renderscript/src/android/renderscript/cts/ComputeTest.java
index d095dd0..40611fd 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/ComputeTest.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/ComputeTest.java
@@ -782,4 +782,44 @@
}
checkForErrors();
}
+
+ /**
+ * Test script instancing.
+ */
+ public void testInstance() {
+ ScriptC_instance instance_1 = new ScriptC_instance(mRS);
+ ScriptC_instance instance_2 = new ScriptC_instance(mRS);
+
+ Type t = new Type.Builder(mRS, Element.I32(mRS)).setX(1).create();
+ Allocation ai1 = Allocation.createTyped(mRS, t);
+ Allocation ai2 = Allocation.createTyped(mRS, t);
+
+ instance_1.set_i(1);
+ instance_2.set_i(2);
+ instance_1.set_ai(ai1);
+ instance_2.set_ai(ai2);
+
+ // We now check to ensure that the global is not being shared across
+ // our separate script instances. Our invoke here merely sets the
+ // instanced allocation with the instanced global variable's value.
+ // If globals are being shared (i.e. not instancing scripts), then
+ // both instanced allocations will have the same resulting value
+ // (depending on the order in which the invokes complete).
+ instance_1.invoke_instance_test();
+ instance_2.invoke_instance_test();
+
+ int i1[] = new int[1];
+ int i2[] = new int[1];
+
+ ai1.copyTo(i1);
+ ai2.copyTo(i2);
+
+ // 3-step check ensures that a fortunate race condition wouldn't let us
+ // pass accidentally.
+ assertEquals(2, i2[0]);
+ assertEquals(1, i1[0]);
+ assertEquals(2, i2[0]);
+
+ checkForErrors();
+ }
}
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/ElementTest.java b/tests/tests/renderscript/src/android/renderscript/cts/ElementTest.java
index 2ad45b1..ead4528 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/ElementTest.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/ElementTest.java
@@ -487,11 +487,12 @@
assertEquals(DataKind.PIXEL_RGB, DataKind.valueOf("PIXEL_RGB"));
assertEquals(DataKind.PIXEL_RGBA, DataKind.valueOf("PIXEL_RGBA"));
assertEquals(DataKind.PIXEL_DEPTH, DataKind.valueOf("PIXEL_DEPTH"));
+ assertEquals(DataKind.PIXEL_YUV, DataKind.valueOf("PIXEL_YUV"));
// Make sure no new enums are added
- assertEquals(7, DataKind.values().length);
+ assertEquals(8, DataKind.values().length);
for (DataKind dk : DataKind.values()) {
- if (dk != DataKind.USER) {
+ if (dk != DataKind.USER && dk != DataKind.PIXEL_YUV) {
Element.createPixel(mRS, DataType.UNSIGNED_8, dk);
}
}
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/ForEachTest.java b/tests/tests/renderscript/src/android/renderscript/cts/ForEachTest.java
index f633168..433b7e6 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/ForEachTest.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/ForEachTest.java
@@ -471,6 +471,7 @@
typeBuilder.setX(X).setY(Y);
Allocation A = Allocation.createTyped(mRS, typeBuilder.create());
s.bind_a(A);
+ s.set_aRaw(A);
s.forEach_root(A);
s.invoke_verify_root();
s.forEach_foo(A, A);
@@ -491,6 +492,7 @@
typeBuilder.setX(X).setY(Y);
Allocation A = Allocation.createTyped(mRS, typeBuilder.create());
s.bind_a(A);
+ s.set_aRaw(A);
s.forEach_foo(A, A);
s.invoke_verify_foo();
s.invoke_noroot_test();
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/ImageProcessingTest.java b/tests/tests/renderscript/src/android/renderscript/cts/ImageProcessingTest.java
new file mode 100644
index 0000000..fae20f4
--- /dev/null
+++ b/tests/tests/renderscript/src/android/renderscript/cts/ImageProcessingTest.java
@@ -0,0 +1,338 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.renderscript.cts;
+
+import android.renderscript.Allocation;
+
+import android.renderscript.Byte2;
+import android.renderscript.Byte3;
+import android.renderscript.Byte4;
+
+import android.renderscript.Double2;
+import android.renderscript.Double3;
+import android.renderscript.Double4;
+
+import android.renderscript.Element;
+
+import android.renderscript.Float2;
+import android.renderscript.Float3;
+import android.renderscript.Float4;
+
+import android.renderscript.Int2;
+import android.renderscript.Int3;
+import android.renderscript.Int4;
+
+import android.renderscript.Long2;
+import android.renderscript.Long3;
+import android.renderscript.Long4;
+
+import android.renderscript.RSRuntimeException;
+
+import android.renderscript.Short2;
+import android.renderscript.Short3;
+import android.renderscript.Short4;
+
+import android.renderscript.Matrix4f;
+
+import android.renderscript.Type;
+
+import android.renderscript.ScriptGroup;
+
+import android.renderscript.ScriptIntrinsicBlend;
+import android.renderscript.ScriptIntrinsicBlur;
+import android.renderscript.ScriptIntrinsicColorMatrix;
+import android.renderscript.ScriptIntrinsicConvolve3x3;
+import android.renderscript.ScriptIntrinsicConvolve5x5;
+import android.renderscript.ScriptIntrinsicLUT;
+
+import com.android.cts.stub.R;
+
+public class ImageProcessingTest extends RSBaseCompute {
+ private Allocation a1, a2;
+
+ private final int MAX_RADIUS = 25;
+ private final int dimX = 256;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ Type t = new Type.Builder(mRS, Element.U8_4(mRS)).setX(dimX).setY(dimX).create();
+ a1 = Allocation.createTyped(mRS, t);
+ a2 = Allocation.createTyped(mRS, t);
+ }
+
+ public void testBlur() {
+ ScriptIntrinsicBlur mBlur;
+ mBlur = ScriptIntrinsicBlur.create(mRS, Element.U8_4(mRS));
+
+ Allocation a1_copy, a2_copy;
+ a1_copy = Allocation.createTyped(mRS, a1.getType());
+ a2_copy = Allocation.createTyped(mRS, a2.getType());
+
+ for (int i = 1; i < MAX_RADIUS; i++) {
+
+ a1_copy.copy2DRangeFrom(0, 0, a1.getType().getX(), a1.getType().getY(), a1, 0, 0);
+
+ mBlur.setRadius(i);
+ mBlur.setInput(a1_copy);
+
+ mBlur.forEach(a2_copy);
+
+ // validate
+
+ }
+
+ }
+
+ public void testBlend() {
+ ScriptIntrinsicBlend mBlend;
+ mBlend = ScriptIntrinsicBlend.create(mRS, Element.U8_4(mRS));
+
+ Allocation a1_copy, a2_copy;
+ a1_copy = Allocation.createTyped(mRS, a1.getType());
+ a2_copy = Allocation.createTyped(mRS, a2.getType());
+
+ for (int i = 0; i < 14; i++) {
+
+ a1_copy.copy2DRangeFrom(0, 0, a1.getType().getX(), a1.getType().getY(), a1, 0, 0);
+ a2_copy.copy2DRangeFrom(0, 0, a2.getType().getX(), a2.getType().getY(), a2, 0, 0);
+
+ switch (i) {
+ case 0:
+ mBlend.forEachSrc(a1_copy, a2_copy);
+ break;
+ case 1:
+ mBlend.forEachDst(a1_copy, a2_copy);
+ break;
+ case 2:
+ mBlend.forEachSrcOver(a1_copy, a2_copy);
+ break;
+ case 3:
+ mBlend.forEachDstOver(a1_copy, a2_copy);
+ break;
+ case 4:
+ mBlend.forEachSrcIn(a1_copy, a2_copy);
+ break;
+ case 5:
+ mBlend.forEachDstIn(a1_copy, a2_copy);
+ break;
+ case 6:
+ mBlend.forEachSrcOut(a1_copy, a2_copy);
+ break;
+ case 7:
+ mBlend.forEachDstOut(a1_copy, a2_copy);
+ break;
+ case 8:
+ mBlend.forEachSrcAtop(a1_copy, a2_copy);
+ break;
+ case 9:
+ mBlend.forEachDstAtop(a1_copy, a2_copy);
+ break;
+ case 10:
+ mBlend.forEachXor(a1_copy, a2_copy);
+ break;
+ case 11:
+ mBlend.forEachAdd(a1_copy, a2_copy);
+ break;
+ case 12:
+ mBlend.forEachSubtract(a1_copy, a2_copy);
+ break;
+ case 13:
+ mBlend.forEachMultiply(a1_copy, a2_copy);
+ break;
+ }
+
+ // validate
+
+ }
+
+ }
+
+ public void testColorMatrix() {
+ ScriptIntrinsicColorMatrix mColorMatrix;
+ mColorMatrix = ScriptIntrinsicColorMatrix.create(mRS, Element.U8_4(mRS));
+
+ Allocation a1_copy, a2_copy;
+ a1_copy = Allocation.createTyped(mRS, a1.getType());
+ a2_copy = Allocation.createTyped(mRS, a2.getType());
+
+ Matrix4f m = new Matrix4f();
+ m.set(1, 0, 0.2f);
+ m.set(1, 1, 0.9f);
+ m.set(1, 2, 0.2f);
+
+ //test greyscale
+ mColorMatrix.setGreyscale();
+
+ a1_copy.copy2DRangeFrom(0, 0, a1.getType().getX(), a1.getType().getY(), a1, 0, 0);
+ a2_copy.copy2DRangeFrom(0, 0, a2.getType().getX(), a2.getType().getY(), a2, 0, 0);
+
+ mColorMatrix.forEach(a1_copy, a2_copy);
+
+ //validate greyscale
+
+
+ //test color matrix
+ mColorMatrix.setColorMatrix(m);
+
+ a1_copy.copy2DRangeFrom(0, 0, a1.getType().getX(), a1.getType().getY(), a1, 0, 0);
+ a2_copy.copy2DRangeFrom(0, 0, a2.getType().getX(), a2.getType().getY(), a2, 0, 0);
+
+ mColorMatrix.forEach(a1_copy, a2_copy);
+
+ //validate color matrix
+
+
+ }
+
+
+ public void testConvolve3x3() {
+ ScriptIntrinsicConvolve3x3 mConvolve3x3;
+ mConvolve3x3 = ScriptIntrinsicConvolve3x3.create(mRS, Element.U8_4(mRS));
+
+ Allocation a1_copy, a2_copy;
+ a1_copy = Allocation.createTyped(mRS, a1.getType());
+ a2_copy = Allocation.createTyped(mRS, a2.getType());
+
+ a1_copy.copy2DRangeFrom(0, 0, a1.getType().getX(), a1.getType().getY(), a1, 0, 0);
+
+ float f[] = new float[9];
+ f[0] = 0.f; f[1] = -1.f; f[2] = 0.f;
+ f[3] = -1.f; f[4] = 5.f; f[5] = -1.f;
+ f[6] = 0.f; f[7] = -1.f; f[8] = 0.f;
+
+ mConvolve3x3.setCoefficients(f);
+ mConvolve3x3.setInput(a1_copy);
+ mConvolve3x3.forEach(a2_copy);
+
+ // validate
+
+ }
+
+ public void testConvolve5x5() {
+ ScriptIntrinsicConvolve5x5 mConvolve5x5;
+ mConvolve5x5 = ScriptIntrinsicConvolve5x5.create(mRS, Element.U8_4(mRS));
+
+ Allocation a1_copy, a2_copy;
+ a1_copy = Allocation.createTyped(mRS, a1.getType());
+ a2_copy = Allocation.createTyped(mRS, a2.getType());
+
+ a1_copy.copy2DRangeFrom(0, 0, a1.getType().getX(), a1.getType().getY(), a1, 0, 0);
+
+ float f[] = new float[25];
+ f[0] = -1.f; f[1] = -3.f; f[2] = -4.f; f[3] = -3.f; f[4] = -1.f;
+ f[5] = -3.f; f[6] = 0.f; f[7] = 6.f; f[8] = 0.f; f[9] = -3.f;
+ f[10]= -4.f; f[11]= 6.f; f[12]= 20.f; f[13]= 6.f; f[14]= -4.f;
+ f[15]= -3.f; f[16]= 0.f; f[17]= 6.f; f[18]= 0.f; f[19]= -3.f;
+ f[20]= -1.f; f[21]= -3.f; f[22]= -4.f; f[23]= -3.f; f[24]= -1.f;
+
+ mConvolve5x5.setCoefficients(f);
+ mConvolve5x5.setInput(a1_copy);
+ mConvolve5x5.forEach(a2_copy);
+
+ // validate
+
+ }
+
+ public void testLUT() {
+ ScriptIntrinsicLUT mLUT;
+ mLUT = ScriptIntrinsicLUT.create(mRS, Element.U8_4(mRS));
+
+ Allocation a1_copy, a2_copy;
+ a1_copy = Allocation.createTyped(mRS, a1.getType());
+ a2_copy = Allocation.createTyped(mRS, a2.getType());
+
+ a1_copy.copy2DRangeFrom(0, 0, a1.getType().getX(), a1.getType().getY(), a1, 0, 0);
+
+ for (int ct=0; ct < 256; ct++) {
+ float f = ((float)ct) / 255.f;
+
+ float r = f;
+ if (r < 0.5f) {
+ r = 4.0f * r * r * r;
+ } else {
+ r = 1.0f - r;
+ r = 1.0f - (4.0f * r * r * r);
+ }
+ mLUT.setRed(ct, (int)(r * 255.f + 0.5f));
+
+ float g = f;
+ if (g < 0.5f) {
+ g = 2.0f * g * g;
+ } else {
+ g = 1.0f - g;
+ g = 1.0f - (2.0f * g * g);
+ }
+ mLUT.setGreen(ct, (int)(g * 255.f + 0.5f));
+
+ float b = f * 0.5f + 0.25f;
+ mLUT.setBlue(ct, (int)(b * 255.f + 0.5f));
+ }
+
+ mLUT.forEach(a1_copy, a2_copy);
+
+ // validate
+
+ }
+
+ public void testScriptGroup() {
+ ScriptGroup group;
+
+ ScriptIntrinsicConvolve3x3 mConvolve3x3;
+ ScriptIntrinsicColorMatrix mColorMatrix;
+
+ mConvolve3x3 = ScriptIntrinsicConvolve3x3.create(mRS, Element.U8_4(mRS));
+ mColorMatrix = ScriptIntrinsicColorMatrix.create(mRS, Element.U8_4(mRS));
+
+ Allocation a1_copy, a2_copy;
+ a1_copy = Allocation.createTyped(mRS, a1.getType());
+ a2_copy = Allocation.createTyped(mRS, a2.getType());
+
+ a1_copy.copy2DRangeFrom(0, 0, a1.getType().getX(), a1.getType().getY(), a1, 0, 0);
+
+ float f[] = new float[9];
+ f[0] = 0.f; f[1] = -1.f; f[2] = 0.f;
+ f[3] = -1.f; f[4] = 5.f; f[5] = -1.f;
+ f[6] = 0.f; f[7] = -1.f; f[8] = 0.f;
+
+ mConvolve3x3.setCoefficients(f);
+
+ Matrix4f m = new Matrix4f();
+ m.set(1, 0, 0.2f);
+ m.set(1, 1, 0.9f);
+ m.set(1, 2, 0.2f);
+ mColorMatrix.setColorMatrix(m);
+
+ Type connect = new Type.Builder(mRS, Element.U8_4(mRS)).setX(dimX).setY(dimX).create();
+
+ ScriptGroup.Builder b = new ScriptGroup.Builder(mRS);
+ b.addKernel(mConvolve3x3.getKernelID());
+ b.addKernel(mColorMatrix.getKernelID());
+ b.addConnection(connect, mConvolve3x3.getKernelID(), mColorMatrix.getKernelID());
+ group = b.create();
+
+ mConvolve3x3.setInput(a1_copy);
+ group.setOutput(mColorMatrix.getKernelID(), a2_copy);
+ group.execute();
+
+ // validate
+
+ }
+
+
+}
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/KernelTest.java b/tests/tests/renderscript/src/android/renderscript/cts/KernelTest.java
index 7caacfc..56b5e89 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/KernelTest.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/KernelTest.java
@@ -471,6 +471,7 @@
typeBuilder.setX(X).setY(Y);
Allocation A = Allocation.createTyped(mRS, typeBuilder.create());
s.bind_a(A);
+ s.set_aRaw(A);
s.forEach_root(A);
s.invoke_verify_root();
s.forEach_foo(A, A);
@@ -491,6 +492,7 @@
typeBuilder.setX(X).setY(Y);
Allocation A = Allocation.createTyped(mRS, typeBuilder.create());
s.bind_a(A);
+ s.set_aRaw(A);
s.forEach_foo(A, A);
s.invoke_verify_foo();
s.invoke_noroot_test();
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/SamplerTest.java b/tests/tests/renderscript/src/android/renderscript/cts/SamplerTest.java
index 35e813e..fa30f9f 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/SamplerTest.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/SamplerTest.java
@@ -115,6 +115,9 @@
assertTrue(Sampler.WRAP_LINEAR(mRS) != null);
assertTrue(Sampler.WRAP_LINEAR_MIP_LINEAR(mRS) != null);
assertTrue(Sampler.WRAP_NEAREST(mRS) != null);
+ assertTrue(Sampler.MIRRORED_REPEAT_NEAREST(mRS) != null);
+ assertTrue(Sampler.MIRRORED_REPEAT_LINEAR(mRS) != null);
+ assertTrue(Sampler.MIRRORED_REPEAT_LINEAR_MIP_LINEAR(mRS) != null);
}
public void testSamplerValue() {
@@ -124,9 +127,10 @@
assertEquals(Value.LINEAR_MIP_NEAREST, Value.valueOf("LINEAR_MIP_NEAREST"));
assertEquals(Value.WRAP, Value.valueOf("WRAP"));
assertEquals(Value.CLAMP, Value.valueOf("CLAMP"));
+ assertEquals(Value.MIRRORED_REPEAT, Value.valueOf("MIRRORED_REPEAT"));
// Make sure no new enums are added
- assertEquals(6, Value.values().length);
+ assertEquals(7, Value.values().length);
}
}
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_11.java b/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_11.java
index 6ad1748..a16059a 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_11.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_11.java
@@ -27,7 +27,16 @@
* @hide
*/
public class ScriptC_set_target_api_11 extends ScriptC {
+ private static final String __rs_resource_name = "set_target_api_11";
// Constructor
+ public ScriptC_set_target_api_11(RenderScript rs) {
+ this(rs,
+ rs.getApplicationContext().getResources(),
+ rs.getApplicationContext().getResources().getIdentifier(
+ __rs_resource_name, "raw",
+ rs.getApplicationContext().getPackageName()));
+ }
+
public ScriptC_set_target_api_11(RenderScript rs, Resources resources, int id) {
super(rs, resources, id);
}
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_12.java b/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_12.java
index dc772d5..5c31f94 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_12.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_12.java
@@ -27,7 +27,16 @@
* @hide
*/
public class ScriptC_set_target_api_12 extends ScriptC {
+ private static final String __rs_resource_name = "set_target_api_12";
// Constructor
+ public ScriptC_set_target_api_12(RenderScript rs) {
+ this(rs,
+ rs.getApplicationContext().getResources(),
+ rs.getApplicationContext().getResources().getIdentifier(
+ __rs_resource_name, "raw",
+ rs.getApplicationContext().getPackageName()));
+ }
+
public ScriptC_set_target_api_12(RenderScript rs, Resources resources, int id) {
super(rs, resources, id);
}
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_13.java b/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_13.java
index e1580e4..e12bf00 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_13.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_13.java
@@ -27,7 +27,16 @@
* @hide
*/
public class ScriptC_set_target_api_13 extends ScriptC {
+ private static final String __rs_resource_name = "set_target_api_13";
// Constructor
+ public ScriptC_set_target_api_13(RenderScript rs) {
+ this(rs,
+ rs.getApplicationContext().getResources(),
+ rs.getApplicationContext().getResources().getIdentifier(
+ __rs_resource_name, "raw",
+ rs.getApplicationContext().getPackageName()));
+ }
+
public ScriptC_set_target_api_13(RenderScript rs, Resources resources, int id) {
super(rs, resources, id);
}
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_14.java b/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_14.java
index 42d08c6..97879a5 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_14.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_14.java
@@ -27,7 +27,16 @@
* @hide
*/
public class ScriptC_set_target_api_14 extends ScriptC {
+ private static final String __rs_resource_name = "set_target_api_14";
// Constructor
+ public ScriptC_set_target_api_14(RenderScript rs) {
+ this(rs,
+ rs.getApplicationContext().getResources(),
+ rs.getApplicationContext().getResources().getIdentifier(
+ __rs_resource_name, "raw",
+ rs.getApplicationContext().getPackageName()));
+ }
+
public ScriptC_set_target_api_14(RenderScript rs, Resources resources, int id) {
super(rs, resources, id);
}
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_15.java b/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_15.java
index 6acfede..636a128 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_15.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_15.java
@@ -27,7 +27,16 @@
* @hide
*/
public class ScriptC_set_target_api_15 extends ScriptC {
+ private static final String __rs_resource_name = "set_target_api_15";
// Constructor
+ public ScriptC_set_target_api_15(RenderScript rs) {
+ this(rs,
+ rs.getApplicationContext().getResources(),
+ rs.getApplicationContext().getResources().getIdentifier(
+ __rs_resource_name, "raw",
+ rs.getApplicationContext().getPackageName()));
+ }
+
public ScriptC_set_target_api_15(RenderScript rs, Resources resources, int id) {
super(rs, resources, id);
}
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_16.java b/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_16.java
index f111005..435316f 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_16.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_16.java
@@ -27,7 +27,16 @@
* @hide
*/
public class ScriptC_set_target_api_16 extends ScriptC {
+ private static final String __rs_resource_name = "set_target_api_16";
// Constructor
+ public ScriptC_set_target_api_16(RenderScript rs) {
+ this(rs,
+ rs.getApplicationContext().getResources(),
+ rs.getApplicationContext().getResources().getIdentifier(
+ __rs_resource_name, "raw",
+ rs.getApplicationContext().getPackageName()));
+ }
+
public ScriptC_set_target_api_16(RenderScript rs, Resources resources, int id) {
super(rs, resources, id);
}
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_17.java b/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_17.java
new file mode 100644
index 0000000..c706ca6
--- /dev/null
+++ b/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_17.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2011-2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * This file is auto-generated. DO NOT MODIFY!
+ * The source Renderscript file: set_target_api_17.rs
+ */
+package android.renderscript.cts;
+
+import android.renderscript.*;
+import android.content.res.Resources;
+
+/**
+ * @hide
+ */
+public class ScriptC_set_target_api_17 extends ScriptC {
+ private static final String __rs_resource_name = "set_target_api_17";
+ // Constructor
+ public ScriptC_set_target_api_17(RenderScript rs) {
+ this(rs,
+ rs.getApplicationContext().getResources(),
+ rs.getApplicationContext().getResources().getIdentifier(
+ __rs_resource_name, "raw",
+ rs.getApplicationContext().getPackageName()));
+ }
+
+ public ScriptC_set_target_api_17(RenderScript rs, Resources resources, int id) {
+ super(rs, resources, id);
+ }
+
+ private final static int mExportFuncIdx_check = 0;
+ public void invoke_check(int version) {
+ FieldPacker check_fp = new FieldPacker(4);
+ check_fp.addI32(version);
+ invoke(mExportFuncIdx_check, check_fp);
+ }
+
+}
+
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_too_high.java b/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_too_high.java
new file mode 100644
index 0000000..f54ab46
--- /dev/null
+++ b/tests/tests/renderscript/src/android/renderscript/cts/ScriptC_set_target_api_too_high.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2011-2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * This file is auto-generated. DO NOT MODIFY!
+ * The source Renderscript file: set_target_api_16.rs
+ */
+package android.renderscript.cts;
+
+import android.renderscript.*;
+import android.content.res.Resources;
+
+/**
+ * @hide
+ */
+public class ScriptC_set_target_api_too_high extends ScriptC {
+ private static final String __rs_resource_name = "set_target_api_too_high";
+ // Constructor
+ public ScriptC_set_target_api_too_high(RenderScript rs) {
+ this(rs,
+ rs.getApplicationContext().getResources(),
+ rs.getApplicationContext().getResources().getIdentifier(
+ __rs_resource_name, "raw",
+ rs.getApplicationContext().getPackageName()));
+ }
+
+ public ScriptC_set_target_api_too_high(RenderScript rs, Resources resources, int id) {
+ super(rs, resources, id);
+ }
+
+ private final static int mExportFuncIdx_check = 0;
+ public void invoke_check(int version) {
+ FieldPacker check_fp = new FieldPacker(4);
+ check_fp.addI32(version);
+ invoke(mExportFuncIdx_check, check_fp);
+ }
+
+}
+
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/ScriptGroupTest.java b/tests/tests/renderscript/src/android/renderscript/cts/ScriptGroupTest.java
index e73195c..64496ef 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/ScriptGroupTest.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/ScriptGroupTest.java
@@ -262,7 +262,6 @@
compare.forEach_compare(out);
compare.forEach_getCompareResult(resultAlloc);
resultAlloc.copyTo(result);
- Log.e("ARGH", "result = " + result[0]);
assertTrue(result[0] == 2);
}
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/VersionTest.java b/tests/tests/renderscript/src/android/renderscript/cts/VersionTest.java
index 6c9e7e9..c3966a9 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/VersionTest.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/VersionTest.java
@@ -17,6 +17,7 @@
package android.renderscript.cts;
import android.renderscript.RenderScript.RSMessageHandler;
+import android.renderscript.RSRuntimeException;
import com.android.cts.stub.R;
/**
@@ -82,4 +83,26 @@
waitForMessage();
checkForErrors();
}
+
+ public void testVersion17() {
+ ScriptC_set_target_api_17 test17 =
+ new ScriptC_set_target_api_17(mRS,
+ mRes,
+ R.raw.set_target_api_17);
+ test17.invoke_check(17);
+ waitForMessage();
+ checkForErrors();
+ }
+
+ public void testVersion_too_high() {
+ try {
+ ScriptC_set_target_api_too_high test_too_high =
+ new ScriptC_set_target_api_too_high(mRS,
+ mRes,
+ R.raw.set_target_api_too_high);
+ fail("should throw RSRuntimeException");
+ } catch (RSRuntimeException e) {
+ }
+ checkForErrors();
+ }
}
diff --git a/tests/tests/security/src/android/security/cts/BannedFilesTest.java b/tests/tests/security/src/android/security/cts/BannedFilesTest.java
index ada9aaf..6ce8588 100644
--- a/tests/tests/security/src/android/security/cts/BannedFilesTest.java
+++ b/tests/tests/security/src/android/security/cts/BannedFilesTest.java
@@ -68,7 +68,7 @@
if (!FileUtils.getFileStatus(file, fs, false)) {
return;
}
- assertTrue((fs.mode & FileUtils.S_ISUID) == 0);
- assertTrue((fs.mode & FileUtils.S_ISGID) == 0);
+ assertTrue("File \"" + file + "\" is setUID", (fs.mode & FileUtils.S_ISUID) == 0);
+ assertTrue("File \"" + file + "\" is setGID", (fs.mode & FileUtils.S_ISGID) == 0);
}
}
diff --git a/tests/tests/security/src/android/security/cts/CertificateData.java b/tests/tests/security/src/android/security/cts/CertificateData.java
index 1714461..1b039a5 100644
--- a/tests/tests/security/src/android/security/cts/CertificateData.java
+++ b/tests/tests/security/src/android/security/cts/CertificateData.java
@@ -40,6 +40,7 @@
"40:54:DA:6F:1C:3F:40:74:AC:ED:0F:EC:CD:DB:79:D1:53:FB:90:1D",
"43:F9:B1:10:D5:BA:FD:48:22:52:31:B0:D0:08:2B:37:2F:EF:9A:54",
"F4:8B:11:BF:DE:AB:BE:94:54:20:71:E6:41:DE:6B:BE:88:2B:40:B9",
+ "58:E8:AB:B0:36:15:33:FB:80:F7:9B:1B:6D:29:D3:FF:8D:5F:00:F0",
"96:56:CD:7B:57:96:98:95:D0:E1:41:46:68:06:FB:B8:C6:11:06:87",
"55:A6:72:3E:CB:F2:EC:CD:C3:23:74:70:19:9D:2A:BE:11:E3:81:D1",
"D6:9B:56:11:48:F0:1C:77:C5:45:78:C1:09:26:DF:5B:85:69:76:AD",
@@ -48,6 +49,7 @@
"AD:7E:1C:28:B0:64:EF:8F:60:03:40:20:14:C3:D0:E3:37:0E:B5:8A",
"8D:17:84:D5:37:F3:03:7D:EC:70:FE:57:8B:51:9A:99:E6:10:D7:B0",
"AE:50:83:ED:7C:F4:5C:BC:8F:61:C6:21:FE:68:5D:79:42:21:15:6E",
+ "DA:FA:F7:FA:66:84:EC:06:8F:14:50:BD:C7:C2:81:A5:BC:A9:64:57",
"5F:4E:1F:CF:31:B7:91:3B:85:0B:54:F6:E5:FF:50:1A:2B:6F:C6:CF",
"74:F8:A3:C3:EF:E7:B3:90:06:4B:83:90:3C:21:64:60:20:E5:DF:CE",
"85:B5:FF:67:9B:0C:79:96:1F:C8:6E:44:22:00:46:13:DB:17:92:84",
@@ -64,6 +66,7 @@
"8C:F4:27:FD:79:0C:3A:D1:66:06:8D:E8:1E:57:EF:BB:93:22:72:D4",
"56:E0:FA:C0:3B:8F:18:23:55:18:E5:D3:11:CA:E8:C2:43:31:AB:66",
"02:72:68:29:3E:5F:5D:17:AA:A4:B3:C3:E6:36:1E:1F:92:57:5E:AA",
+ "2F:78:3D:25:52:18:A7:4A:65:39:71:B5:2C:A2:9C:45:15:6F:E9:19",
"97:81:79:50:D8:1C:96:70:CC:34:D8:09:CF:79:44:31:36:7E:F4:74",
"85:A4:08:C0:9C:19:3E:5D:51:58:7D:CD:D6:13:30:FD:8C:DE:37:BF",
"58:11:9F:0E:12:82:87:EA:50:FD:D9:87:45:6F:4F:78:DC:FA:D6:D4",
@@ -98,6 +101,7 @@
"69:BD:8C:F4:9C:D3:00:FB:59:2E:17:93:CA:55:6A:F3:EC:AA:35:FB",
"13:2D:0D:45:53:4B:69:97:CD:B2:D5:C3:39:E2:55:76:60:9B:5C:C6",
"5F:B7:EE:06:33:E2:59:DB:AD:0C:4C:9A:E6:D3:8F:1A:61:C7:DC:25",
+ "49:0A:75:74:DE:87:0A:47:FE:58:EE:F6:C7:6B:EB:C6:0B:12:40:99",
"25:01:90:19:CF:FB:D9:99:1C:B7:68:25:74:8D:94:5F:30:93:95:42",
"79:98:A3:08:E1:4D:65:85:E6:C2:1E:15:3A:71:9F:BA:5A:D3:4A:D9",
"B5:1C:06:7C:EE:2B:0C:3D:F8:55:AB:2D:92:F4:FE:39:D4:E7:0F:0E",
@@ -142,6 +146,7 @@
"21:FC:BD:8E:7F:6C:AF:05:1B:D1:B3:43:EC:A8:E7:61:47:F2:0F:8A",
"5F:3B:8C:F2:F8:10:B3:7D:78:B4:CE:EC:19:19:C3:73:34:B9:C7:74",
"2A:C8:D5:8B:57:CE:BF:2F:49:AF:F2:FC:76:8F:51:14:62:90:7A:41",
+ "96:C9:1B:0B:95:B4:10:98:42:FA:D0:D8:22:79:FE:60:FA:B9:16:83",
"CA:BB:51:67:24:00:58:8E:64:19:F1:D4:08:78:D0:40:3A:A2:02:64",
"5D:98:9C:DB:15:96:11:36:51:65:64:1B:56:0F:DB:EA:2A:C2:3E:F1",
"D8:A6:33:2C:E0:03:6F:B1:85:F6:63:4F:7D:6A:06:65:26:32:28:27",
diff --git a/tests/tests/security/src/android/security/cts/KernelSettingsTest.java b/tests/tests/security/src/android/security/cts/KernelSettingsTest.java
index cc4b6a6..6daaffe 100644
--- a/tests/tests/security/src/android/security/cts/KernelSettingsTest.java
+++ b/tests/tests/security/src/android/security/cts/KernelSettingsTest.java
@@ -19,6 +19,7 @@
import junit.framework.TestCase;
import java.io.BufferedReader;
+import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
@@ -86,6 +87,40 @@
}
}
+ /**
+ * Assert that support for loadable modules is not compiled into the
+ * kernel.
+ *
+ * Loadable modules are often used to implement rootkit like functionality.
+ * In addition, loadable modules enable support for /proc/sys/kernel/modprobe,
+ * which is commonly used by exploit writers to gain root access.
+ *
+ * Support for loadable modules can be removed by editing the Linux kernel
+ * config and removing the CONFIG_KMOD option.
+ */
+ public void testNoLoadableModules() throws IOException {
+ assertFalse(
+ "Support for loadable modules is compiled into the kernel. "
+ + "Loadable modules are often used by rootkits and other "
+ + "exploits and should be disabled. Please remove "
+ + "CONFIG_KMOD from your kernel config and compile "
+ + "all modules directly into the kernel.",
+ new File("/proc/sys/kernel/modprobe").exists());
+ }
+
+ /**
+ * Assert that the kernel config file is not compiled into the kernel.
+ *
+ * Compiling the config file into the kernel leaks the kernel base address
+ * via CONFIG_PHYS_OFFSET. It also wastes a small amount of valuable kernel memory.
+ */
+ public void testNoConfigGz() throws IOException {
+ assertFalse(
+ "/proc/config.gz is readable. Please recompile your "
+ + "kernel with CONFIG_IKCONFIG_PROC disabled",
+ new File("/proc/config.gz").exists());
+ }
+
private String getFile(String filename) throws IOException {
BufferedReader in = null;
try {
diff --git a/tests/tests/text/src/android/text/bidi/cts/BidiFormatterTest.java b/tests/tests/text/src/android/text/bidi/cts/BidiFormatterTest.java
new file mode 100644
index 0000000..198a52f
--- /dev/null
+++ b/tests/tests/text/src/android/text/bidi/cts/BidiFormatterTest.java
@@ -0,0 +1,422 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text.bidi.cts;
+
+import android.test.AndroidTestCase;
+import android.text.TextDirectionHeuristics;
+import android.text.bidi.BidiFormatter;
+
+import java.util.Locale;
+
+public class BidiFormatterTest extends AndroidTestCase {
+
+ private static final BidiFormatter LTR_FMT = BidiFormatter.getInstance(false /* LTR context */);
+ private static final BidiFormatter RTL_FMT = BidiFormatter.getInstance(true /* RTL context */);
+
+ private static final BidiFormatter LTR_FMT_EXIT_RESET =
+ new BidiFormatter.Builder(false /* LTR context */).stereoReset(false).build();
+ private static final BidiFormatter RTL_FMT_EXIT_RESET =
+ new BidiFormatter.Builder(true /* RTL context */).stereoReset(false).build();
+
+ private static final String EN = "abba";
+ private static final String HE = "\u05e0\u05e1";
+
+ private static final String LRM = "\u200E";
+ private static final String RLM = "\u200F";
+ private static final String LRE = "\u202A";
+ private static final String RLE = "\u202B";
+ private static final String PDF = "\u202C";
+
+ private static final String LEFT = "left";
+ private static final String RIGHT = "right";
+
+
+ public void testIsRtlContext() {
+ assertEquals(false, LTR_FMT.isRtlContext());
+ assertEquals(true, RTL_FMT.isRtlContext());
+
+ assertEquals(false, BidiFormatter.getInstance(Locale.ENGLISH).isRtlContext());
+ assertEquals(true, BidiFormatter.getInstance(true).isRtlContext());
+ }
+
+ public void testBuilderIsRtlContext() {
+ assertEquals(false, new BidiFormatter.Builder(false).build().isRtlContext());
+ assertEquals(true, new BidiFormatter.Builder(true).build().isRtlContext());
+ }
+
+ public void testIsRtl() {
+ assertEquals(true, BidiFormatter.getInstance(true).isRtl(HE));
+ assertEquals(true, BidiFormatter.getInstance(false).isRtl(HE));
+
+ assertEquals(false, BidiFormatter.getInstance(true).isRtl(EN));
+ assertEquals(false, BidiFormatter.getInstance(false).isRtl(EN));
+ }
+
+ public void testDirAttrValue() {
+ assertEquals("ltr", LTR_FMT.dirAttrValue(EN));
+ assertEquals("ltr", RTL_FMT.dirAttrValue(EN));
+
+ assertEquals("rtl", LTR_FMT.dirAttrValue(HE));
+ assertEquals("rtl", RTL_FMT.dirAttrValue(HE));
+
+ assertEquals("ltr", LTR_FMT.dirAttrValue(EN, TextDirectionHeuristics.LTR));
+ assertEquals("rtl", LTR_FMT.dirAttrValue(EN, TextDirectionHeuristics.RTL));
+
+ assertEquals("ltr", RTL_FMT.dirAttrValue(EN, TextDirectionHeuristics.LTR));
+ assertEquals("rtl", RTL_FMT.dirAttrValue(EN, TextDirectionHeuristics.RTL));
+
+ assertEquals("ltr", LTR_FMT.dirAttrValue(HE, TextDirectionHeuristics.LTR));
+ assertEquals("rtl", LTR_FMT.dirAttrValue(HE, TextDirectionHeuristics.RTL));
+
+ assertEquals("ltr", RTL_FMT.dirAttrValue(HE, TextDirectionHeuristics.LTR));
+ assertEquals("rtl", RTL_FMT.dirAttrValue(HE, TextDirectionHeuristics.RTL));
+
+ assertEquals("ltr", LTR_FMT.dirAttrValue("", TextDirectionHeuristics.LTR));
+ assertEquals("rtl", LTR_FMT.dirAttrValue("", TextDirectionHeuristics.RTL));
+
+ assertEquals("ltr", RTL_FMT.dirAttrValue("", TextDirectionHeuristics.LTR));
+ assertEquals("rtl", RTL_FMT.dirAttrValue("", TextDirectionHeuristics.RTL));
+ }
+
+ public void testDirAttr() {
+ assertEquals("", LTR_FMT.dirAttr(EN));
+ assertEquals("dir=\"ltr\"", RTL_FMT.dirAttr(EN));
+
+ assertEquals("dir=\"rtl\"", LTR_FMT.dirAttr(HE));
+ assertEquals("", RTL_FMT.dirAttr(HE));
+
+ assertEquals("", LTR_FMT.dirAttr(".", TextDirectionHeuristics.LTR));
+ assertEquals("dir=\"ltr\"", RTL_FMT.dirAttr(".", TextDirectionHeuristics.LTR));
+
+ assertEquals("dir=\"rtl\"", LTR_FMT.dirAttr(".", TextDirectionHeuristics.RTL));
+ assertEquals("", RTL_FMT.dirAttr(".", TextDirectionHeuristics.RTL));
+ }
+
+ public void testMarkAfter() {
+ assertEquals("uniform dir matches LTR context",
+ "", LTR_FMT.markAfter(EN));
+ assertEquals("uniform dir matches RTL context",
+ "", RTL_FMT.markAfter(HE));
+
+ assertEquals("exit dir opposite to LTR context",
+ LRM, LTR_FMT.markAfter(EN + HE, TextDirectionHeuristics.LTR));
+ assertEquals("exit dir opposite to RTL context",
+ RLM, RTL_FMT.markAfter(HE + EN, TextDirectionHeuristics.RTL));
+
+ assertEquals("overall dir (but not exit dir) opposite to LTR context",
+ LRM, LTR_FMT.markAfter(HE + EN, TextDirectionHeuristics.RTL));
+ assertEquals("overall dir (but not exit dir) opposite to RTL context",
+ RLM, RTL_FMT.markAfter(EN + HE, TextDirectionHeuristics.LTR));
+
+ assertEquals("exit dir neutral, overall dir matches LTR context",
+ "", LTR_FMT.markAfter(".", TextDirectionHeuristics.LTR));
+ assertEquals("exit dir neutral, overall dir matches RTL context",
+ "", RTL_FMT.markAfter(".", TextDirectionHeuristics.RTL));
+ }
+
+ public void testMarkBefore() {
+ assertEquals("uniform dir matches LTR context",
+ "", LTR_FMT.markBefore(EN));
+ assertEquals("uniform dir matches RTL context",
+ "", RTL_FMT.markBefore(HE));
+
+ assertEquals("entry dir opposite to LTR context",
+ LRM, LTR_FMT.markBefore(HE + EN, TextDirectionHeuristics.LTR));
+ assertEquals("entry dir opposite to RTL context",
+ RLM, RTL_FMT.markBefore(EN + HE, TextDirectionHeuristics.RTL));
+
+ assertEquals("overall dir (but not entry dir) opposite to LTR context",
+ LRM, LTR_FMT.markBefore(EN + HE, TextDirectionHeuristics.RTL));
+ assertEquals("overall dir (but not entry dir) opposite to RTL context",
+ RLM, RTL_FMT.markBefore(HE + EN, TextDirectionHeuristics.LTR));
+
+ assertEquals("exit dir neutral, overall dir matches LTR context",
+ "", LTR_FMT.markBefore(".", TextDirectionHeuristics.LTR));
+ assertEquals("exit dir neutral, overall dir matches RTL context",
+ "", RTL_FMT.markBefore(".", TextDirectionHeuristics.RTL));
+ }
+
+
+ public void testMark() {
+ assertEquals(LRM, LTR_FMT.mark());
+ assertEquals(RLM, RTL_FMT.mark());
+ }
+
+ public void testStartEdge() {
+ assertEquals(LEFT, LTR_FMT.startEdge());
+ assertEquals(RIGHT, RTL_FMT.startEdge());
+ }
+
+ public void testEndEdge() {
+ assertEquals(RIGHT, LTR_FMT.endEdge());
+ assertEquals(LEFT, RTL_FMT.endEdge());
+ }
+
+ public void testUnicodeWrap() {
+ // Uniform directionality in opposite context.
+ assertEquals("uniform dir opposite to LTR context",
+ RLE + "." + HE + "." + PDF + LRM,
+ LTR_FMT_EXIT_RESET.unicodeWrap("." + HE + "."));
+ assertEquals("uniform dir opposite to LTR context, stereo reset",
+ LRM + RLE + "." + HE + "." + PDF + LRM,
+ LTR_FMT.unicodeWrap("." + HE + "."));
+ assertEquals("uniform dir opposite to LTR context, stereo reset, no isolation",
+ RLE + "." + HE + "." + PDF,
+ LTR_FMT.unicodeWrap("." + HE + ".", false));
+ assertEquals("neutral treated as opposite to LTR context",
+ RLE + "." + PDF + LRM,
+ LTR_FMT_EXIT_RESET.unicodeWrap(".", TextDirectionHeuristics.RTL));
+ assertEquals("uniform dir opposite to RTL context",
+ LRE + "." + EN + "." + PDF + RLM,
+ RTL_FMT_EXIT_RESET.unicodeWrap("." + EN + "."));
+ assertEquals("uniform dir opposite to RTL context, stereo reset",
+ RLM + LRE + "." + EN + "." + PDF + RLM,
+ RTL_FMT.unicodeWrap("." + EN + "."));
+ assertEquals("uniform dir opposite to RTL context, stereo reset, no isolation",
+ LRE + "." + EN + "." + PDF,
+ RTL_FMT.unicodeWrap("." + EN + ".", false));
+ assertEquals("neutral treated as opposite to RTL context",
+ LRE + "." + PDF + RLM,
+ RTL_FMT_EXIT_RESET.unicodeWrap(".", TextDirectionHeuristics.LTR));
+
+ // We test mixed-directionality cases only with an explicit overall directionality parameter
+ // because the estimation logic is outside the sphere of BidiFormatter, and different
+ // estimators will treat them differently.
+
+ // Overall directionality matching context, but with opposite exit directionality.
+ assertEquals("exit dir opposite to LTR context",
+ EN + HE + LRM,
+ LTR_FMT_EXIT_RESET.unicodeWrap(EN + HE, TextDirectionHeuristics.LTR));
+ assertEquals("exit dir opposite to LTR context, stereo reset",
+ EN + HE + LRM,
+ LTR_FMT.unicodeWrap(EN + HE, TextDirectionHeuristics.LTR));
+ assertEquals("exit dir opposite to LTR context, stereo reset, no isolation",
+ EN + HE,
+ LTR_FMT.unicodeWrap(EN + HE, TextDirectionHeuristics.LTR, false));
+
+ assertEquals("exit dir opposite to RTL context",
+ HE + EN + RLM,
+ RTL_FMT_EXIT_RESET.unicodeWrap(HE + EN, TextDirectionHeuristics.RTL));
+ assertEquals("exit dir opposite to RTL context, stereo reset",
+ HE + EN + RLM,
+ RTL_FMT.unicodeWrap(HE + EN, TextDirectionHeuristics.RTL));
+ assertEquals("exit dir opposite to RTL context, stereo reset, no isolation",
+ HE + EN,
+ RTL_FMT.unicodeWrap(HE + EN, TextDirectionHeuristics.RTL, false));
+
+ // Overall directionality matching context, but with opposite entry directionality.
+ assertEquals("entry dir opposite to LTR context",
+ HE + EN,
+ LTR_FMT_EXIT_RESET.unicodeWrap(HE + EN, TextDirectionHeuristics.LTR));
+ assertEquals("entry dir opposite to LTR context, stereo reset",
+ LRM + HE + EN,
+ LTR_FMT.unicodeWrap(HE + EN, TextDirectionHeuristics.LTR));
+ assertEquals("entry dir opposite to LTR context, stereo reset, no isolation",
+ HE + EN,
+ LTR_FMT.unicodeWrap(HE + EN, TextDirectionHeuristics.LTR, false));
+
+ assertEquals("entry dir opposite to RTL context",
+ EN + HE,
+ RTL_FMT_EXIT_RESET.unicodeWrap(EN + HE, TextDirectionHeuristics.RTL));
+ assertEquals("entry dir opposite to RTL context, stereo reset",
+ RLM + EN + HE,
+ RTL_FMT.unicodeWrap(EN + HE, TextDirectionHeuristics.RTL));
+ assertEquals("entry dir opposite to RTL context, stereo reset, no isolation",
+ EN + HE,
+ RTL_FMT.unicodeWrap(EN + HE, TextDirectionHeuristics.RTL, false));
+
+ // Overall directionality matching context, but with opposite entry and exit directionality.
+ assertEquals("entry and exit dir opposite to LTR context",
+ HE + EN + HE + LRM,
+ LTR_FMT_EXIT_RESET.unicodeWrap(HE + EN + HE, TextDirectionHeuristics.LTR));
+ assertEquals("entry and exit dir opposite to LTR context, stereo reset",
+ LRM + HE + EN + HE + LRM,
+ LTR_FMT.unicodeWrap(HE + EN + HE, TextDirectionHeuristics.LTR));
+ assertEquals("entry and exit dir opposite to LTR context, no isolation",
+ HE + EN + HE,
+ LTR_FMT_EXIT_RESET.unicodeWrap(HE + EN + HE, TextDirectionHeuristics.LTR, false));
+
+ assertEquals("entry and exit dir opposite to RTL context",
+ EN + HE + EN + RLM,
+ RTL_FMT_EXIT_RESET.unicodeWrap(EN + HE + EN, TextDirectionHeuristics.RTL));
+ assertEquals("entry and exit dir opposite to RTL context, no isolation",
+ EN + HE + EN,
+ RTL_FMT_EXIT_RESET.unicodeWrap(EN + HE + EN, TextDirectionHeuristics.RTL, false));
+
+ // Entry and exit directionality matching context, but with opposite overall directionality.
+ assertEquals("overall dir (but not entry or exit dir) opposite to LTR context",
+ RLE + EN + HE + EN + PDF + LRM,
+ LTR_FMT_EXIT_RESET.unicodeWrap(EN + HE + EN, TextDirectionHeuristics.RTL));
+ assertEquals("overall dir (but not entry or exit dir) opposite to LTR context, stereo reset",
+ LRM + RLE + EN + HE + EN + PDF + LRM,
+ LTR_FMT.unicodeWrap(EN + HE + EN, TextDirectionHeuristics.RTL));
+ assertEquals("overall dir (but not entry or exit dir) opposite to LTR context, no isolation",
+ RLE + EN + HE + EN + PDF,
+ LTR_FMT_EXIT_RESET.unicodeWrap(EN + HE + EN, TextDirectionHeuristics.RTL, false));
+
+ assertEquals("overall dir (but not entry or exit dir) opposite to RTL context",
+ LRE + HE + EN + HE + PDF + RLM,
+ RTL_FMT_EXIT_RESET.unicodeWrap(HE + EN + HE, TextDirectionHeuristics.LTR));
+ assertEquals("overall dir (but not entry or exit dir) opposite to RTL context, stereo reset",
+ RLM + LRE + HE + EN + HE + PDF + RLM,
+ RTL_FMT.unicodeWrap(HE + EN + HE, TextDirectionHeuristics.LTR));
+ assertEquals("overall dir (but not entry or exit dir) opposite to RTL context, no isolation",
+ LRE + HE + EN + HE + PDF,
+ RTL_FMT_EXIT_RESET.unicodeWrap(HE + EN + HE, TextDirectionHeuristics.LTR, false));
+ }
+
+
+ public void testSpanWrap() {
+ // Uniform directionality in matching context.
+ assertEquals("uniform dir matches LTR context",
+ "& " + EN + "<", LTR_FMT.spanWrap("& " + EN + "<"));
+ assertEquals("neutral treated as matching LTR context",
+ ".", LTR_FMT.spanWrap(".", TextDirectionHeuristics.LTR));
+ assertEquals("uniform dir matches RTL context",
+ "& " + HE + "<", RTL_FMT.spanWrap("& " + HE + "<"));
+ assertEquals("neutral treated as matching RTL context",
+ ".", RTL_FMT.spanWrap(".", TextDirectionHeuristics.RTL));
+
+ // Uniform directionality in opposite context.
+ assertEquals("uniform dir opposite to LTR context",
+ "<span dir=\"rtl\">." + HE + ".</span>" + LRM,
+ LTR_FMT_EXIT_RESET.spanWrap("." + HE + "."));
+ assertEquals("uniform dir opposite to LTR context, stereo reset",
+ LRM + "<span dir=\"rtl\">." + HE + ".</span>" + LRM,
+ LTR_FMT.spanWrap("." + HE + "."));
+ assertEquals("uniform dir opposite to LTR context, no isolation",
+ "<span dir=\"rtl\">." + HE + ".</span>",
+ LTR_FMT_EXIT_RESET.spanWrap("." + HE + ".", false));
+ assertEquals("uniform dir opposite to LTR context, stereo reset, no isolation",
+ "<span dir=\"rtl\">." + HE + ".</span>",
+ LTR_FMT.spanWrap("." + HE + ".", false));
+ assertEquals("neutral treated as opposite to LTR context",
+ "<span dir=\"rtl\">" + "." + "</span>" + LRM,
+ LTR_FMT_EXIT_RESET.spanWrap(".", TextDirectionHeuristics.RTL));
+ assertEquals("uniform dir opposite to RTL context",
+ "<span dir=\"ltr\">." + EN + ".</span>" + RLM,
+ RTL_FMT_EXIT_RESET.spanWrap("." + EN + "."));
+ assertEquals("uniform dir opposite to RTL context, stereo reset",
+ RLM + "<span dir=\"ltr\">." + EN + ".</span>" + RLM,
+ RTL_FMT.spanWrap("." + EN + "."));
+ assertEquals("uniform dir opposite to RTL context, no isolation",
+ "<span dir=\"ltr\">." + EN + ".</span>",
+ RTL_FMT_EXIT_RESET.spanWrap("." + EN + ".", false));
+ assertEquals("uniform dir opposite to RTL context, stereo reset, no isolation",
+ "<span dir=\"ltr\">." + EN + ".</span>",
+ RTL_FMT.spanWrap("." + EN + ".", false));
+ assertEquals("neutral treated as opposite to RTL context",
+ "<span dir=\"ltr\">" + "." + "</span>" + RLM,
+ RTL_FMT_EXIT_RESET.spanWrap(".", TextDirectionHeuristics.LTR));
+
+ // We test mixed-directionality cases only with an explicit overall directionality parameter
+ // because the estimation logic is outside the sphere of BidiFormatter, and different
+ // estimators will treat them differently.
+
+ // Overall directionality matching context, but with opposite exit directionality.
+ assertEquals("exit dir opposite to LTR context",
+ EN + HE + LRM,
+ LTR_FMT_EXIT_RESET.spanWrap(EN + HE, TextDirectionHeuristics.LTR));
+ assertEquals("exit dir opposite to LTR context, stereo reset",
+ EN + HE + LRM,
+ LTR_FMT.spanWrap(EN + HE, TextDirectionHeuristics.LTR));
+ assertEquals("exit dir opposite to LTR context, no isolation",
+ EN + HE,
+ LTR_FMT_EXIT_RESET.spanWrap(EN + HE, TextDirectionHeuristics.LTR, false));
+ assertEquals("exit dir opposite to LTR context, stereo reset, no isolation",
+ EN + HE,
+ LTR_FMT.spanWrap(EN + HE, TextDirectionHeuristics.LTR, false));
+ assertEquals("exit dir opposite to RTL context",
+ HE + EN + RLM,
+ RTL_FMT_EXIT_RESET.spanWrap(HE + EN, TextDirectionHeuristics.RTL));
+ assertEquals("exit dir opposite to RTL context, stereo reset",
+ HE + EN + RLM,
+ RTL_FMT.spanWrap(HE + EN, TextDirectionHeuristics.RTL));
+ assertEquals("exit dir opposite to RTL context, no isolation",
+ HE + EN,
+ RTL_FMT_EXIT_RESET.spanWrap(HE + EN, TextDirectionHeuristics.RTL, false));
+ assertEquals("exit dir opposite to RTL context, stereo reset, no isolation",
+ HE + EN,
+ RTL_FMT.spanWrap( HE + EN, TextDirectionHeuristics.RTL, false));
+
+ // Overall directionality matching context, but with opposite entry directionality.
+ assertEquals("entry dir opposite to LTR context",
+ HE + EN,
+ LTR_FMT_EXIT_RESET.spanWrap(HE + EN, TextDirectionHeuristics.LTR));
+ assertEquals("entry dir opposite to LTR context, stereo reset",
+ LRM + HE + EN,
+ LTR_FMT.spanWrap(HE + EN, TextDirectionHeuristics.LTR));
+ assertEquals("entry dir opposite to LTR context, no isolation",
+ HE + EN,
+ LTR_FMT_EXIT_RESET.spanWrap(HE + EN, TextDirectionHeuristics.LTR, false));
+ assertEquals("entry dir opposite to LTR context, stereo reset, no isolation",
+ HE + EN,
+ LTR_FMT.spanWrap(HE + EN, TextDirectionHeuristics.LTR, false));
+ assertEquals("entry dir opposite to RTL context",
+ EN + HE,
+ RTL_FMT_EXIT_RESET.spanWrap(EN + HE, TextDirectionHeuristics.RTL));
+ assertEquals("entry dir opposite to RTL context, stereo reset",
+ RLM + EN + HE,
+ RTL_FMT.spanWrap(EN + HE, TextDirectionHeuristics.RTL));
+ assertEquals("entry dir opposite to RTL context, no isolation",
+ EN + HE,
+ RTL_FMT_EXIT_RESET.spanWrap(EN + HE, TextDirectionHeuristics.RTL, false));
+ assertEquals("entry dir opposite to RTL context, stereo reset, no isolation",
+ EN + HE,
+ RTL_FMT.spanWrap(EN + HE, TextDirectionHeuristics.RTL, false));
+
+ // Overall directionality matching context, but with opposite entry and exit directionality.
+ assertEquals("entry and exit dir opposite to LTR context",
+ HE + EN + HE + LRM,
+ LTR_FMT_EXIT_RESET.spanWrap(HE + EN + HE, TextDirectionHeuristics.LTR));
+ assertEquals("entry and exit dir opposite to LTR context, stereo reset",
+ LRM + HE + EN + HE + LRM,
+ LTR_FMT.spanWrap(HE + EN + HE, TextDirectionHeuristics.LTR));
+ assertEquals("entry and exit dir opposite to LTR context, no isolation",
+ HE + EN + HE,
+ LTR_FMT_EXIT_RESET.spanWrap(HE + EN + HE, TextDirectionHeuristics.LTR, false));
+ assertEquals("entry and exit dir opposite to RTL context",
+ EN + HE + EN + RLM,
+ RTL_FMT_EXIT_RESET.spanWrap(EN + HE + EN, TextDirectionHeuristics.RTL));
+ assertEquals("entry and exit dir opposite to RTL context, stereo reset",
+ RLM + EN + HE + EN + RLM,
+ RTL_FMT.spanWrap(EN + HE + EN, TextDirectionHeuristics.RTL));
+ assertEquals("entry and exit dir opposite to RTL context, no isolation",
+ EN + HE + EN,
+ RTL_FMT_EXIT_RESET.spanWrap(EN + HE + EN, TextDirectionHeuristics.RTL, false));
+
+ // Entry and exit directionality matching context, but with opposite overall directionality.
+ assertEquals("overall dir (but not entry or exit dir) opposite to LTR context",
+ "<span dir=\"rtl\">" + EN + HE + EN + "</span>" + LRM,
+ LTR_FMT_EXIT_RESET.spanWrap(EN + HE + EN, TextDirectionHeuristics.RTL));
+ assertEquals("overall dir (but not entry or exit dir) opposite to LTR context, stereo reset",
+ LRM + "<span dir=\"rtl\">" + EN + HE + EN + "</span>" + LRM,
+ LTR_FMT.spanWrap(EN + HE + EN, TextDirectionHeuristics.RTL));
+ assertEquals("overall dir (but not entry or exit dir) opposite to LTR context, no isolation",
+ "<span dir=\"rtl\">" + EN + HE + EN + "</span>",
+ LTR_FMT_EXIT_RESET.spanWrap(EN + HE + EN, TextDirectionHeuristics.RTL, false));
+ assertEquals("overall dir (but not entry or exit dir) opposite to RTL context",
+ "<span dir=\"ltr\">" + HE + EN + HE + "</span>" + RLM,
+ RTL_FMT_EXIT_RESET.spanWrap(HE + EN + HE, TextDirectionHeuristics.LTR));
+ assertEquals("overall dir (but not entry or exit dir) opposite to RTL context, stereo reset",
+ RLM + "<span dir=\"ltr\">" + HE + EN + HE + "</span>" + RLM,
+ RTL_FMT.spanWrap(HE + EN + HE, TextDirectionHeuristics.LTR));
+ assertEquals("overall dir (but not entry or exit dir) opposite to RTL context, no isolation",
+ "<span dir=\"ltr\">" + HE + EN + HE + "</span>",
+ RTL_FMT_EXIT_RESET.spanWrap(HE + EN + HE, TextDirectionHeuristics.LTR, false));
+ }
+}
diff --git a/tests/tests/text/src/android/text/cts/EmojiConstants.java b/tests/tests/text/src/android/text/cts/EmojiConstants.java
new file mode 100755
index 0000000..dcae622
--- /dev/null
+++ b/tests/tests/text/src/android/text/cts/EmojiConstants.java
@@ -0,0 +1,759 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text.cts;
+
+/**
+ * List of Emoji tested by EmojiTest
+ */
+class EmojiConstants {
+
+ /*
+ * Emoji list from Unicode.org:
+ * http://unicode.org/cldr/utility/list-unicodeset.jsp?a=%5Cp%7BEmoji%7D%0D%0A&g=
+ */
+ static int[] emojiCodePoints = {
+ 0x000A9, // COPYRIGHT SIGN
+ 0x000AE, // REGISTERED SIGN
+ 0x02002, // EN SPACE
+ 0x02003, // EM SPACE
+ 0x02005, // FOUR-PER-EM SPACE
+ 0x0203C, // DOUBLE EXCLAMATION MARK
+ 0x02049, // EXCLAMATION QUESTION MARK
+ 0x020E3, // COMBINING ENCLOSING KEYCAP
+ 0x02122, // TRADE MARK SIGN
+ 0x02139, // INFORMATION SOURCE
+ 0x02194, // LEFT RIGHT ARROW
+ 0x02195, // UP DOWN ARROW
+ 0x02196, // NORTH WEST ARROW
+ 0x02197, // NORTH EAST ARROW
+ 0x02198, // SOUTH EAST ARROW
+ 0x02199, // SOUTH WEST ARROW
+ 0x021A9, // LEFTWARDS ARROW WITH HOOK
+ 0x021AA, // RIGHTWARDS ARROW WITH HOOK
+ 0x0231A, // WATCH
+ 0x0231B, // HOURGLASS
+ 0x023E9, // BLACK RIGHT-POINTING DOUBLE TRIANGLE
+ 0x023EA, // BLACK LEFT-POINTING DOUBLE TRIANGLE
+ 0x023EB, // BLACK UP-POINTING DOUBLE TRIANGLE
+ 0x023EC, // BLACK DOWN-POINTING DOUBLE TRIANGLE
+ 0x023F0, // ALARM CLOCK
+ 0x023F3, // HOURGLASS WITH FLOWING SAND
+ 0x024C2, // CIRCLED LATIN CAPITAL LETTER M
+ 0x025AA, // BLACK SMALL SQUARE
+ 0x025AB, // WHITE SMALL SQUARE
+ 0x025B6, // BLACK RIGHT-POINTING TRIANGLE
+ 0x025C0, // BLACK LEFT-POINTING TRIANGLE
+ 0x025FB, // WHITE MEDIUM SQUARE
+ 0x025FC, // BLACK MEDIUM SQUARE
+ 0x025FD, // WHITE MEDIUM SMALL SQUARE
+ 0x025FE, // BLACK MEDIUM SMALL SQUARE
+ 0x02600, // BLACK SUN WITH RAYS
+ 0x02601, // CLOUD
+ 0x0260E, // BLACK TELEPHONE
+ 0x02611, // BALLOT BOX WITH CHECK
+ 0x02615, // HOT BEVERAGE
+ 0x02668, // HOT SPRINGS
+ 0x0267F, // WHEELCHAIR SYMBOL
+ 0x026A0, // WARNING SIGN
+ 0x026A1, // HIGH VOLTAGE SIGN
+ 0x02614, // UMBRELLA WITH RAIN DROPS
+ 0x0261D, // WHITE UP POINTING INDEX
+ 0x0263A, // WHITE SMILING FACE
+ 0x02648, // ARIES
+ 0x02649, // TAURUS
+ 0x0264A, // GEMINI
+ 0x0264B, // CANCER
+ 0x0264C, // LEO
+ 0x0264D, // VIRGO
+ 0x0264E, // LIBRA
+ 0x0264F, // SCORPIUS
+ 0x02650, // SAGITTARIUS
+ 0x02651, // CAPRICORN
+ 0x02652, // AQUARIUS
+ 0x02653, // PISCES
+ 0x026CE, // OPHIUCHUS
+ 0x02660, // BLACK SPADE SUIT
+ 0x02663, // BLACK CLUB SUIT
+ 0x02665, // BLACK HEART SUIT
+ 0x02666, // BLACK DIAMOND SUIT
+ 0x0267B, // BLACK UNIVERSAL RECYCLING SYMBOL
+ 0x02693, // ANCHOR
+ 0x026AA, // MEDIUM WHITE CIRCLE
+ 0x026AB, // MEDIUM BLACK CIRCLE
+ 0x026BD, // SOCCER BALL
+ 0x026BE, // BASEBALL
+ 0x026C4, // SNOWMAN WITHOUT SNOW
+ 0x026C5, // SUN BEHIND CLOUD
+ 0x026D4, // NO ENTRY
+ 0x026EA, // CHURCH
+ 0x026F2, // FOUNTAIN
+ 0x026F3, // FLAG IN HOLE
+ 0x026F5, // SAILBOAT
+ 0x026FA, // TENT
+ 0x026FD, // FUEL PUMP
+ 0x02702, // BLACK SCISSORS
+ 0x02705, // WHITE HEAVY CHECK MARK
+ 0x02708, // AIRPLANE
+ 0x02709, // ENVELOPE
+ 0x0270A, // RAISED FIST
+ 0x0270B, // RAISED HAND
+ 0x0270C, // VICTORY HAND
+ 0x0270F, // PENCIL
+ 0x02712, // BLACK NIB
+ 0x02714, // HEAVY CHECK MARK
+ 0x02716, // HEAVY MULTIPLICATION X
+ 0x0274C, // CROSS MARK
+ 0x0274E, // NEGATIVE SQUARED CROSS MARK
+ 0x02753, // BLACK QUESTION MARK ORNAMENT
+ 0x02754, // WHITE QUESTION MARK ORNAMENT
+ 0x02755, // WHITE EXCLAMATION MARK ORNAMENT
+ 0x02757, // HEAVY EXCLAMATION MARK SYMBOL
+ 0x027B0, // CURLY LOOP
+ 0x027BF, // DOUBLE CURLY LOOP
+ 0x02728, // SPARKLES
+ 0x02733, // EIGHT SPOKED ASTERISK
+ 0x02734, // EIGHT POINTED BLACK STAR
+ 0x02744, // SNOWFLAKE
+ 0x02747, // SPARKLE
+ 0x02764, // HEAVY BLACK HEART
+ 0x02795, // HEAVY PLUS SIGN
+ 0x02796, // HEAVY MINUS SIGN
+ 0x02797, // HEAVY DIVISION SIGN
+ 0x027A1, // BLACK RIGHTWARDS ARROW
+ 0x02934, // ARROW POINTING RIGHTWARDS THEN CURVING UPWARDS
+ 0x02935, // ARROW POINTING RIGHTWARDS THEN CURVING DOWNWARDS
+ 0x02B05, // LEFTWARDS BLACK ARROW
+ 0x02B06, // UPWARDS BLACK ARROW
+ 0x02B07, // DOWNWARDS BLACK ARROW
+ 0x02B1B, // BLACK LARGE SQUARE
+ 0x02B1C, // WHITE LARGE SQUARE
+ 0x02B50, // WHITE MEDIUM STAR
+ 0x02B55, // HEAVY LARGE CIRCLE
+ 0x03030, // WAVY DASH
+ 0x0303D, // PART ALTERNATION MARK
+ 0x03297, // CIRCLED IDEOGRAPH CONGRATULATION
+ 0x03299, // CIRCLED IDEOGRAPH SECRET
+ 0x1F004, // MAHJONG TILE RED DRAGON
+ 0x1F0CF, // PLAYING CARD BLACK JOKER
+ 0x1F170, // NEGATIVE SQUARED LATIN CAPITAL LETTER A
+ 0x1F171, // NEGATIVE SQUARED LATIN CAPITAL LETTER B
+ 0x1F17E, // NEGATIVE SQUARED LATIN CAPITAL LETTER O
+ 0x1F17F, // NEGATIVE SQUARED LATIN CAPITAL LETTER P
+ 0x1F18E, // NEGATIVE SQUARED AB
+ 0x1F191, // SQUARED CL
+ 0x1F192, // SQUARED COOL
+ 0x1F193, // SQUARED FREE
+ 0x1F194, // SQUARED ID
+ 0x1F195, // SQUARED NEW
+ 0x1F196, // SQUARED NG
+ 0x1F197, // SQUARED OK
+ 0x1F198, // SQUARED SOS
+ 0x1F199, // SQUARED UP WITH EXCLAMATION MARK
+ 0x1F19A, // SQUARED VS
+ 0x1F1E6, // REGIONAL INDICATOR SYMBOL LETTER A
+ 0x1F1E7, // REGIONAL INDICATOR SYMBOL LETTER B
+ 0x1F1E8, // REGIONAL INDICATOR SYMBOL LETTER C
+ 0x1F1E9, // REGIONAL INDICATOR SYMBOL LETTER D
+ 0x1F1EA, // REGIONAL INDICATOR SYMBOL LETTER E
+ 0x1F1EB, // REGIONAL INDICATOR SYMBOL LETTER F
+ 0x1F1EC, // REGIONAL INDICATOR SYMBOL LETTER G
+ 0x1F1ED, // REGIONAL INDICATOR SYMBOL LETTER H
+ 0x1F1EE, // REGIONAL INDICATOR SYMBOL LETTER I
+ 0x1F1EF, // REGIONAL INDICATOR SYMBOL LETTER J
+ 0x1F1F0, // REGIONAL INDICATOR SYMBOL LETTER K
+ 0x1F1F1, // REGIONAL INDICATOR SYMBOL LETTER L
+ 0x1F1F2, // REGIONAL INDICATOR SYMBOL LETTER M
+ 0x1F1F3, // REGIONAL INDICATOR SYMBOL LETTER N
+ 0x1F1F4, // REGIONAL INDICATOR SYMBOL LETTER O
+ 0x1F1F5, // REGIONAL INDICATOR SYMBOL LETTER P
+ 0x1F1F6, // REGIONAL INDICATOR SYMBOL LETTER Q
+ 0x1F1F7, // REGIONAL INDICATOR SYMBOL LETTER R
+ 0x1F1F8, // REGIONAL INDICATOR SYMBOL LETTER S
+ 0x1F1F9, // REGIONAL INDICATOR SYMBOL LETTER T
+ 0x1F1FA, // REGIONAL INDICATOR SYMBOL LETTER U
+ 0x1F1FB, // REGIONAL INDICATOR SYMBOL LETTER V
+ 0x1F1FC, // REGIONAL INDICATOR SYMBOL LETTER W
+ 0x1F1FD, // REGIONAL INDICATOR SYMBOL LETTER X
+ 0x1F1FE, // REGIONAL INDICATOR SYMBOL LETTER Y
+ 0x1F1FF, // REGIONAL INDICATOR SYMBOL LETTER Z
+ 0x1F201, // SQUARED KATAKANA KOKO
+ 0x1F202, // SQUARED KATAKANA SA
+ 0x1F21A, // SQUARED CJK UNIFIED IDEOGRAPH-7121
+ 0x1F22F, // SQUARED CJK UNIFIED IDEOGRAPH-6307
+ 0x1F232, // SQUARED CJK UNIFIED IDEOGRAPH-7981
+ 0x1F233, // SQUARED CJK UNIFIED IDEOGRAPH-7A7A
+ 0x1F234, // SQUARED CJK UNIFIED IDEOGRAPH-5408
+ 0x1F235, // SQUARED CJK UNIFIED IDEOGRAPH-6E80
+ 0x1F236, // SQUARED CJK UNIFIED IDEOGRAPH-6709
+ 0x1F237, // SQUARED CJK UNIFIED IDEOGRAPH-6708
+ 0x1F238, // SQUARED CJK UNIFIED IDEOGRAPH-7533
+ 0x1F239, // SQUARED CJK UNIFIED IDEOGRAPH-5272
+ 0x1F23A, // SQUARED CJK UNIFIED IDEOGRAPH-55B6
+ 0x1F250, // CIRCLED IDEOGRAPH ADVANTAGE
+ 0x1F251, // CIRCLED IDEOGRAPH ACCEPT
+ 0x1F300, // CYCLONE
+ 0x1F301, // FOGGY
+ 0x1F302, // CLOSED UMBRELLA
+ 0x1F303, // NIGHT WITH STARS
+ 0x1F304, // SUNRISE OVER MOUNTAINS
+ 0x1F305, // SUNRISE
+ 0x1F306, // CITYSCAPE AT DUSK
+ 0x1F307, // SUNSET OVER BUILDINGS
+ 0x1F308, // RAINBOW
+ 0x1F309, // BRIDGE AT NIGHT
+ 0x1F30A, // WATER WAVE
+ 0x1F30B, // VOLCANO
+ 0x1F30C, // MILKY WAY
+ 0x1F30F, // EARTH GLOBE ASIA-AUSTRALIA
+ 0x1F311, // NEW MOON SYMBOL
+ 0x1F313, // FIRST QUARTER MOON SYMBOL
+ 0x1F314, // WAXING GIBBOUS MOON SYMBOL
+ 0x1F315, // FULL MOON SYMBOL
+ 0x1F319, // CRESCENT MOON
+ 0x1F31B, // FIRST QUARTER MOON WITH FACE
+ 0x1F31F, // GLOWING STAR
+ 0x1F320, // SHOOTING STAR
+ 0x1F330, // CHESTNUT
+ 0x1F331, // SEEDLING
+ 0x1F334, // PALM TREE
+ 0x1F335, // CACTUS
+ 0x1F337, // TULIP
+ 0x1F338, // CHERRY BLOSSOM
+ 0x1F339, // ROSE
+ 0x1F33A, // HIBISCUS
+ 0x1F33B, // SUNFLOWER
+ 0x1F33C, // BLOSSOM
+ 0x1F33D, // EAR OF MAIZE
+ 0x1F33E, // EAR OF RICE
+ 0x1F33F, // HERB
+ 0x1F340, // FOUR LEAF CLOVER
+ 0x1F341, // MAPLE LEAF
+ 0x1F342, // FALLEN LEAF
+ 0x1F343, // LEAF FLUTTERING IN WIND
+ 0x1F344, // MUSHROOM
+ 0x1F345, // TOMATO
+ 0x1F346, // AUBERGINE
+ 0x1F347, // GRAPES
+ 0x1F348, // MELON
+ 0x1F349, // WATERMELON
+ 0x1F34A, // TANGERINE
+ 0x1F34C, // BANANA
+ 0x1F34D, // PINEAPPLE
+ 0x1F34E, // RED APPLE
+ 0x1F34F, // GREEN APPLE
+ 0x1F351, // PEACH
+ 0x1F352, // CHERRIES
+ 0x1F353, // STRAWBERRY
+ 0x1F354, // HAMBURGER
+ 0x1F355, // SLICE OF PIZZA
+ 0x1F356, // MEAT ON BONE
+ 0x1F357, // POULTRY LEG
+ 0x1F358, // RICE CRACKER
+ 0x1F359, // RICE BALL
+ 0x1F35A, // COOKED RICE
+ 0x1F35B, // CURRY AND RICE
+ 0x1F35C, // STEAMING BOWL
+ 0x1F35D, // SPAGHETTI
+ 0x1F35E, // BREAD
+ 0x1F35F, // FRENCH FRIES
+ 0x1F360, // ROASTED SWEET POTATO
+ 0x1F361, // DANGO
+ 0x1F362, // ODEN
+ 0x1F363, // SUSHI
+ 0x1F364, // FRIED SHRIMP
+ 0x1F365, // FISH CAKE WITH SWIRL DESIGN
+ 0x1F366, // SOFT ICE CREAM
+ 0x1F367, // SHAVED ICE
+ 0x1F368, // ICE CREAM
+ 0x1F369, // DOUGHNUT
+ 0x1F36A, // COOKIE
+ 0x1F36B, // CHOCOLATE BAR
+ 0x1F36C, // CANDY
+ 0x1F36D, // LOLLIPOP
+ 0x1F36E, // CUSTARD
+ 0x1F36F, // HONEY POT
+ 0x1F370, // SHORTCAKE
+ 0x1F371, // BENTO BOX
+ 0x1F372, // POT OF FOOD
+ 0x1F373, // COOKING
+ 0x1F374, // FORK AND KNIFE
+ 0x1F375, // TEACUP WITHOUT HANDLE
+ 0x1F376, // SAKE BOTTLE AND CUP
+ 0x1F377, // WINE GLASS
+ 0x1F378, // COCKTAIL GLASS
+ 0x1F379, // TROPICAL DRINK
+ 0x1F37A, // BEER MUG
+ 0x1F37B, // CLINKING BEER MUGS
+ 0x1F380, // RIBBON
+ 0x1F381, // WRAPPED PRESENT
+ 0x1F382, // BIRTHDAY CAKE
+ 0x1F383, // JACK-O-LANTERN
+ 0x1F384, // CHRISTMAS TREE
+ 0x1F385, // FATHER CHRISTMAS
+ 0x1F386, // FIREWORKS
+ 0x1F387, // FIREWORK SPARKLER
+ 0x1F388, // BALLOON
+ 0x1F389, // PARTY POPPER
+ 0x1F38A, // CONFETTI BALL
+ 0x1F38B, // TANABATA TREE
+ 0x1F38C, // CROSSED FLAGS
+ 0x1F38D, // PINE DECORATION
+ 0x1F38E, // JAPANESE DOLLS
+ 0x1F38F, // CARP STREAMER
+ 0x1F390, // WIND CHIME
+ 0x1F391, // MOON VIEWING CEREMONY
+ 0x1F392, // SCHOOL SATCHEL
+ 0x1F393, // GRADUATION CAP
+ 0x1F3A0, // CAROUSEL HORSE
+ 0x1F3A1, // FERRIS WHEEL
+ 0x1F3A2, // ROLLER COASTER
+ 0x1F3A3, // FISHING POLE AND FISH
+ 0x1F3A4, // MICROPHONE
+ 0x1F3A5, // MOVIE CAMERA
+ 0x1F3A6, // CINEMA
+ 0x1F3A7, // HEADPHONE
+ 0x1F3A8, // ARTIST PALETTE
+ 0x1F3A9, // TOP HAT
+ 0x1F3AA, // CIRCUS TENT
+ 0x1F3AB, // TICKET
+ 0x1F3AC, // CLAPPER BOARD
+ 0x1F3AD, // PERFORMING ARTS
+ 0x1F3AE, // VIDEO GAME
+ 0x1F3AF, // DIRECT HIT
+ 0x1F3B0, // SLOT MACHINE
+ 0x1F3B1, // BILLIARDS
+ 0x1F3B2, // GAME DIE
+ 0x1F3B3, // BOWLING
+ 0x1F3B4, // FLOWER PLAYING CARDS
+ 0x1F3B5, // MUSICAL NOTE
+ 0x1F3B6, // MULTIPLE MUSICAL NOTES
+ 0x1F3B7, // SAXOPHONE
+ 0x1F3B8, // GUITAR
+ 0x1F3B9, // MUSICAL KEYBOARD
+ 0x1F3BA, // TRUMPET
+ 0x1F3BB, // VIOLIN
+ 0x1F3BC, // MUSICAL SCORE
+ 0x1F3BD, // RUNNING SHIRT WITH SASH
+ 0x1F3BE, // TENNIS RACQUET AND BALL
+ 0x1F3BF, // SKI AND SKI BOOT
+ 0x1F3C0, // BASKETBALL AND HOOP
+ 0x1F3C1, // CHEQUERED FLAG
+ 0x1F3C2, // SNOWBOARDER
+ 0x1F3C3, // RUNNER
+ 0x1F3C4, // SURFER
+ 0x1F3C6, // TROPHY
+ 0x1F3C8, // AMERICAN FOOTBALL
+ 0x1F3CA, // SWIMMER
+ 0x1F3E0, // HOUSE BUILDING
+ 0x1F3E1, // HOUSE WITH GARDEN
+ 0x1F3E2, // OFFICE BUILDING
+ 0x1F3E3, // JAPANESE POST OFFICE
+ 0x1F3E5, // HOSPITAL
+ 0x1F3E6, // BANK
+ 0x1F3E7, // AUTOMATED TELLER MACHINE
+ 0x1F3E8, // HOTEL
+ 0x1F3E9, // LOVE HOTEL
+ 0x1F3EA, // CONVENIENCE STORE
+ 0x1F3EB, // SCHOOL
+ 0x1F3EC, // DEPARTMENT STORE
+ 0x1F3ED, // FACTORY
+ 0x1F3EE, // IZAKAYA LANTERN
+ 0x1F3EF, // JAPANESE CASTLE
+ 0x1F3F0, // EUROPEAN CASTLE
+ 0x1F40C, // SNAIL
+ 0x1F40D, // SNAKE
+ 0x1F40E, // HORSE
+ 0x1F411, // SHEEP
+ 0x1F412, // MONKEY
+ 0x1F414, // CHICKEN
+ 0x1F417, // BOAR
+ 0x1F418, // ELEPHANT
+ 0x1F419, // OCTOPUS
+ 0x1F41A, // SPIRAL SHELL
+ 0x1F41B, // BUG
+ 0x1F41C, // ANT
+ 0x1F41D, // HONEYBEE
+ 0x1F41E, // LADY BEETLE
+ 0x1F41F, // FISH
+ 0x1F420, // TROPICAL FISH
+ 0x1F421, // BLOWFISH
+ 0x1F422, // TURTLE
+ 0x1F423, // HATCHING CHICK
+ 0x1F424, // BABY CHICK
+ 0x1F425, // FRONT-FACING BABY CHICK
+ 0x1F426, // BIRD
+ 0x1F427, // PENGUIN
+ 0x1F428, // KOALA
+ 0x1F429, // POODLE
+ 0x1F42B, // BACTRIAN CAMEL
+ 0x1F42C, // DOLPHIN
+ 0x1F42D, // MOUSE FACE
+ 0x1F42E, // COW FACE
+ 0x1F42F, // TIGER FACE
+ 0x1F430, // RABBIT FACE
+ 0x1F431, // CAT FACE
+ 0x1F432, // DRAGON FACE
+ 0x1F433, // SPOUTING WHALE
+ 0x1F434, // HORSE FACE
+ 0x1F435, // MONKEY FACE
+ 0x1F436, // DOG FACE
+ 0x1F437, // PIG FACE
+ 0x1F438, // FROG FACE
+ 0x1F439, // HAMSTER FACE
+ 0x1F43A, // WOLF FACE
+ 0x1F43B, // BEAR FACE
+ 0x1F43C, // PANDA FACE
+ 0x1F43D, // PIG NOSE
+ 0x1F43E, // PAW PRINTS
+ 0x1F440, // EYES
+ 0x1F442, // EAR
+ 0x1F443, // NOSE
+ 0x1F444, // MOUTH
+ 0x1F445, // TONGUE
+ 0x1F446, // WHITE UP POINTING BACKHAND INDEX
+ 0x1F447, // WHITE DOWN POINTING BACKHAND INDEX
+ 0x1F448, // WHITE LEFT POINTING BACKHAND INDEX
+ 0x1F449, // WHITE RIGHT POINTING BACKHAND INDEX
+ 0x1F44A, // FISTED HAND SIGN
+ 0x1F44B, // WAVING HAND SIGN
+ 0x1F44C, // OK HAND SIGN
+ 0x1F44D, // THUMBS UP SIGN
+ 0x1F44E, // THUMBS DOWN SIGN
+ 0x1F44F, // CLAPPING HANDS SIGN
+ 0x1F450, // OPEN HANDS SIGN
+ 0x1F451, // CROWN
+ 0x1F452, // WOMANS HAT
+ 0x1F453, // EYEGLASSES
+ 0x1F454, // NECKTIE
+ 0x1F455, // T-SHIRT
+ 0x1F456, // JEANS
+ 0x1F457, // DRESS
+ 0x1F458, // KIMONO
+ 0x1F459, // BIKINI
+ 0x1F45A, // WOMANS CLOTHES
+ 0x1F45B, // PURSE
+ 0x1F45C, // HANDBAG
+ 0x1F45D, // POUCH
+ 0x1F45E, // MANS SHOE
+ 0x1F45F, // ATHLETIC SHOE
+ 0x1F460, // HIGH-HEELED SHOE
+ 0x1F461, // WOMANS SANDAL
+ 0x1F462, // WOMANS BOOTS
+ 0x1F463, // FOOTPRINTS
+ 0x1F464, // BUST IN SILHOUETTE
+ 0x1F466, // BOY
+ 0x1F467, // GIRL
+ 0x1F468, // MAN
+ 0x1F469, // WOMAN
+ 0x1F46A, // FAMILY
+ 0x1F46B, // MAN AND WOMAN HOLDING HANDS
+ 0x1F46E, // POLICE OFFICER
+ 0x1F46F, // WOMAN WITH BUNNY EARS
+ 0x1F470, // BRIDE WITH VEIL
+ 0x1F471, // PERSON WITH BLOND HAIR
+ 0x1F472, // MAN WITH GUA PI MAO
+ 0x1F473, // MAN WITH TURBAN
+ 0x1F474, // OLDER MAN
+ 0x1F475, // OLDER WOMAN
+ 0x1F476, // BABY
+ 0x1F477, // CONSTRUCTION WORKER
+ 0x1F478, // PRINCESS
+ 0x1F479, // JAPANESE OGRE
+ 0x1F47A, // JAPANESE GOBLIN
+ 0x1F47B, // GHOST
+ 0x1F47C, // BABY ANGEL
+ 0x1F47D, // EXTRATERRESTRIAL ALIEN
+ 0x1F47E, // ALIEN MONSTER
+ 0x1F47F, // IMP
+ 0x1F480, // SKULL
+ 0x1F481, // INFORMATION DESK PERSON
+ 0x1F482, // GUARDSMAN
+ 0x1F483, // DANCER
+ 0x1F484, // LIPSTICK
+ 0x1F485, // NAIL POLISH
+ 0x1F486, // FACE MASSAGE
+ 0x1F487, // HAIRCUT
+ 0x1F488, // BARBER POLE
+ 0x1F489, // SYRINGE
+ 0x1F48A, // PILL
+ 0x1F48B, // KISS MARK
+ 0x1F48C, // LOVE LETTER
+ 0x1F48D, // RING
+ 0x1F48E, // GEM STONE
+ 0x1F48F, // KISS
+ 0x1F490, // BOUQUET
+ 0x1F491, // COUPLE WITH HEART
+ 0x1F492, // WEDDING
+ 0x1F493, // BEATING HEART
+ 0x1F494, // BROKEN HEART
+ 0x1F495, // TWO HEARTS
+ 0x1F496, // SPARKLING HEART
+ 0x1F497, // GROWING HEART
+ 0x1F498, // HEART WITH ARROW
+ 0x1F499, // BLUE HEART
+ 0x1F49A, // GREEN HEART
+ 0x1F49B, // YELLOW HEART
+ 0x1F49C, // PURPLE HEART
+ 0x1F49D, // HEART WITH RIBBON
+ 0x1F49E, // REVOLVING HEARTS
+ 0x1F49F, // HEART DECORATION
+ 0x1F4A0, // DIAMOND SHAPE WITH A DOT INSIDE
+ 0x1F4A1, // ELECTRIC LIGHT BULB
+ 0x1F4A2, // ANGER SYMBOL
+ 0x1F4A3, // BOMB
+ 0x1F4A4, // SLEEPING SYMBOL
+ 0x1F4A5, // COLLISION SYMBOL
+ 0x1F4A6, // SPLASHING SWEAT SYMBOL
+ 0x1F4A7, // DROPLET
+ 0x1F4A8, // DASH SYMBOL
+ 0x1F4A9, // PILE OF POO
+ 0x1F4AA, // FLEXED BICEPS
+ 0x1F4AB, // DIZZY SYMBOL
+ 0x1F4AC, // SPEECH BALLOON
+ 0x1F4AE, // WHITE FLOWER
+ 0x1F4AF, // HUNDRED POINTS SYMBOL
+ 0x1F4B0, // MONEY BAG
+ 0x1F4B1, // CURRENCY EXCHANGE
+ 0x1F4B2, // HEAVY DOLLAR SIGN
+ 0x1F4B3, // CREDIT CARD
+ 0x1F4B4, // BANKNOTE WITH YEN SIGN
+ 0x1F4B5, // BANKNOTE WITH DOLLAR SIGN
+ 0x1F4B8, // MONEY WITH WINGS
+ 0x1F4B9, // CHART WITH UPWARDS TREND AND YEN SIGN
+ 0x1F4BA, // SEAT
+ 0x1F4BB, // PERSONAL COMPUTER
+ 0x1F4BC, // BRIEFCASE
+ 0x1F4BD, // MINIDISC
+ 0x1F4BE, // FLOPPY DISK
+ 0x1F4BF, // OPTICAL DISC
+ 0x1F4C0, // DVD
+ 0x1F4C1, // FILE FOLDER
+ 0x1F4C2, // OPEN FILE FOLDER
+ 0x1F4C3, // PAGE WITH CURL
+ 0x1F4C4, // PAGE FACING UP
+ 0x1F4C5, // CALENDAR
+ 0x1F4C6, // TEAR-OFF CALENDAR
+ 0x1F4C7, // CARD INDEX
+ 0x1F4C8, // CHART WITH UPWARDS TREND
+ 0x1F4C9, // CHART WITH DOWNWARDS TREND
+ 0x1F4CA, // BAR CHART
+ 0x1F4CB, // CLIPBOARD
+ 0x1F4CC, // PUSHPIN
+ 0x1F4CD, // ROUND PUSHPIN
+ 0x1F4CE, // PAPERCLIP
+ 0x1F4CF, // STRAIGHT RULER
+ 0x1F4D0, // TRIANGULAR RULER
+ 0x1F4D1, // BOOKMARK TABS
+ 0x1F4D2, // LEDGER
+ 0x1F4D3, // NOTEBOOK
+ 0x1F4D4, // NOTEBOOK WITH DECORATIVE COVER
+ 0x1F4D5, // CLOSED BOOK
+ 0x1F4D6, // OPEN BOOK
+ 0x1F4D7, // GREEN BOOK
+ 0x1F4D8, // BLUE BOOK
+ 0x1F4D9, // ORANGE BOOK
+ 0x1F4DA, // BOOKS
+ 0x1F4DB, // NAME BADGE
+ 0x1F4DC, // SCROLL
+ 0x1F4DD, // MEMO
+ 0x1F4DE, // TELEPHONE RECEIVER
+ 0x1F4DF, // PAGER
+ 0x1F4E0, // FAX MACHINE
+ 0x1F4E1, // SATELLITE ANTENNA
+ 0x1F4E2, // PUBLIC ADDRESS LOUDSPEAKER
+ 0x1F4E3, // CHEERING MEGAPHONE
+ 0x1F4E4, // OUTBOX TRAY
+ 0x1F4E5, // INBOX TRAY
+ 0x1F4E6, // PACKAGE
+ 0x1F4E7, // E-MAIL SYMBOL
+ 0x1F4E8, // INCOMING ENVELOPE
+ 0x1F4E9, // ENVELOPE WITH DOWNWARDS ARROW ABOVE
+ 0x1F4EA, // CLOSED MAILBOX WITH LOWERED FLAG
+ 0x1F4EB, // CLOSED MAILBOX WITH RAISED FLAG
+ 0x1F4EE, // POSTBOX
+ 0x1F4F0, // NEWSPAPER
+ 0x1F4F1, // MOBILE PHONE
+ 0x1F4F2, // MOBILE PHONE WITH RIGHTWARDS ARROW AT LEFT
+ 0x1F4F3, // VIBRATION MODE
+ 0x1F4F4, // MOBILE PHONE OFF
+ 0x1F4F6, // ANTENNA WITH BARS
+ 0x1F4F7, // CAMERA
+ 0x1F4F9, // VIDEO CAMERA
+ 0x1F4FA, // TELEVISION
+ 0x1F4FB, // RADIO
+ 0x1F4FC, // VIDEOCASSETTE
+ 0x1F503, // CLOCKWISE DOWNWARDS AND UPWARDS OPEN CIRCLE ARROWS
+ 0x1F50A, // SPEAKER WITH THREE SOUND WAVES
+ 0x1F50B, // BATTERY
+ 0x1F50C, // ELECTRIC PLUG
+ 0x1F50D, // LEFT-POINTING MAGNIFYING GLASS
+ 0x1F50E, // RIGHT-POINTING MAGNIFYING GLASS
+ 0x1F50F, // LOCK WITH INK PEN
+ 0x1F510, // CLOSED LOCK WITH KEY
+ 0x1F511, // KEY
+ 0x1F512, // LOCK
+ 0x1F513, // OPEN LOCK
+ 0x1F514, // BELL
+ 0x1F516, // BOOKMARK
+ 0x1F517, // LINK SYMBOL
+ 0x1F518, // RADIO BUTTON
+ 0x1F53A, // UP-POINTING RED TRIANGLE
+ 0x1F53B, // DOWN-POINTING RED TRIANGLE
+ 0x1F53C, // UP-POINTING SMALL RED TRIANGLE
+ 0x1F53D, // DOWN-POINTING SMALL RED TRIANGLE
+ 0x1F519, // BACK WITH LEFTWARDS ARROW ABOVE
+ 0x1F51A, // END WITH LEFTWARDS ARROW ABOVE
+ 0x1F51B, // ON WITH EXCLAMATION MARK WITH LEFT RIGHT ARROW ABOVE
+ 0x1F51C, // SOON WITH RIGHTWARDS ARROW ABOVE
+ 0x1F51D, // TOP WITH UPWARDS ARROW ABOVE
+ 0x1F51E, // NO ONE UNDER EIGHTEEN SYMBOL
+ 0x1F51F, // KEYCAP TEN
+ 0x1F520, // INPUT SYMBOL FOR LATIN CAPITAL LETTERS
+ 0x1F521, // INPUT SYMBOL FOR LATIN SMALL LETTERS
+ 0x1F522, // INPUT SYMBOL FOR NUMBERS
+ 0x1F523, // INPUT SYMBOL FOR SYMBOLS
+ 0x1F524, // INPUT SYMBOL FOR LATIN LETTERS
+ 0x1F525, // FIRE
+ 0x1F526, // ELECTRIC TORCH
+ 0x1F527, // WRENCH
+ 0x1F528, // HAMMER
+ 0x1F529, // NUT AND BOLT
+ 0x1F52A, // HOCHO
+ 0x1F52B, // PISTOL
+ 0x1F52E, // CRYSTAL BALL
+ 0x1F52F, // SIX POINTED STAR WITH MIDDLE DOT
+ 0x1F530, // JAPANESE SYMBOL FOR BEGINNER
+ 0x1F531, // TRIDENT EMBLEM
+ 0x1F532, // BLACK SQUARE BUTTON
+ 0x1F533, // WHITE SQUARE BUTTON
+ 0x1F534, // LARGE RED CIRCLE
+ 0x1F535, // LARGE BLUE CIRCLE
+ 0x1F536, // LARGE ORANGE DIAMOND
+ 0x1F537, // LARGE BLUE DIAMOND
+ 0x1F538, // SMALL ORANGE DIAMOND
+ 0x1F539, // SMALL BLUE DIAMOND
+ 0x1F550, // CLOCK FACE ONE OCLOCK
+ 0x1F551, // CLOCK FACE TWO OCLOCK
+ 0x1F552, // CLOCK FACE THREE OCLOCK
+ 0x1F553, // CLOCK FACE FOUR OCLOCK
+ 0x1F554, // CLOCK FACE FIVE OCLOCK
+ 0x1F555, // CLOCK FACE SIX OCLOCK
+ 0x1F556, // CLOCK FACE SEVEN OCLOCK
+ 0x1F557, // CLOCK FACE EIGHT OCLOCK
+ 0x1F558, // CLOCK FACE NINE OCLOCK
+ 0x1F559, // CLOCK FACE TEN OCLOCK
+ 0x1F55A, // CLOCK FACE ELEVEN OCLOCK
+ 0x1F55B, // CLOCK FACE TWELVE OCLOCK
+ 0x1F5FB, // MOUNT FUJI
+ 0x1F5FC, // TOKYO TOWER
+ 0x1F5FD, // STATUE OF LIBERTY
+ 0x1F5FE, // SILHOUETTE OF JAPAN
+ 0x1F5FF, // MOYAI
+ 0x1F601, // GRINNING FACE WITH SMILING EYES
+ 0x1F602, // FACE WITH TEARS OF JOY
+ 0x1F603, // SMILING FACE WITH OPEN MOUTH
+ 0x1F604, // SMILING FACE WITH OPEN MOUTH AND SMILING EYES
+ 0x1F605, // SMILING FACE WITH OPEN MOUTH AND COLD SWEAT
+ 0x1F606, // SMILING FACE WITH OPEN MOUTH AND TIGHTLY-CLOSED EYES
+ 0x1F609, // WINKING FACE
+ 0x1F60A, // SMILING FACE WITH SMILING EYES
+ 0x1F60B, // FACE SAVOURING DELICIOUS FOOD
+ 0x1F60C, // RELIEVED FACE
+ 0x1F60D, // SMILING FACE WITH HEART-SHAPED EYES
+ 0x1F60F, // SMIRKING FACE
+ 0x1F612, // UNAMUSED FACE
+ 0x1F613, // FACE WITH COLD SWEAT
+ 0x1F614, // PENSIVE FACE
+ 0x1F616, // CONFOUNDED FACE
+ 0x1F618, // FACE THROWING A KISS
+ 0x1F61A, // KISSING FACE WITH CLOSED EYES
+ 0x1F61C, // FACE WITH STUCK-OUT TONGUE AND WINKING EYE
+ 0x1F61D, // FACE WITH STUCK-OUT TONGUE AND TIGHTLY-CLOSED EYES
+ 0x1F61E, // DISAPPOINTED FACE
+ 0x1F620, // ANGRY FACE
+ 0x1F621, // POUTING FACE
+ 0x1F622, // CRYING FACE
+ 0x1F623, // PERSEVERING FACE
+ 0x1F624, // FACE WITH LOOK OF TRIUMPH
+ 0x1F625, // DISAPPOINTED BUT RELIEVED FACE
+ 0x1F628, // FEARFUL FACE
+ 0x1F629, // WEARY FACE
+ 0x1F62A, // SLEEPY FACE
+ 0x1F62B, // TIRED FACE
+ 0x1F62D, // LOUDLY CRYING FACE
+ 0x1F630, // FACE WITH OPEN MOUTH AND COLD SWEAT
+ 0x1F631, // FACE SCREAMING IN FEAR
+ 0x1F632, // ASTONISHED FACE
+ 0x1F633, // FLUSHED FACE
+ 0x1F635, // DIZZY FACE
+ 0x1F637, // FACE WITH MEDICAL MASK
+ 0x1F638, // GRINNING CAT FACE WITH SMILING EYES
+ 0x1F639, // CAT FACE WITH TEARS OF JOY
+ 0x1F63A, // SMILING CAT FACE WITH OPEN MOUTH
+ 0x1F63B, // SMILING CAT FACE WITH HEART-SHAPED EYES
+ 0x1F63C, // CAT FACE WITH WRY SMILE
+ 0x1F63D, // KISSING CAT FACE WITH CLOSED EYES
+ 0x1F63E, // POUTING CAT FACE
+ 0x1F63F, // CRYING CAT FACE
+ 0x1F640, // WEARY CAT FACE
+ 0x1F645, // FACE WITH NO GOOD GESTURE
+ 0x1F646, // FACE WITH OK GESTURE
+ 0x1F647, // PERSON BOWING DEEPLY
+ 0x1F648, // SEE-NO-EVIL MONKEY
+ 0x1F649, // HEAR-NO-EVIL MONKEY
+ 0x1F64A, // SPEAK-NO-EVIL MONKEY
+ 0x1F64B, // HAPPY PERSON RAISING ONE HAND
+ 0x1F64C, // PERSON RAISING BOTH HANDS IN CELEBRATION
+ 0x1F64D, // PERSON FROWNING
+ 0x1F64E, // PERSON WITH POUTING FACE
+ 0x1F64F, // PERSON WITH FOLDED HANDS
+ 0x1F680, // ROCKET
+ 0x1F683, // RAILWAY CAR
+ 0x1F684, // HIGH-SPEED TRAIN
+ 0x1F685, // HIGH-SPEED TRAIN WITH BULLET NOSE
+ 0x1F687, // METRO
+ 0x1F689, // STATION
+ 0x1F68C, // BUS
+ 0x1F68F, // BUS STOP
+ 0x1F691, // AMBULANCE
+ 0x1F692, // FIRE ENGINE
+ 0x1F693, // POLICE CAR
+ 0x1F695, // TAXI
+ 0x1F697, // AUTOMOBILE
+ 0x1F699, // RECREATIONAL VEHICLE
+ 0x1F69A, // DELIVERY TRUCK
+ 0x1F6A2, // SHIP
+ 0x1F6A4, // SPEEDBOAT
+ 0x1F6A5, // HORIZONTAL TRAFFIC LIGHT
+ 0x1F6A7, // CONSTRUCTION SIGN
+ 0x1F6A8, // POLICE CARS REVOLVING LIGHT
+ 0x1F6A9, // TRIANGULAR FLAG ON POST
+ 0x1F6AA, // DOOR
+ 0x1F6AB, // NO ENTRY SIGN
+ 0x1F6AC, // SMOKING SYMBOL
+ 0x1F6AD, // NO SMOKING SYMBOL
+ 0x1F6B2, // BICYCLE
+ 0x1F6B6, // PEDESTRIAN
+ 0x1F6B9, // MENS SYMBOL
+ 0x1F6BA, // WOMENS SYMBOL
+ 0x1F6BB, // RESTROOM
+ 0x1F6BC, // BABY SYMBOL
+ 0x1F6BD, // TOILET
+ 0x1F6BE, // WATER CLOSET
+ 0x1F6C0 // BAT
+ };
+}
diff --git a/tests/tests/text/src/android/text/cts/EmojiTest.java b/tests/tests/text/src/android/text/cts/EmojiTest.java
new file mode 100755
index 0000000..867cb8d
--- /dev/null
+++ b/tests/tests/text/src/android/text/cts/EmojiTest.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text.cts;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Picture;
+import android.test.ActivityInstrumentationTestCase2;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.View;
+import android.webkit.WebView;
+import android.widget.TextView;
+import android.widget.EditText;
+
+public class EmojiTest extends ActivityInstrumentationTestCase2<EmojiStubActivity> {
+
+ public EmojiTest() {
+ super("com.android.cts.stub", EmojiStubActivity.class);
+ }
+
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ /**
+ * Tests all Emoji are defined in Character class
+ */
+ public void testEmojiCodePoints() {
+ for (int i = 0; i < EmojiConstants.emojiCodePoints.length; i++) {
+ assertTrue(Character.isDefined(EmojiConstants.emojiCodePoints[i]));
+ }
+ }
+
+ /**
+ * Tests Emoji has different glyph for different meaning characters.
+ * Test on Canvas, TextView, EditText and WebView
+ */
+ public void testEmojiGlyph() {
+ CaptureCanvas ccanvas = new CaptureCanvas(getInstrumentation().getContext());
+ CaptureWebView cwebview = new CaptureWebView(getInstrumentation().getContext());
+
+ Bitmap mBitmapA, mBitmapB; // Emoji displayed Bitmaps to compare
+
+ int comparedCodePoints[][] = { // Emojis should have different characters
+ {0x1F436, 0x1F435}, // Dog(U+1F436) and Monkey(U+1F435)
+ {0x26BD, 0x26BE}, // Soccer ball(U+26BD) and Baseball(U+26BE)
+ {0x1F47B, 0x1F381}, // Ghost(U+1F47B) and wrapped present(U+1F381)
+ {0x2764, 0x1F494}, // Heavy black heart(U+2764) and broken heart(U+1F494)
+ {0x1F603, 0x1F33B} // Smiling face with open mouth(U+1F603) and sunflower(U+1F33B)
+ };
+
+ for (int i = 0; i < comparedCodePoints.length; i++) {
+
+ mBitmapA = ccanvas.capture(Character.toChars(comparedCodePoints[i][0]));
+ mBitmapB = ccanvas.capture(Character.toChars(comparedCodePoints[i][1]));
+
+ assertFalse(mBitmapA.sameAs(mBitmapB));
+
+ // cannot reuse CaptureTextView as 2nd setText call throws NullPointerException
+ CaptureTextView cviewA = new CaptureTextView(getInstrumentation().getContext());
+ mBitmapA = cviewA.capture(Character.toChars(comparedCodePoints[i][0]));
+ CaptureTextView cviewB = new CaptureTextView(getInstrumentation().getContext());
+ mBitmapB = cviewB.capture(Character.toChars(comparedCodePoints[i][1]));
+
+ assertFalse(mBitmapA.sameAs(mBitmapB));
+
+ CaptureEditText cedittextA = new CaptureEditText(getInstrumentation().getContext());
+ mBitmapA = cedittextA.capture(Character.toChars(comparedCodePoints[i][0]));
+ CaptureEditText cedittextB = new CaptureEditText(getInstrumentation().getContext());
+ mBitmapB = cedittextB.capture(Character.toChars(comparedCodePoints[i][1]));
+
+ assertFalse(mBitmapA.sameAs(mBitmapB));
+
+ mBitmapA = cwebview.capture(Character.toChars(comparedCodePoints[i][0]));
+ mBitmapB = cwebview.capture(Character.toChars(comparedCodePoints[i][1]));
+
+ assertFalse(mBitmapA.sameAs(mBitmapB));
+
+ }
+ }
+
+ /**
+ * Tests EditText handles Emoji
+ */
+ public void testEmojiEditable() {
+ int testedCodePoints[] = {
+ 0xAE, // registered mark
+ 0x2764, // heavy black heart
+ 0x1F353 // strawberry - surrogate pair sample. Count as two characters.
+ };
+
+ String origStr, newStr;
+
+ // delete Emoji by sending KEYCODE_DEL
+ for (int i = 0; i < testedCodePoints.length; i++) {
+ origStr = "Test character ";
+ // cannot reuse CaptureTextView as 2nd setText call throws NullPointerException
+ EditText editText = new EditText(getInstrumentation().getContext());
+ editText.setText(origStr + String.valueOf(Character.toChars(testedCodePoints[i])));
+
+ // confirm the emoji is added.
+ newStr = editText.getText().toString();
+ assertEquals(newStr.codePointCount(0, newStr.length()), origStr.length() + 1);
+
+ // Delete added character by sending KEYCODE_DEL event
+ editText.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL));
+
+ newStr = editText.getText().toString();
+ assertEquals(newStr.codePointCount(0, newStr.length()), origStr.length() + 1);
+ }
+ }
+
+ private class CaptureCanvas extends View {
+
+ String mTestStr;
+ Paint paint = new Paint();
+
+ CaptureCanvas(Context context) {
+ super(context);
+ }
+
+ public void onDraw(Canvas canvas) {
+ if (mTestStr != null) {
+ canvas.drawText(mTestStr, 50, 50, paint);
+ }
+ return;
+ }
+
+ Bitmap capture(char c[]) {
+ mTestStr = String.valueOf(c);
+ invalidate();
+
+ setDrawingCacheEnabled(true);
+ measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
+ MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+ layout(0, 0, 200,200);
+
+ Bitmap bitmap = Bitmap.createBitmap(getDrawingCache());
+ setDrawingCacheEnabled(false);
+ return bitmap;
+ }
+
+ }
+
+ private class CaptureTextView extends TextView {
+
+ CaptureTextView(Context context) {
+ super(context);
+ }
+
+ Bitmap capture(char c[]) {
+ setText(String.valueOf(c));
+
+ invalidate();
+
+ setDrawingCacheEnabled(true);
+ measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
+ MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+ layout(0, 0, 200,200);
+
+ Bitmap bitmap = Bitmap.createBitmap(getDrawingCache());
+ setDrawingCacheEnabled(false);
+ return bitmap;
+ }
+
+ }
+
+ private class CaptureEditText extends EditText {
+
+ CaptureEditText(Context context) {
+ super(context);
+ }
+
+ Bitmap capture(char c[]) {
+ setText(String.valueOf(c));
+
+ invalidate();
+
+ setDrawingCacheEnabled(true);
+ measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
+ MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+ layout(0, 0, 200,200);
+
+ Bitmap bitmap = Bitmap.createBitmap(getDrawingCache());
+ setDrawingCacheEnabled(false);
+ return bitmap;
+ }
+
+ }
+
+
+ private class CaptureWebView {
+
+ WebView view;
+ Bitmap bitmap;
+ CaptureWebView(Context context) {
+ view = getActivity().getWebView();
+ }
+
+ Bitmap capture(char c[]) {
+
+ view.loadData("<html><body>" + String.valueOf(c) + "</body></html>",
+ "text/html; charset=utf-8", "utf-8");
+
+ try {
+ Thread.sleep(200);
+ } catch (InterruptedException ie) {
+ return null;
+ }
+
+ Picture picture = view.capturePicture();
+ if (picture == null || picture.getHeight() <= 0 || picture.getWidth() <= 0) {
+ return null;
+ } else {
+ bitmap = Bitmap.createBitmap(picture.getWidth(), picture.getHeight(),
+ Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ picture.draw(canvas);
+ }
+
+ return bitmap;
+ }
+
+ }
+
+}
+
diff --git a/tests/tests/text/src/android/text/cts/StaticLayoutLineBreakingTest.java b/tests/tests/text/src/android/text/cts/StaticLayoutLineBreakingTest.java
index 3aa8f35..f702c06 100644
--- a/tests/tests/text/src/android/text/cts/StaticLayoutLineBreakingTest.java
+++ b/tests/tests/text/src/android/text/cts/StaticLayoutLineBreakingTest.java
@@ -48,18 +48,14 @@
@Override
public float getTextRunAdvances(char[] chars, int index, int count,
- int contextIndex, int contextCount, int flags, float[] advances,
- int advancesIndex, int reserved) {
+ int contextIndex, int contextCount, float[] advances,
+ int advancesIndex) {
// Conditions copy pasted from Paint
if (chars == null) {
throw new IllegalArgumentException("text cannot be null");
}
- if (flags != DIRECTION_LTR && flags != DIRECTION_RTL) {
- throw new IllegalArgumentException("unknown flags value: " + flags);
- }
-
if ((index | count | contextIndex | contextCount | advancesIndex
| (index - contextIndex) | (contextCount - count)
| ((contextIndex + contextCount) - (index + count))
diff --git a/tests/tests/text/src/android/text/method/cts/DigitsKeyListenerTest.java b/tests/tests/text/src/android/text/method/cts/DigitsKeyListenerTest.java
index 0a1b5ae..ea50294 100644
--- a/tests/tests/text/src/android/text/method/cts/DigitsKeyListenerTest.java
+++ b/tests/tests/text/src/android/text/method/cts/DigitsKeyListenerTest.java
@@ -64,8 +64,9 @@
* 1. filter "123456", return null.
* 2. filter "a1b2c3d", return "123"
* 3. filter "-a1.b2c3d", return "123"
- * 4. filter Spanned("-a1.b2c3d"), return Spanned("123") and copy spans.
- * 5. filter "", return null
+ * 4. filter "+a1.b2c3d", return "123"
+ * 5. filter Spanned("-a1.b2c3d"), return Spanned("123") and copy spans.
+ * 6. filter "", return null
*/
public void testFilter1() {
String source = "123456";
@@ -87,6 +88,11 @@
dest, 0, dest.length())).toString());
assertEquals(destString, dest.toString());
+ source = "+a1.b2c3d";
+ assertEquals("123", (digitsKeyListener.filter(source, 0, source.length(),
+ dest, 0, dest.length())).toString());
+ assertEquals(destString, dest.toString());
+
Object what = new Object();
Spannable spannableSource = new SpannableString(source);
spannableSource.setSpan(what, 0, spannableSource.length(), Spanned.SPAN_POINT_POINT);
@@ -103,15 +109,21 @@
/**
* Check point:
- * Current accepted characters are '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-'.
+ * Current accepted characters are '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '+'.
* 1. filter "-123456", return null
- * 2. filter "-a1.b2c3d", return "-123"
- * 3. filter "-a1-b2c3d", return "-123"
- * 4. filter "5-a1-b2c3d", return "5123"
- * 5. filter Spanned("5-a1-b2c3d"), return Spanned("5123") and copy spans.
- * 6. filter "", return null
- * 7. filter "-123456" but dest has '-' after dend, return ""
- * 8. filter "-123456" but dest has '-' before dstart, return "123456"
+ * 2. filter "+123456", return null
+ * 3. filter "-a1.b2c3d", return "-123"
+ * 4. filter "-a1-b2c3d", return "-123"
+ * 5. filter "+a1-b2c3d", return "+123"
+ * 6. filter "5-a1-b2c3d", return "5123"
+ * 7. filter "5-a1+b2c3d", return "5123"
+ * 8. filter "+5-a1+b2c3d", return "+5123"
+ * 9. filter Spanned("5-a1-b2c3d"), return Spanned("5123") and copy spans.
+ * 10. filter "", return null
+ * 11. filter "-123456" but dest has '-' after dend, return ""
+ * 12. filter "-123456" but dest has '+' after dend, return ""
+ * 13. filter "-123456" but dest has '-' before dstart, return "123456"
+ * 14. filter "+123456" but dest has '-' before dstart, return "123456"
*/
public void testFilter2() {
String source = "-123456";
@@ -123,6 +135,11 @@
dest, 0, dest.length()));
assertEquals(destString, dest.toString());
+ source = "+123456";
+ assertNull(digitsKeyListener.filter(source, 0, source.length(),
+ dest, 0, dest.length()));
+ assertEquals(destString, dest.toString());
+
source = "-a1.b2c3d";
assertEquals("-123", (digitsKeyListener.filter(source, 0, source.length(),
dest, 0, dest.length())).toString());
@@ -133,11 +150,27 @@
dest, 0, dest.length())).toString());
assertEquals(destString, dest.toString());
+ source = "+a1-b2c3d";
+ assertEquals("+123", (digitsKeyListener.filter(source, 0, source.length(),
+ dest, 0, dest.length())).toString());
+ assertEquals(destString, dest.toString());
+
source = "5-a1-b2c3d";
assertEquals("5123", (digitsKeyListener.filter(source, 0, source.length(),
dest, 0, dest.length())).toString());
assertEquals(destString, dest.toString());
+ source = "5-a1+b2c3d";
+ assertEquals("5123", (digitsKeyListener.filter(source, 0, source.length(),
+ dest, 0, dest.length())).toString());
+ assertEquals(destString, dest.toString());
+
+ source = "+5-a1+b2c3d";
+ assertEquals("+5123", (digitsKeyListener.filter(source, 0, source.length(),
+ dest, 0, dest.length())).toString());
+ assertEquals(destString, dest.toString());
+
+ source = "5-a1+b2c3d";
Object what = new Object();
Spannable spannableSource = new SpannableString(source);
spannableSource.setSpan(what, 0, spannableSource.length(), Spanned.SPAN_POINT_POINT);
@@ -158,11 +191,23 @@
dest, 0, dest.length() - 1)).toString());
assertEquals(endSign, dest.toString());
+ endSign = "789+";
+ dest = new SpannableString(endSign);
+ assertEquals("", (digitsKeyListener.filter(source, 0, source.length(),
+ dest, 0, dest.length() - 1)).toString());
+ assertEquals(endSign, dest.toString());
+
String startSign = "-789";
dest = new SpannableString(startSign);
assertEquals("123456", (digitsKeyListener.filter(source, 0, source.length(),
dest, 1, dest.length())).toString());
assertEquals(startSign, dest.toString());
+
+ source = "+123456";
+ dest = new SpannableString(startSign);
+ assertEquals("123456", (digitsKeyListener.filter(source, 0, source.length(),
+ dest, 1, dest.length())).toString());
+ assertEquals(startSign, dest.toString());
}
/**
@@ -170,12 +215,13 @@
* Current accepted characters are '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.'.
* 1. filter "123.456", return null
* 2. filter "-a1.b2c3d", return "1.23"
- * 3. filter "a1.b2c3d.", return "123."
- * 4. filter "5.a1.b2c3d", return "51.23"
- * 5. filter Spanned("5.a1.b2c3d"), return Spanned("51.23") and copy spans.
- * 6. filter "", return null
- * 7. filter "123.456" but dest has '.' after dend, return "123456"
- * 8. filter "123.456" but dest has '.' before dstart, return "123456"
+ * 3. filter "+a1.b2c3d", return "1.23"
+ * 4. filter "a1.b2c3d.", return "123."
+ * 5. filter "5.a1.b2c3d", return "51.23"
+ * 6. filter Spanned("5.a1.b2c3d"), return Spanned("51.23") and copy spans.
+ * 7. filter "", return null
+ * 8. filter "123.456" but dest has '.' after dend, return "123456"
+ * 9. filter "123.456" but dest has '.' before dstart, return "123456"
*/
public void testFilter3() {
String source = "123.456";
@@ -192,6 +238,11 @@
dest, 0, dest.length())).toString());
assertEquals(destString, dest.toString());
+ source = "+a1.b2c3d";
+ assertEquals("1.23", (digitsKeyListener.filter(source, 0, source.length(),
+ dest, 0, dest.length())).toString());
+ assertEquals(destString, dest.toString());
+
source = "a1.b2c3d.";
assertEquals("123.", (digitsKeyListener.filter(source, 0, source.length(),
dest, 0, dest.length())).toString());
@@ -231,17 +282,25 @@
/**
* Check point:
- * Current accepted characters are '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '-'.
+ * Current accepted characters are '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '-',
+ * '+'.
* 1. filter "-123.456", return null
- * 2. filter "-a1.b2c3d", return "-1.23"
- * 3. filter "a1.b-2c3d.", return "123."
- * 4. filter "-5.a1.b2c3d", return "-51.23"
- * 5. filter Spanned("-5.a1.b2c3d"), return Spanned("-51.23") and copy spans.
- * 6. filter "", return null
- * 7. filter "-123.456" but dest has '.' after dend, return "-123456"
- * 8. filter "-123.456" but dest has '.' before dstart, return "123456"
- * 9. filter "-123.456" but dest has '-' after dend, return ""
- * 10. filter "-123.456" but dest has '-' before dstart, return "123.456"
+ * 2. filter "+123.456", return null
+ * 3. filter "-a1.b2c3d", return "-1.23"
+ * 4. filter "+a1.b2c3d", return "+1.23"
+ * 5. filter "a1.b-2c+3d.", return "123."
+ * 6. filter "-5.a1.b2c+3d", return "-51.23"
+ * 7. filter "+5.a1.b2c-3d", return "+51.23"
+ * 8. filter Spanned("-5.a1.b2c3d"), return Spanned("-51.23") and copy spans.
+ * 9. filter "", return null
+ * 10. filter "-123.456" but dest has '.' after dend, return "-123456"
+ * 11. filter "-123.456" but dest has '.' before dstart, return "123456"
+ * 12. filter "+123.456" but dest has '.' after dend, return "+123456"
+ * 13. filter "+123.456" but dest has '.' before dstart, return "123456"
+ * 14. filter "-123.456" but dest has '-' after dend, return ""
+ * 15. filter "-123.456" but dest has '+' after dend, return ""
+ * 16. filter "-123.456" but dest has '-' before dstart, return "123.456"
+ * 17. filter "+123.456" but dest has '-' before dstart, return "123.456"
*/
public void testFilter4() {
String source = "-123.456";
@@ -253,21 +312,32 @@
dest, 0, dest.length()));
assertEquals(destString, dest.toString());
+ source = "+123.456";
+ assertNull(digitsKeyListener.filter(source, 0, source.length(),
+ dest, 0, dest.length()));
+ assertEquals(destString, dest.toString());
+
source = "-a1.b2c3d";
assertEquals("-1.23", (digitsKeyListener.filter(source, 0, source.length(),
dest, 0, dest.length())).toString());
assertEquals(destString, dest.toString());
- source = "a1.b-2c3d.";
+ source = "a1.b-2c+3d.";
assertEquals("123.", (digitsKeyListener.filter(source, 0, source.length(),
dest, 0, dest.length())).toString());
assertEquals(destString, dest.toString());
- source = "-5.a1.b2c3d";
+ source = "-5.a1.b2c+3d";
assertEquals("-51.23", (digitsKeyListener.filter(source, 0, source.length(),
dest, 0, dest.length())).toString());
assertEquals(destString, dest.toString());
+ source = "+5.a1.b2c-3d";
+ assertEquals("+51.23", (digitsKeyListener.filter(source, 0, source.length(),
+ dest, 0, dest.length())).toString());
+ assertEquals(destString, dest.toString());
+
+ source = "-5.a1.b2c+3d";
Object what = new Object();
Spannable spannableSource = new SpannableString(source);
spannableSource.setSpan(what, 0, spannableSource.length(), Spanned.SPAN_POINT_POINT);
@@ -294,17 +364,43 @@
dest, 1, dest.length())).toString());
assertEquals(startDecimal, dest.toString());
+ source = "+123.456";
+ endDecimal = "789.";
+ dest = new SpannableString(endDecimal);
+ assertEquals("+123456", (digitsKeyListener.filter(source, 0, source.length(),
+ dest, 0, dest.length() - 1)).toString());
+ assertEquals(endDecimal, dest.toString());
+
+ startDecimal = ".789";
+ dest = new SpannableString(startDecimal);
+ assertEquals("123456", (digitsKeyListener.filter(source, 0, source.length(),
+ dest, 1, dest.length())).toString());
+ assertEquals(startDecimal, dest.toString());
+
+ source = "-123.456";
String endSign = "789-";
dest = new SpannableString(endSign);
assertEquals("", (digitsKeyListener.filter(source, 0, source.length(),
dest, 0, dest.length() - 1)).toString());
assertEquals(endSign, dest.toString());
+ endSign = "789+";
+ dest = new SpannableString(endSign);
+ assertEquals("", (digitsKeyListener.filter(source, 0, source.length(),
+ dest, 0, dest.length() - 1)).toString());
+ assertEquals(endSign, dest.toString());
+
String startSign = "-789";
dest = new SpannableString(startSign);
assertEquals("123.456", (digitsKeyListener.filter(source, 0, source.length(),
dest, 1, dest.length())).toString());
assertEquals(startSign, dest.toString());
+
+ source = "+123.456";
+ dest = new SpannableString(startSign);
+ assertEquals("123.456", (digitsKeyListener.filter(source, 0, source.length(),
+ dest, 1, dest.length())).toString());
+ assertEquals(startSign, dest.toString());
}
/**
@@ -346,12 +442,13 @@
/**
* Scenario description:
- * Current accepted characters are '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-'.
+ * Current accepted characters are '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '+'.
* 1. Press '-' key and check if the content of TextView becomes "-"
* 2. Press '1' key and check if the content of TextView becomes "-1"
* 3. Press '.' key and this key could not be accepted.
- * 4. Press '2' key and check if the content of TextView becomes "-12"
- * 5. Press '-' key and this key could not be accepted,
+ * 4. Press '+' key and this key could not be accepted.
+ * 5. Press '2' key and check if the content of TextView becomes "-12"
+ * 6. Press '-' key and this key could not be accepted,
* because text view accepts minus sign iff it at the beginning.
*/
public void testDigitsKeyListener2() {
@@ -378,6 +475,10 @@
sendKeys(KeyEvent.KEYCODE_PERIOD);
assertEquals("-1", mTextView.getText().toString());
+ // press '+' key.
+ sendKeys(KeyEvent.KEYCODE_PLUS);
+ assertEquals("-1", mTextView.getText().toString());
+
// press '2' key.
sendKeys(KeyEvent.KEYCODE_2);
assertEquals("-12", mTextView.getText().toString());
@@ -391,92 +492,98 @@
* Scenario description:
* Current accepted characters are '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.'.
* 1. Press '-' key and check if the content of TextView becomes ""
- * 2. Press '1' key and check if the content of TextView becomes "1"
- * 3. Press '.' key and check if the content of TextView becomes "1."
- * 4. Press '2' key and check if the content of TextView becomes "1.2"
- * 5. Press '.' key and this key could not be accepted,
+ * 2. Press '+' key and check if the content of TextView becomes ""
+ * 3. Press '1' key and check if the content of TextView becomes "1"
+ * 4. Press '.' key and check if the content of TextView becomes "1."
+ * 5. Press '2' key and check if the content of TextView becomes "1.2"
+ * 6. Press '.' key and this key could not be accepted,
* because text view accepts only one decimal point per field.
*/
public void testDigitsKeyListener3() {
-// final DigitsKeyListener digitsKeyListener = DigitsKeyListener.getInstance(false, true);
-//
-// mActivity.runOnUiThread(new Runnable() {
-// public void run() {
-// mTextView.setKeyListener(digitsKeyListener);
-// mTextView.requestFocus();
-// }
-// });
-// mInstrumentation.waitForIdleSync();
-// assertEquals("", mTextView.getText().toString());
-//
-// // press '-' key.
-// sendKeys(KeyEvent.KEYCODE_MINUS);
-// assertEquals("", mTextView.getText().toString());
-//
-// // press '1' key.
-// sendKeys(KeyEvent.KEYCODE_1);
-// assertEquals("1", mTextView.getText().toString());
-//
-// // press '.' key.
-// sendKeys(KeyEvent.KEYCODE_PERIOD);
-// assertEquals("1.", mTextView.getText().toString());
-//
-// // press '2' key.
-// sendKeys(KeyEvent.KEYCODE_2);
-// assertEquals("1.2", mTextView.getText().toString());
-//
-// // press '.' key.
-// sendKeys(KeyEvent.KEYCODE_PERIOD);
-// assertEquals("1.2", mTextView.getText().toString());
+ final DigitsKeyListener digitsKeyListener = DigitsKeyListener.getInstance(false, true);
+
+ mActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ mTextView.setKeyListener(digitsKeyListener);
+ mTextView.requestFocus();
+ }
+ });
+ mInstrumentation.waitForIdleSync();
+ assertEquals("", mTextView.getText().toString());
+
+ // press '-' key.
+ sendKeys(KeyEvent.KEYCODE_MINUS);
+ assertEquals("", mTextView.getText().toString());
+
+ // press '+' key.
+ sendKeys(KeyEvent.KEYCODE_PLUS);
+ assertEquals("", mTextView.getText().toString());
+
+ // press '1' key.
+ sendKeys(KeyEvent.KEYCODE_1);
+ assertEquals("1", mTextView.getText().toString());
+
+ // press '.' key.
+ sendKeys(KeyEvent.KEYCODE_PERIOD);
+ assertEquals("1.", mTextView.getText().toString());
+
+ // press '2' key.
+ sendKeys(KeyEvent.KEYCODE_2);
+ assertEquals("1.2", mTextView.getText().toString());
+
+ // press '.' key.
+ sendKeys(KeyEvent.KEYCODE_PERIOD);
+ assertEquals("1.2", mTextView.getText().toString());
}
/**
* Scenario description:
- * Current accepted characters are '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '.'.
- * 1. Press '-' key and check if the content of TextView becomes "-"
- * 2. Press '1' key and check if the content of TextView becomes "-1"
+ * Current accepted characters are '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '+',
+ * '.'.
+ * 1. Press '+' key and check if the content of TextView becomes "+"
+ * 2. Press '1' key and check if the content of TextView becomes "+1"
* 3. Press '.' key and this key could not be accepted.
- * 4. Press '2' key and check if the content of TextView becomes "-12"
+ * 4. Press '2' key and check if the content of TextView becomes "+12"
* 5. Press '-' key and this key could not be accepted,
* because text view accepts minus sign iff it at the beginning.
* 6. Press '.' key and this key could not be accepted,
* because text view accepts only one decimal point per field.
*/
public void testDigitsKeyListener4() {
-// final DigitsKeyListener digitsKeyListener = DigitsKeyListener.getInstance(true, true);
-//
-// mActivity.runOnUiThread(new Runnable() {
-// public void run() {
-// mTextView.setKeyListener(digitsKeyListener);
-// mTextView.requestFocus();
-// }
-// });
-// mInstrumentation.waitForIdleSync();
-// assertEquals("", mTextView.getText().toString());
-//
-// // press '-' key.
-// sendKeys(KeyEvent.KEYCODE_MINUS);
-// assertEquals("-", mTextView.getText().toString());
-//
-// // press '1' key.
-// sendKeys(KeyEvent.KEYCODE_1);
-// assertEquals("-1", mTextView.getText().toString());
-//
-// // press '.' key.
-// sendKeys(KeyEvent.KEYCODE_PERIOD);
-// assertEquals("-1.", mTextView.getText().toString());
-//
-// // press '2' key.
-// sendKeys(KeyEvent.KEYCODE_2);
-// assertEquals("-1.2", mTextView.getText().toString());
-//
-// // press '-' key.
-// sendKeys(KeyEvent.KEYCODE_MINUS);
-// assertEquals("-1.2", mTextView.getText().toString());
-//
-// // press '.' key.
-// sendKeys(KeyEvent.KEYCODE_PERIOD);
-// assertEquals("-1.2", mTextView.getText().toString());
+ final DigitsKeyListener digitsKeyListener = DigitsKeyListener.getInstance(true, true);
+
+ mActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ mTextView.setKeyListener(digitsKeyListener);
+ mTextView.requestFocus();
+ }
+ });
+ mInstrumentation.waitForIdleSync();
+ assertEquals("", mTextView.getText().toString());
+
+ // press '+' key.
+ sendKeys(KeyEvent.KEYCODE_PLUS);
+ assertEquals("+", mTextView.getText().toString());
+
+ // press '1' key.
+ sendKeys(KeyEvent.KEYCODE_1);
+ assertEquals("+1", mTextView.getText().toString());
+
+ // press '.' key.
+ sendKeys(KeyEvent.KEYCODE_PERIOD);
+ assertEquals("+1.", mTextView.getText().toString());
+
+ // press '2' key.
+ sendKeys(KeyEvent.KEYCODE_2);
+ assertEquals("+1.2", mTextView.getText().toString());
+
+ // press '-' key.
+ sendKeys(KeyEvent.KEYCODE_MINUS);
+ assertEquals("+1.2", mTextView.getText().toString());
+
+ // press '.' key.
+ sendKeys(KeyEvent.KEYCODE_PERIOD);
+ assertEquals("+1.2", mTextView.getText().toString());
}
/**
@@ -570,9 +677,9 @@
final char[][] expected = new char[][] {
new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' },
- new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-' },
+ new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '+' },
new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.' },
- new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '.' },
+ new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '+', '.' },
};
TextMethodUtils.assertEquals(expected[0],
diff --git a/tests/tests/view/src/android/view/cts/DisplayTest.java b/tests/tests/view/src/android/view/cts/DisplayTest.java
deleted file mode 100644
index 489282a..0000000
--- a/tests/tests/view/src/android/view/cts/DisplayTest.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view.cts;
-
-import android.content.Context;
-import android.test.AndroidTestCase;
-import android.util.DisplayMetrics;
-import android.view.Display;
-import android.view.WindowManager;
-
-public class DisplayTest extends AndroidTestCase {
-
- /**
- * Test the properties of Display, they are:
- * 1 index of this display
- * 2 height of this display in pixels
- * 3 width of this display in pixels
- * 4 orientation of this display
- * 5 pixel format of this display
- * 6 refresh rate of this display in frames per second
- * 7 Initialize a DisplayMetrics object from this display's data
- */
- public void testGetDisplayAttrs() {
- Context con = getContext();
- WindowManager windowManager = (WindowManager) con.getSystemService(Context.WINDOW_SERVICE);
- Display display = windowManager.getDefaultDisplay();
-
- assertEquals(Display.DEFAULT_DISPLAY, display.getDisplayId());
- assertTrue(0 < display.getHeight());
- assertTrue(0 < display.getWidth());
- display.getOrientation();
- assertTrue(0 < display.getPixelFormat());
- assertTrue(0 < display.getRefreshRate());
-
- DisplayMetrics outMetrics = new DisplayMetrics();
- outMetrics.setToDefaults();
- display.getMetrics(outMetrics);
- assertEquals(display.getHeight(), outMetrics.heightPixels);
- assertEquals(display.getWidth(), outMetrics.widthPixels);
-
- // The scale is in [0.1, 3], and density is the scale factor.
- assertTrue(0.1f <= outMetrics.density && outMetrics.density <= 3.0f);
- assertTrue(0.1f <= outMetrics.scaledDensity && outMetrics.density <= 3.0f);
- assertTrue(0 < outMetrics.xdpi);
- assertTrue(0 < outMetrics.ydpi);
- }
-}
diff --git a/tests/tests/view/src/android/view/cts/ViewTreeObserverTest.java b/tests/tests/view/src/android/view/cts/ViewTreeObserverTest.java
index c5b56a4..1ee690b 100644
--- a/tests/tests/view/src/android/view/cts/ViewTreeObserverTest.java
+++ b/tests/tests/view/src/android/view/cts/ViewTreeObserverTest.java
@@ -35,7 +35,6 @@
import android.view.ViewTreeObserver.OnTouchModeChangeListener;
import android.widget.Button;
import android.widget.LinearLayout;
-import android.widget.ListView;
import android.widget.ScrollView;
public class ViewTreeObserverTest extends ActivityInstrumentationTestCase2<MockActivity> {
@@ -69,8 +68,15 @@
public void testAddOnGlobalFocusChangeListener() {
final LinearLayout layout = (LinearLayout) mActivity.findViewById(R.id.linearlayout);
- final ListView lv1 = (ListView) mActivity.findViewById(R.id.listview1);
- final ListView lv2 = (ListView) mActivity.findViewById(R.id.listview2);
+ final View view1 = mActivity.findViewById(R.id.view1);
+ final View view2 = mActivity.findViewById(R.id.view2);
+
+ mInstrumentation.runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ view1.requestFocus();
+ }
+ });
mViewTreeObserver = layout.getViewTreeObserver();
final MockOnGlobalFocusChangeListener listener = new MockOnGlobalFocusChangeListener();
@@ -80,14 +86,15 @@
mInstrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
- layout.requestChildFocus(lv1, lv2);
+ view2.requestFocus();
}
});
mInstrumentation.waitForIdleSync();
new PollingCheck() {
@Override
protected boolean check() {
- return listener.hasCalledOnGlobalFocusChanged();
+ return listener.hasCalledOnGlobalFocusChanged()
+ && listener.oldFocus == view1 && listener.newFocus == view2;
}
}.run();
}
@@ -144,16 +151,16 @@
}
public void testAddOnComputeInternalInsetsListener() {
- final ListView lv1 = (ListView) mActivity.findViewById(R.id.listview1);
- mViewTreeObserver = lv1.getViewTreeObserver();
+ final View view1 = mActivity.findViewById(R.id.view1);
+ mViewTreeObserver = view1.getViewTreeObserver();
MockOnComputeInternalInsetsListener listener = new MockOnComputeInternalInsetsListener();
mViewTreeObserver.addOnComputeInternalInsetsListener(listener);
}
public void testRemoveOnComputeInternalInsetsListener() {
- final ListView lv1 = (ListView) mActivity.findViewById(R.id.listview1);
- mViewTreeObserver = lv1.getViewTreeObserver();
+ final View view1 = mActivity.findViewById(R.id.view1);
+ mViewTreeObserver = view1.getViewTreeObserver();
MockOnComputeInternalInsetsListener listener = new MockOnComputeInternalInsetsListener();
mViewTreeObserver.removeOnComputeInternalInsetsListener(listener);
@@ -207,8 +214,15 @@
public void testRemoveOnGlobalFocusChangeListener() {
final LinearLayout layout = (LinearLayout) mActivity.findViewById(R.id.linearlayout);
- final ListView lv1 = (ListView) mActivity.findViewById(R.id.listview1);
- final ListView lv2 = (ListView) mActivity.findViewById(R.id.listview2);
+ final View view1 = mActivity.findViewById(R.id.view1);
+ final View view2 = mActivity.findViewById(R.id.view2);
+
+ mInstrumentation.runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ view1.requestFocus();
+ }
+ });
mViewTreeObserver = layout.getViewTreeObserver();
final MockOnGlobalFocusChangeListener listener = new MockOnGlobalFocusChangeListener();
@@ -217,14 +231,15 @@
mInstrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
- layout.requestChildFocus(lv1, lv2);
+ view2.requestFocus();
}
});
mInstrumentation.waitForIdleSync();
new PollingCheck() {
@Override
protected boolean check() {
- return listener.hasCalledOnGlobalFocusChanged();
+ return listener.hasCalledOnGlobalFocusChanged()
+ && listener.oldFocus == view1 && listener.newFocus == view2;
}
}.run();
@@ -234,7 +249,7 @@
mInstrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
- layout.requestChildFocus(lv1, lv2);
+ view1.requestFocus();
}
});
mInstrumentation.waitForIdleSync();
@@ -345,10 +360,14 @@
private class MockOnGlobalFocusChangeListener implements OnGlobalFocusChangeListener {
private boolean mCalledOnGlobalFocusChanged = false;
+ View oldFocus;
+ View newFocus;
@Override
public void onGlobalFocusChanged(View oldFocus, View newFocus) {
mCalledOnGlobalFocusChanged = true;
+ this.oldFocus = oldFocus;
+ this.newFocus = newFocus;
}
public boolean hasCalledOnGlobalFocusChanged() {
@@ -357,6 +376,8 @@
public void reset() {
mCalledOnGlobalFocusChanged = false;
+ oldFocus = null;
+ newFocus = null;
}
}
diff --git a/tests/tests/webkit/AndroidManifest.xml b/tests/tests/webkit/AndroidManifest.xml
index 9475451..493762e 100644
--- a/tests/tests/webkit/AndroidManifest.xml
+++ b/tests/tests/webkit/AndroidManifest.xml
@@ -19,6 +19,8 @@
package="com.android.cts.webkit">
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+ <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"/>
+ <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/>
<application>
<uses-library android:name="android.test.runner" />
</application>
diff --git a/tests/tests/webkit/src/android/webkit/cts/CacheManagerTest.java b/tests/tests/webkit/src/android/webkit/cts/CacheManagerTest.java
deleted file mode 100644
index 66b1e02..0000000
--- a/tests/tests/webkit/src/android/webkit/cts/CacheManagerTest.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.webkit.cts;
-
-import android.cts.util.PollingCheck;
-import android.test.ActivityInstrumentationTestCase2;
-import android.webkit.CacheManager;
-import android.webkit.CacheManager.CacheResult;
-
-
-import java.util.Map;
-
-public class CacheManagerTest extends ActivityInstrumentationTestCase2<WebViewStubActivity> {
- private static final long CACHEMANAGER_INIT_TIMEOUT = 5000L;
- private static final long NETWORK_OPERATION_TIMEOUT = 10000L;
-
- private CtsTestServer mWebServer;
- private WebViewOnUiThread mOnUiThread;
-
- public CacheManagerTest() {
- super("com.android.cts.stub", WebViewStubActivity.class);
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mOnUiThread = new WebViewOnUiThread(this, getActivity().getWebView());
- }
-
- @Override
- protected void tearDown() throws Exception {
- mOnUiThread.cleanUp();
- if (mWebServer != null) {
- mWebServer.shutdown();
- }
- super.tearDown();
- }
-
- public void testGetCacheFileBaseDir() {
- assertTrue(CacheManager.getCacheFileBaseDir().exists());
- }
-
- public void testCacheTransaction() {
- }
-
- public void testCacheFile() throws Exception {
- mWebServer = new CtsTestServer(getActivity());
- final String url = mWebServer.getAssetUrl(TestHtmlConstants.EMBEDDED_IMG_URL);
-
- // Wait for CacheManager#init() finish.
- new PollingCheck(CACHEMANAGER_INIT_TIMEOUT) {
- @Override
- protected boolean check() {
- return CacheManager.getCacheFileBaseDir() != null;
- }
- }.run();
-
- mOnUiThread.clearCache(true);
- new PollingCheck(NETWORK_OPERATION_TIMEOUT) {
- @Override
- protected boolean check() {
- CacheResult result = CacheManager.getCacheFile(url, null);
- return result == null;
- }
- }.run();
-
- mOnUiThread.loadUrlAndWaitForCompletion(url);
- new PollingCheck(NETWORK_OPERATION_TIMEOUT) {
- @Override
- protected boolean check() {
- CacheResult result = CacheManager.getCacheFile(url, null);
- return result != null;
- }
- }.run();
-
- // Can not test saveCacheFile(), because the output stream is null and
- // saveCacheFile() will throw a NullPointerException. There is no
- // public API to set the output stream.
- }
-
- public void testCacheDisabled() {
- // The cache should always be enabled.
- assertFalse(CacheManager.cacheDisabled());
- }
-}
diff --git a/tests/tests/webkit/src/android/webkit/cts/CacheManager_CacheResultTest.java b/tests/tests/webkit/src/android/webkit/cts/CacheManager_CacheResultTest.java
deleted file mode 100755
index 0c2a51a..0000000
--- a/tests/tests/webkit/src/android/webkit/cts/CacheManager_CacheResultTest.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.webkit.cts;
-
-import android.cts.util.PollingCheck;
-import android.test.ActivityInstrumentationTestCase2;
-import android.webkit.CacheManager;
-import android.webkit.CacheManager.CacheResult;
-import android.webkit.WebView;
-
-
-import org.apache.http.HttpStatus;
-import org.apache.http.impl.cookie.DateUtils;
-
-import java.io.File;
-import java.io.InputStream;
-import java.util.HashMap;
-import java.util.Map;
-
-public class CacheManager_CacheResultTest
- extends ActivityInstrumentationTestCase2<WebViewStubActivity> {
- private static final long NETWORK_OPERATION_TIMEOUT = 10000L;
-
- private CtsTestServer mWebServer;
- private WebViewOnUiThread mOnUiThread;
-
- public CacheManager_CacheResultTest() {
- super("com.android.cts.stub", WebViewStubActivity.class);
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mOnUiThread = new WebViewOnUiThread(this, getActivity().getWebView());
- }
-
- @Override
- protected void tearDown() throws Exception {
- mOnUiThread.cleanUp();
- if (mWebServer != null) {
- mWebServer.shutdown();
- }
- super.tearDown();
- }
-
- public void testCacheResult() throws Exception {
- final long validity = 5 * 60 * 1000; // 5 min
- final long age = 30 * 60 * 1000; // 30 min
- final long tolerance = 5 * 1000; // 5s
-
- mWebServer = new CtsTestServer(getActivity());
- final String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
- mWebServer.setDocumentAge(age);
- mWebServer.setDocumentValidity(validity);
-
- mOnUiThread.clearCache(true);
- new PollingCheck(NETWORK_OPERATION_TIMEOUT) {
- @Override
- protected boolean check() {
- CacheResult result =
- CacheManager.getCacheFile(url, null);
- return result == null;
- }
- }.run();
- final long time = System.currentTimeMillis();
- mOnUiThread.loadUrlAndWaitForCompletion(url);
-
- Map<String, String> headers = new HashMap<String, String>();
- CacheResult result = CacheManager.getCacheFile(url, headers);
- assertTrue(headers.isEmpty());
-
- assertNotNull(result);
- assertNotNull(result.getInputStream());
- assertTrue(result.getContentLength() > 0);
- assertNull(result.getETag());
- assertEquals((double)(time - age),
- (double)DateUtils.parseDate(result.getLastModified()).getTime(),
- (double)tolerance);
- File file = new File(CacheManager.getCacheFileBaseDir().getPath(), result.getLocalPath());
- assertTrue(file.exists());
- assertNull(result.getLocation());
- assertEquals("text/html", result.getMimeType());
- assertNull(result.getOutputStream());
- assertEquals((double)(time + validity), (double)result.getExpires(),
- (double)tolerance);
- assertEquals(HttpStatus.SC_OK, result.getHttpStatusCode());
- assertNotNull(result.getEncoding());
-
- result.setEncoding("iso-8859-1");
- assertEquals("iso-8859-1", result.getEncoding());
-
- result.setInputStream(null);
- assertNull(result.getInputStream());
- }
-}
diff --git a/tests/tests/webkit/src/android/webkit/cts/ChromeClient.java b/tests/tests/webkit/src/android/webkit/cts/ChromeClient.java
index 3df78a4..ca61b24 100644
--- a/tests/tests/webkit/src/android/webkit/cts/ChromeClient.java
+++ b/tests/tests/webkit/src/android/webkit/cts/ChromeClient.java
@@ -20,6 +20,7 @@
// A chrome client for listening webview chrome events.
class ChromeClient extends WaitForProgressClient {
+
private boolean mIsMessageLevelAvailable;
private ConsoleMessage.MessageLevel mMessageLevel;
@@ -32,7 +33,8 @@
mMessageLevel = message.messageLevel();
mIsMessageLevelAvailable = true;
notify();
- return true;
+ // return false for default handling; i.e. printing the message.
+ return false;
}
public synchronized ConsoleMessage.MessageLevel getMessageLevel(int timeout) {
@@ -45,4 +47,4 @@
}
return mMessageLevel;
}
-}
\ No newline at end of file
+}
diff --git a/tests/tests/webkit/src/android/webkit/cts/CookieManagerTest.java b/tests/tests/webkit/src/android/webkit/cts/CookieManagerTest.java
index 51eae48..a0b4fa6 100755
--- a/tests/tests/webkit/src/android/webkit/cts/CookieManagerTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/CookieManagerTest.java
@@ -68,7 +68,7 @@
CtsTestServer server = new CtsTestServer(getActivity(), false);
String url = server.getCookieUrl("conquest.html");
mOnUiThread.loadUrlAndWaitForCompletion(url);
- assertEquals(null, mOnUiThread.getTitle()); // no cookies passed
+ assertEquals("0", mOnUiThread.getTitle()); // no cookies passed
Thread.sleep(500);
assertNull(mCookieManager.getCookie(url));
@@ -77,7 +77,7 @@
url = server.getCookieUrl("war.html");
mOnUiThread.loadUrlAndWaitForCompletion(url);
- assertEquals(null, mOnUiThread.getTitle()); // no cookies passed
+ assertEquals("0", mOnUiThread.getTitle()); // no cookies passed
waitForCookie(url);
String cookie = mCookieManager.getCookie(url);
assertNotNull(cookie);
@@ -89,7 +89,7 @@
url = server.getCookieUrl("famine.html");
mOnUiThread.loadUrlAndWaitForCompletion(url);
- assertEquals("count=0", mOnUiThread.getTitle()); // outgoing cookie
+ assertEquals("1|count=0", mOnUiThread.getTitle()); // outgoing cookie
waitForCookie(url);
cookie = mCookieManager.getCookie(url);
assertNotNull(cookie);
@@ -100,7 +100,7 @@
url = server.getCookieUrl("death.html");
mCookieManager.setCookie(url, "count=41");
mOnUiThread.loadUrlAndWaitForCompletion(url);
- assertEquals("count=41", mOnUiThread.getTitle()); // outgoing cookie
+ assertEquals("1|count=41", mOnUiThread.getTitle()); // outgoing cookie
waitForCookie(url);
cookie = mCookieManager.getCookie(url);
assertNotNull(cookie);
diff --git a/tests/tests/webkit/src/android/webkit/cts/CookieTest.java b/tests/tests/webkit/src/android/webkit/cts/CookieTest.java
index 14a14d0..0c0396a 100644
--- a/tests/tests/webkit/src/android/webkit/cts/CookieTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/CookieTest.java
@@ -45,23 +45,6 @@
}
}
assertFalse(mCookieManager.hasCookies());
-
- }
-
- public void testParse() {
- String url = "http://www.foo.com";
-
- // basic
- mCookieManager.setCookie(url, "a=b");
- String cookie = mCookieManager.getCookie(url);
- assertTrue(cookie.equals("a=b"));
-
- // quoted
- mCookieManager.setCookie(url, "c=\"d;\"");
- cookie = mCookieManager.getCookie(url);
-
- assertTrue(cookie.contains("a=b"));
- assertTrue(cookie.contains("c=\"d;\""));
}
public void testDomain() {
diff --git a/tests/tests/webkit/src/android/webkit/cts/GeolocationTest.java b/tests/tests/webkit/src/android/webkit/cts/GeolocationTest.java
new file mode 100644
index 0000000..1dafd3b
--- /dev/null
+++ b/tests/tests/webkit/src/android/webkit/cts/GeolocationTest.java
@@ -0,0 +1,514 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit.cts;
+
+import android.content.Context;
+import android.cts.util.PollingCheck;
+import android.graphics.Bitmap;
+import android.location.Criteria;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.location.LocationProvider;
+import android.os.Bundle;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.test.ActivityInstrumentationTestCase2;
+import android.webkit.CookieManager;
+import android.webkit.CookieSyncManager;
+import android.webkit.GeolocationPermissions;
+import android.webkit.JavascriptInterface;
+import android.webkit.WebChromeClient;
+import android.webkit.WebResourceResponse;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.webkit.cts.WebViewOnUiThread.WaitForLoadedClient;
+import android.webkit.cts.WebViewOnUiThread.WaitForProgressClient;
+
+import java.io.ByteArrayInputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.concurrent.Callable;
+import java.util.Date;
+import java.util.Random;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.Set;
+import java.util.TreeSet;
+
+import junit.framework.Assert;
+
+public class GeolocationTest extends ActivityInstrumentationTestCase2<WebViewStubActivity> {
+
+ // TODO Write additional tests to cover:
+ // - test that the errors are correct
+ // - test that use of gps and network location is correct
+
+ // The URLs does not matter since the tests will intercept the load, but it has to be a real
+ // url, and different domains.
+ private static final String URL_1 = "http://www.example.com";
+ private static final String URL_2 = "http://www.example.org";
+
+ private static final String JS_INTERFACE_NAME = "Android";
+ private static final int POLLING_TIMEOUT = 60 * 1000;
+ private static final String PROVIDER_NAME = "WebKitGeolocationTestLocationProvider";
+
+ // static HTML page always injected instead of the url loaded
+ private static final String RAW_HTML =
+ "<!DOCTYPE html>\n" +
+ "<html>\n" +
+ " <head>\n" +
+ " <title>Geolocation</title>\n" +
+ " <script>\n" +
+ " function gotPos(position) {\n" +
+ " " + JS_INTERFACE_NAME + ".gotLocation();\n" +
+ " }\n" +
+ " function initiate_getCurrentPosition() {\n" +
+ " navigator.geolocation.getCurrentPosition(\n" +
+ " gotPos,\n" +
+ " handle_errors,\n" +
+ " {maximumAge:1000});\n" +
+ " }\n" +
+ " function handle_errors(error) {\n" +
+ " switch(error.code) {\n" +
+ " case error.PERMISSION_DENIED:\n" +
+ " " + JS_INTERFACE_NAME + ".errorDenied(); break;\n" +
+ " case error.POSITION_UNAVAILABLE:\n" +
+ " " + JS_INTERFACE_NAME + ".errorUnavailable(); break;\n" +
+ " case error.TIMEOUT:\n" +
+ " " + JS_INTERFACE_NAME + ".errorTimeout(); break;\n" +
+ " default: break;\n" +
+ " }\n" +
+ " }\n" +
+ " </script>\n" +
+ " </head>\n" +
+ " <body onload=\"initiate_getCurrentPosition();\">\n" +
+ " </body>\n" +
+ "</html>";
+
+ private JavascriptStatusReceiver mJavascriptStatusReceiver;
+ private LocationManager mLocationManager;
+ private WebViewOnUiThread mOnUiThread;
+
+ public GeolocationTest() throws Exception {
+ super("com.android.cts.stub", WebViewStubActivity.class);
+ }
+
+ // Both this test and WebViewOnUiThread need to override some of the methods on WebViewClient,
+ // so this test sublclasses the WebViewClient from WebViewOnUiThread
+ private static class InterceptClient extends WaitForLoadedClient {
+
+ public InterceptClient(WebViewOnUiThread webViewOnUiThread) throws Exception {
+ super(webViewOnUiThread);
+ }
+
+ @Override
+ public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
+ // Intercept all page loads with the same geolocation enabled page
+ try {
+ return new WebResourceResponse("text/html", "utf-8",
+ new ByteArrayInputStream(RAW_HTML.getBytes("UTF-8")));
+ } catch(java.io.UnsupportedEncodingException e) {
+ return null;
+ }
+ }
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ // Set up a WebView with JavaScript and Geolocation enabled
+ final String GEO_DIR = "geo_test";
+ mOnUiThread = new WebViewOnUiThread(this, getActivity().getWebView());
+ mOnUiThread.getSettings().setJavaScriptEnabled(true);
+ mOnUiThread.getSettings().setGeolocationEnabled(true);
+ mOnUiThread.getSettings().setGeolocationDatabasePath(
+ getActivity().getApplicationContext().getDir(GEO_DIR, 0).getPath());
+
+ // Add a JsInterface to report back to the test when a location is received
+ mJavascriptStatusReceiver = new JavascriptStatusReceiver();
+ mOnUiThread.addJavascriptInterface(mJavascriptStatusReceiver, JS_INTERFACE_NAME);
+
+ // Always intercept all loads with the same geolocation test page
+ mOnUiThread.setWebViewClient(new InterceptClient(mOnUiThread));
+ // Clear all permissions before each test
+ GeolocationPermissions.getInstance().clearAll();
+ // Cache this mostly because the lookup is two lines of code
+ mLocationManager = (LocationManager)getActivity().getApplicationContext()
+ .getSystemService(Context.LOCATION_SERVICE);
+ // Add a test provider before each test to inject a location
+ addTestProvider(PROVIDER_NAME);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ // Remove the test provider after each test
+ try {
+ mLocationManager.removeTestProvider(PROVIDER_NAME);
+ } catch (IllegalArgumentException e) {} // Not much to do about this
+ mOnUiThread.cleanUp();
+ // This will null all member and static variables
+ super.tearDown();
+ }
+
+ // Update location with a fixed latitude and longtitude, sets the time to the current time.
+ private void updateLocation(final String providerName) {
+ Location location = new Location(providerName);
+ location.setLatitude(40);
+ location.setLongitude(40);
+ location.setAccuracy(1.0f);
+ location.setTime(java.lang.System.currentTimeMillis());
+ location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
+ mLocationManager.setTestProviderLocation(providerName, location);
+ }
+
+ // Need to set the location just after loading the url. Setting it after each load instead of
+ // using a maximum age.
+ private void loadUrlAndUpdateLocation(String url) {
+ mOnUiThread.loadUrlAndWaitForCompletion(url);
+ updateLocation(PROVIDER_NAME);
+ }
+
+ // WebChromeClient that accepts each location for one load. WebChromeClient is used in
+ // WebViewOnUiThread to detect when the page is loaded, so subclassing the one used there.
+ private static class TestSimpleGeolocationRequestWebChromeClient
+ extends WaitForProgressClient {
+ private boolean mReceivedRequest = false;
+ private final boolean mAccept;
+ private final boolean mRetain;
+
+ public TestSimpleGeolocationRequestWebChromeClient(
+ WebViewOnUiThread webViewOnUiThread, boolean accept, boolean retain) {
+ super(webViewOnUiThread);
+ this.mAccept = accept;
+ this.mRetain = retain;
+ }
+
+ @Override
+ public void onGeolocationPermissionsShowPrompt(
+ String origin, GeolocationPermissions.Callback callback) {
+ mReceivedRequest = true;
+ callback.invoke(origin, mAccept, mRetain);
+ }
+ }
+
+ private void addTestProvider(final String providerName) {
+ mLocationManager.addTestProvider(providerName,
+ true, //requiresNetwork,
+ false, // requiresSatellite,
+ true, // requiresCell,
+ false, // hasMonetaryCost,
+ false, // supportsAltitude,
+ false, // supportsSpeed,
+ false, // supportsBearing,
+ Criteria.POWER_MEDIUM, // powerRequirement
+ Criteria.ACCURACY_FINE); // accuracy
+ mLocationManager.setTestProviderEnabled(providerName, true);
+ }
+
+ // Test loading a page and accepting the domain for one load
+ public void testSimpleGeolocationRequestAcceptOnce() throws Exception {
+ final TestSimpleGeolocationRequestWebChromeClient chromeClientAcceptOnce =
+ new TestSimpleGeolocationRequestWebChromeClient(mOnUiThread, true, false);
+ mOnUiThread.setWebChromeClient(chromeClientAcceptOnce);
+ loadUrlAndUpdateLocation(URL_1);
+ Callable<Boolean> receivedRequest = new Callable<Boolean>() {
+ @Override
+ public Boolean call() {
+ return chromeClientAcceptOnce.mReceivedRequest;
+ }
+ };
+ PollingCheck.check("Geolocation prompt not called", POLLING_TIMEOUT, receivedRequest);
+ Callable<Boolean> receivedLocation = new Callable<Boolean>() {
+ @Override
+ public Boolean call() {
+ return mJavascriptStatusReceiver.mHasPosition;
+ }
+ };
+ PollingCheck.check("JS didn't get position", POLLING_TIMEOUT, receivedLocation);
+ chromeClientAcceptOnce.mReceivedRequest = false;
+ // Load URL again, should receive callback again
+ loadUrlAndUpdateLocation(URL_1);
+ PollingCheck.check("Geolocation prompt not called", POLLING_TIMEOUT, receivedRequest);
+ PollingCheck.check("JS didn't get position", POLLING_TIMEOUT, receivedLocation);
+ }
+
+ private static class OriginCheck extends PollingCheck implements
+ android.webkit.ValueCallback<Set<String>> {
+
+ private boolean mReceived = false;
+ private final Set<String> mExpectedValue;
+ private Set<String> mReceivedValue = null;
+
+ public OriginCheck(Set<String> val) {
+ mExpectedValue = val;
+ }
+
+ @Override
+ protected boolean check() {
+ if (!mReceived) return false;
+ if (mExpectedValue.equals(mReceivedValue)) return true;
+ if (mExpectedValue.size() != mReceivedValue.size()) return false;
+ // Origins can have different strings even if they represent the same origin,
+ // for example http://www.example.com is the same origin as http://www.example.com/
+ // and they are both valid representations
+ for (String origin : mReceivedValue) {
+ if (mExpectedValue.contains(origin)) continue;
+ if (origin.endsWith("/")) {
+ if (mExpectedValue.contains(origin.substring(0, origin.length() - 1))) {
+ continue;
+ }
+ } else {
+ if (mExpectedValue.contains(origin + "/")) continue;
+ }
+ return false;
+ }
+ return true;
+ }
+ @Override
+ public void onReceiveValue(Set<String> value) {
+ mReceived = true;
+ mReceivedValue = value;
+ }
+ }
+
+ // Class that waits and checks for a particular value being received
+ private static class BooleanCheck extends PollingCheck implements
+ android.webkit.ValueCallback<Boolean> {
+
+ private boolean mReceived = false;
+ private final boolean mExpectedValue;
+ private boolean mReceivedValue;
+
+ public BooleanCheck(boolean val) {
+ mExpectedValue = val;
+ }
+
+ @Override
+ protected boolean check() {
+ return mReceived && mReceivedValue == mExpectedValue;
+ }
+
+ @Override
+ public void onReceiveValue(Boolean value) {
+ mReceived = true;
+ mReceivedValue = value;
+ }
+ }
+
+ // Test loading a page and retaining the domain forever
+ public void testSimpleGeolocationRequestAcceptAlways() throws Exception {
+ final TestSimpleGeolocationRequestWebChromeClient chromeClientAcceptAlways =
+ new TestSimpleGeolocationRequestWebChromeClient(mOnUiThread, true, true);
+ mOnUiThread.setWebChromeClient(chromeClientAcceptAlways);
+ // Load url once, and the callback should accept the domain for all future loads
+ loadUrlAndUpdateLocation(URL_1);
+ Callable<Boolean> receivedRequest = new Callable<Boolean>() {
+ @Override
+ public Boolean call() {
+ return chromeClientAcceptAlways.mReceivedRequest;
+ }
+ };
+ PollingCheck.check("Geolocation prompt not called", POLLING_TIMEOUT, receivedRequest);
+ Callable<Boolean> receivedLocation = new Callable<Boolean>() {
+ @Override
+ public Boolean call() {
+ return mJavascriptStatusReceiver.mHasPosition;
+ }
+ };
+ PollingCheck.check("JS didn't get position", POLLING_TIMEOUT, receivedLocation);
+ chromeClientAcceptAlways.mReceivedRequest = false;
+ mJavascriptStatusReceiver.clearState();
+ // Load the same URL again
+ loadUrlAndUpdateLocation(URL_1);
+ PollingCheck.check("JS didn't get position", POLLING_TIMEOUT, receivedLocation);
+ // Assert prompt for geolocation permission is not called the second time
+ assertFalse(chromeClientAcceptAlways.mReceivedRequest);
+ // Check that the permission is in GeolocationPermissions
+ BooleanCheck trueCheck = new BooleanCheck(true);
+ GeolocationPermissions.getInstance().getAllowed(URL_1, trueCheck);
+ trueCheck.run();
+ Set<String> acceptedOrigins = new TreeSet<String>();
+ acceptedOrigins.add(URL_1);
+ OriginCheck originCheck = new OriginCheck(acceptedOrigins);
+ GeolocationPermissions.getInstance().getOrigins(originCheck);
+ originCheck.run();
+
+ // URL_2 should get a prompt
+ chromeClientAcceptAlways.mReceivedRequest = false;
+ loadUrlAndUpdateLocation(URL_2);
+ // Checking the callback for geolocation permission prompt is called
+ PollingCheck.check("Geolocation prompt not called", POLLING_TIMEOUT, receivedRequest);
+ PollingCheck.check("JS didn't get position", POLLING_TIMEOUT, receivedLocation);
+ acceptedOrigins.add(URL_2);
+ originCheck = new OriginCheck(acceptedOrigins);
+ GeolocationPermissions.getInstance().getOrigins(originCheck);
+ originCheck.run();
+ // Remove a domain manually that was added by the callback
+ GeolocationPermissions.getInstance().clear(URL_1);
+ acceptedOrigins.remove(URL_1);
+ originCheck = new OriginCheck(acceptedOrigins);
+ GeolocationPermissions.getInstance().getOrigins(originCheck);
+ originCheck.run();
+ }
+
+ // Test the GeolocationPermissions API
+ public void testGeolocationPermissions() {
+ Set<String> acceptedOrigins = new TreeSet<String>();
+ BooleanCheck falseCheck = new BooleanCheck(false);
+ GeolocationPermissions.getInstance().getAllowed(URL_2, falseCheck);
+ falseCheck.run();
+ OriginCheck originCheck = new OriginCheck(acceptedOrigins);
+ GeolocationPermissions.getInstance().getOrigins(originCheck);
+ originCheck.run();
+
+ // Remove a domain that has not been allowed
+ GeolocationPermissions.getInstance().clear(URL_2);
+ acceptedOrigins.remove(URL_2);
+ originCheck = new OriginCheck(acceptedOrigins);
+ GeolocationPermissions.getInstance().getOrigins(originCheck);
+ originCheck.run();
+
+ // Add a domain
+ acceptedOrigins.add(URL_2);
+ GeolocationPermissions.getInstance().allow(URL_2);
+ originCheck = new OriginCheck(acceptedOrigins);
+ GeolocationPermissions.getInstance().getOrigins(originCheck);
+ originCheck.run();
+ BooleanCheck trueCheck = new BooleanCheck(true);
+ GeolocationPermissions.getInstance().getAllowed(URL_2, trueCheck);
+ trueCheck.run();
+
+ // Add a domain
+ acceptedOrigins.add(URL_1);
+ GeolocationPermissions.getInstance().allow(URL_1);
+ originCheck = new OriginCheck(acceptedOrigins);
+ GeolocationPermissions.getInstance().getOrigins(originCheck);
+ originCheck.run();
+
+ // Remove a domain that has been allowed
+ GeolocationPermissions.getInstance().clear(URL_2);
+ acceptedOrigins.remove(URL_2);
+ originCheck = new OriginCheck(acceptedOrigins);
+ GeolocationPermissions.getInstance().getOrigins(originCheck);
+ originCheck.run();
+ falseCheck = new BooleanCheck(false);
+ GeolocationPermissions.getInstance().getAllowed(URL_2, falseCheck);
+ falseCheck.run();
+
+ // Try to clear all domains
+ GeolocationPermissions.getInstance().clearAll();
+ acceptedOrigins.clear();
+ originCheck = new OriginCheck(acceptedOrigins);
+ GeolocationPermissions.getInstance().getOrigins(originCheck);
+ originCheck.run();
+
+ // Add a domain
+ acceptedOrigins.add(URL_1);
+ GeolocationPermissions.getInstance().allow(URL_1);
+ originCheck = new OriginCheck(acceptedOrigins);
+ GeolocationPermissions.getInstance().getOrigins(originCheck);
+ originCheck.run();
+ }
+
+ // Test loading pages and checks rejecting once and recjecting the domain forever
+ public void testSimpleGeolocationRequestReject() throws Exception {
+ final TestSimpleGeolocationRequestWebChromeClient chromeClientRejectOnce =
+ new TestSimpleGeolocationRequestWebChromeClient(mOnUiThread, false, false);
+ mOnUiThread.setWebChromeClient(chromeClientRejectOnce);
+ // Load url once, and the callback should accept the domain for all future loads
+ mOnUiThread.loadUrlAndWaitForCompletion(URL_1);
+ Callable<Boolean> receivedRequest = new Callable<Boolean>() {
+ @Override
+ public Boolean call() {
+ return chromeClientRejectOnce.mReceivedRequest;
+ }
+ };
+ PollingCheck.check("Geolocation prompt not called", POLLING_TIMEOUT, receivedRequest);
+ Callable<Boolean> locationDenied = new Callable<Boolean>() {
+ @Override
+ public Boolean call() {
+ return mJavascriptStatusReceiver.mDenied;
+ }
+ };
+ PollingCheck.check("JS didn't get position", POLLING_TIMEOUT, locationDenied);
+ // Same result should happen on next run
+ mOnUiThread.loadUrlAndWaitForCompletion(URL_1);
+ PollingCheck.check("Geolocation prompt not called", POLLING_TIMEOUT, receivedRequest);
+ PollingCheck.check("JS didn't get position", POLLING_TIMEOUT, locationDenied);
+
+ // Try to reject forever
+ final TestSimpleGeolocationRequestWebChromeClient chromeClientRejectAlways =
+ new TestSimpleGeolocationRequestWebChromeClient(mOnUiThread, false, true);
+ mOnUiThread.setWebChromeClient(chromeClientRejectAlways);
+ mOnUiThread.loadUrlAndWaitForCompletion(URL_2);
+ PollingCheck.check("Geolocation prompt not called", POLLING_TIMEOUT, receivedRequest);
+ PollingCheck.check("JS didn't get position", POLLING_TIMEOUT, locationDenied);
+ // second load should now not get a prompt
+ chromeClientRejectAlways.mReceivedRequest = false;
+ mOnUiThread.loadUrlAndWaitForCompletion(URL_2);
+ PollingCheck.check("JS didn't get position", POLLING_TIMEOUT, locationDenied);
+ PollingCheck.check("Geolocation prompt not called", POLLING_TIMEOUT, receivedRequest);
+
+ // Test if it gets added to origins
+ Set<String> acceptedOrigins = new TreeSet<String>();
+ acceptedOrigins.add(URL_2);
+ OriginCheck domainCheck = new OriginCheck(acceptedOrigins);
+ GeolocationPermissions.getInstance().getOrigins(domainCheck);
+ domainCheck.run();
+ // And now check that getAllowed returns false
+ BooleanCheck falseCheck = new BooleanCheck(false);
+ GeolocationPermissions.getInstance().getAllowed(URL_1, falseCheck);
+ falseCheck.run();
+ }
+
+ // Object added to the page via AddJavascriptInterface() that is used by the test Javascript to
+ // notify back to Java when a location or error is received.
+ public final static class JavascriptStatusReceiver {
+ public volatile boolean mHasPosition = false;
+ public volatile boolean mDenied = false;
+ public volatile boolean mUnavailable = false;
+ public volatile boolean mTimeout = false;
+
+ public void clearState() {
+ mHasPosition = false;
+ mDenied = false;
+ mUnavailable = false;
+ mTimeout = false;
+ }
+
+ @JavascriptInterface
+ public void errorDenied() {
+ mDenied = true;
+ }
+
+ @JavascriptInterface
+ public void errorUnavailable() {
+ mUnavailable = true;
+ }
+
+ @JavascriptInterface
+ public void errorTimeout() {
+ mTimeout = true;
+ }
+
+ @JavascriptInterface
+ public void gotLocation() {
+ mHasPosition = true;
+ }
+ }
+}
diff --git a/tests/tests/webkit/src/android/webkit/cts/HttpAuthHandlerTest.java b/tests/tests/webkit/src/android/webkit/cts/HttpAuthHandlerTest.java
index 2fcf53c..52e9278 100644
--- a/tests/tests/webkit/src/android/webkit/cts/HttpAuthHandlerTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/HttpAuthHandlerTest.java
@@ -28,6 +28,11 @@
private static final long TIMEOUT = 10000;
+ private static final String WRONG_USERNAME = "wrong_user";
+ private static final String WRONG_PASSWORD = "wrong_password";
+ private static final String CORRECT_USERNAME = CtsTestServer.AUTH_USER;
+ private static final String CORRECT_PASSWORD = CtsTestServer.AUTH_PASS;
+
private CtsTestServer mWebServer;
private WebViewOnUiThread mOnUiThread;
@@ -50,64 +55,18 @@
super.tearDown();
}
- public void testProceed() throws Exception {
- mWebServer = new CtsTestServer(getActivity());
- String url = mWebServer.getAuthAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
-
- // wrong credentials
- MyWebViewClient client = new MyWebViewClient(true, "FakeUser", "FakePass");
- mOnUiThread.setWebViewClient(client);
-
- mOnUiThread.loadUrlAndWaitForCompletion(url);
- assertEquals(CtsTestServer.AUTH_REALM, client.realm);
- assertEquals(CtsTestServer.getReasonString(HttpStatus.SC_UNAUTHORIZED), mOnUiThread.getTitle());
- assertTrue(client.useHttpAuthUsernamePassword);
-
- // missing credentials
- client = new MyWebViewClient(true, null, null);
- mOnUiThread.setWebViewClient(client);
-
- mOnUiThread.loadUrlAndWaitForCompletion(url);
- assertEquals(CtsTestServer.AUTH_REALM, client.realm);
- assertEquals(
- CtsTestServer.getReasonString(HttpStatus.SC_UNAUTHORIZED), mOnUiThread.getTitle());
- assertTrue(client.useHttpAuthUsernamePassword);
-
- // correct credentials
- client = new MyWebViewClient(true, CtsTestServer.AUTH_USER, CtsTestServer.AUTH_PASS);
- mOnUiThread.setWebViewClient(client);
-
- mOnUiThread.loadUrlAndWaitForCompletion(url);
- assertEquals(CtsTestServer.AUTH_REALM, client.realm);
- assertEquals(TestHtmlConstants.HELLO_WORLD_TITLE, mOnUiThread.getTitle());
- assertTrue(client.useHttpAuthUsernamePassword);
- }
-
- public void testCancel() throws Exception {
- mWebServer = new CtsTestServer(getActivity());
-
- String url = mWebServer.getAuthAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
- MyWebViewClient client = new MyWebViewClient(false, null, null);
- mOnUiThread.setWebViewClient(client);
-
- mOnUiThread.loadUrlAndWaitForCompletion(url);
- assertEquals(CtsTestServer.AUTH_REALM, client.realm);
- assertEquals(
- CtsTestServer.getReasonString(HttpStatus.SC_UNAUTHORIZED), mOnUiThread.getTitle());
- }
-
- private class MyWebViewClient extends WaitForLoadedClient {
+ private class ProceedHttpAuthClient extends WaitForLoadedClient {
String realm;
boolean useHttpAuthUsernamePassword;
- private boolean mProceed;
+ private int mMaxAuthAttempts;
private String mUser;
private String mPassword;
private int mAuthCount;
- MyWebViewClient(boolean proceed, String user, String password) {
+ ProceedHttpAuthClient(int maxAuthAttempts, String user, String password) {
super(mOnUiThread);
- mProceed = proceed;
+ mMaxAuthAttempts = maxAuthAttempts;
mUser = user;
mPassword = password;
}
@@ -115,18 +74,113 @@
@Override
public void onReceivedHttpAuthRequest(WebView view,
HttpAuthHandler handler, String host, String realm) {
- ++mAuthCount;
- if (mAuthCount > 1) {
+ if (++mAuthCount > mMaxAuthAttempts) {
handler.cancel();
return;
}
+
this.realm = realm;
this.useHttpAuthUsernamePassword = handler.useHttpAuthUsernamePassword();
- if (mProceed) {
- handler.proceed(mUser, mPassword);
- } else {
- handler.cancel();
- }
+
+ handler.proceed(mUser, mPassword);
}
}
+
+ private class CancelHttpAuthClient extends WaitForLoadedClient {
+ String realm;
+
+ CancelHttpAuthClient() {
+ super(mOnUiThread);
+ }
+
+ @Override
+ public void onReceivedHttpAuthRequest(WebView view,
+ HttpAuthHandler handler, String host, String realm) {
+ this.realm = realm;
+ handler.cancel();
+ }
+ }
+
+ private void incorrectCredentialsAccessDenied(String url) throws Throwable {
+ ProceedHttpAuthClient client = new ProceedHttpAuthClient(1, WRONG_USERNAME, WRONG_PASSWORD);
+ mOnUiThread.setWebViewClient(client);
+
+ // As we're providing incorrect credentials, the page should complete but at
+ // an access denied page.
+ mOnUiThread.loadUrlAndWaitForCompletion(url);
+
+ assertEquals(CtsTestServer.AUTH_REALM, client.realm);
+ assertEquals(CtsTestServer.getReasonString(HttpStatus.SC_UNAUTHORIZED), mOnUiThread.getTitle());
+ }
+
+ private void missingCredentialsAccessDenied(String url) throws Throwable {
+ ProceedHttpAuthClient client = new ProceedHttpAuthClient(1, null, null);
+ mOnUiThread.setWebViewClient(client);
+
+ // As we're providing no credentials, the page should complete but at
+ // an access denied page.
+ mOnUiThread.loadUrlAndWaitForCompletion(url);
+
+ assertEquals(CtsTestServer.AUTH_REALM, client.realm);
+ assertEquals(CtsTestServer.getReasonString(HttpStatus.SC_UNAUTHORIZED), mOnUiThread.getTitle());
+ }
+
+ private void correctCredentialsAccessGranted(String url) throws Throwable {
+ ProceedHttpAuthClient client = new ProceedHttpAuthClient(1, CORRECT_USERNAME, CORRECT_PASSWORD);
+ mOnUiThread.setWebViewClient(client);
+
+ // As we're providing valid credentials, the page should complete and
+ // at the page we requested.
+ mOnUiThread.loadUrlAndWaitForCompletion(url);
+
+ assertEquals(CtsTestServer.AUTH_REALM, client.realm);
+ assertEquals(TestHtmlConstants.HELLO_WORLD_TITLE, mOnUiThread.getTitle());
+ }
+
+ public void testProceed() throws Throwable {
+ mWebServer = new CtsTestServer(getActivity());
+ String url = mWebServer.getAuthAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
+
+ incorrectCredentialsAccessDenied(url);
+ missingCredentialsAccessDenied(url);
+ correctCredentialsAccessGranted(url);
+ }
+
+ public void testCancel() throws Throwable {
+ mWebServer = new CtsTestServer(getActivity());
+ String url = mWebServer.getAuthAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
+
+ CancelHttpAuthClient client = new CancelHttpAuthClient();
+ mOnUiThread.setWebViewClient(client);
+
+ mOnUiThread.loadUrlAndWaitForCompletion(url);
+ assertEquals(CtsTestServer.AUTH_REALM, client.realm);
+ assertEquals(CtsTestServer.getReasonString(HttpStatus.SC_UNAUTHORIZED), mOnUiThread.getTitle());
+ }
+
+ public void testUseHttpAuthUsernamePassword() throws Throwable {
+ mWebServer = new CtsTestServer(getActivity());
+ String url = mWebServer.getAuthAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
+
+ // Try to login once with incorrect credentials. This should cause
+ // useHttpAuthUsernamePassword to be true in the callback, as at that point
+ // we don't yet know that the credentials we will use are invalid.
+ ProceedHttpAuthClient client = new ProceedHttpAuthClient(1, WRONG_USERNAME, WRONG_PASSWORD);
+ mOnUiThread.setWebViewClient(client);
+ mOnUiThread.loadUrlAndWaitForCompletion(url);
+ assertEquals(CtsTestServer.AUTH_REALM, client.realm);
+ assertEquals(CtsTestServer.getReasonString(HttpStatus.SC_UNAUTHORIZED), mOnUiThread.getTitle());
+ assertTrue(client.useHttpAuthUsernamePassword);
+
+ // Try to login twice with invalid credentials. This should cause
+ // useHttpAuthUsernamePassword to return false, as the credentials
+ // we would have stored on the first auth request
+ // are not suitable for use the second time.
+ client = new ProceedHttpAuthClient(2, WRONG_USERNAME, WRONG_PASSWORD);
+ mOnUiThread.setWebViewClient(client);
+ mOnUiThread.loadUrlAndWaitForCompletion(url);
+ assertEquals(CtsTestServer.AUTH_REALM, client.realm);
+ assertEquals(CtsTestServer.getReasonString(HttpStatus.SC_UNAUTHORIZED), mOnUiThread.getTitle());
+ assertFalse(client.useHttpAuthUsernamePassword);
+ }
}
diff --git a/tests/tests/webkit/src/android/webkit/cts/TestHtmlConstants.java b/tests/tests/webkit/src/android/webkit/cts/TestHtmlConstants.java
index 63354d4..47ec475 100644
--- a/tests/tests/webkit/src/android/webkit/cts/TestHtmlConstants.java
+++ b/tests/tests/webkit/src/android/webkit/cts/TestHtmlConstants.java
@@ -62,6 +62,8 @@
public static final String ANCHOR_ASSET_URL = "webkit/test_anchor.html";
public static final String IMAGE_ACCESS_URL = "webkit/test_imageaccess.html";
public static final String IFRAME_ACCESS_URL = "webkit/test_iframeaccess.html";
+ public static final String DATABASE_ACCESS_URL = "webkit/test_databaseaccess.html";
+ public static final String STOP_LOADING_URL = "webkit/test_stop_loading.html";
// Must match the title of the page at
// android/frameworks/base/core/res/res/raw/loaderror.html
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebChromeClientTest.java b/tests/tests/webkit/src/android/webkit/cts/WebChromeClientTest.java
index 4a536b8..2a5044e 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebChromeClientTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebChromeClientTest.java
@@ -20,6 +20,7 @@
import android.graphics.Bitmap;
import android.os.Message;
import android.test.ActivityInstrumentationTestCase2;
+import android.view.ViewGroup;
import android.webkit.JsPromptResult;
import android.webkit.JsResult;
import android.webkit.WebIconDatabase;
@@ -27,7 +28,6 @@
import android.webkit.WebView;
import android.webkit.cts.WebViewOnUiThread.WaitForProgressClient;
-
public class WebChromeClientTest extends ActivityInstrumentationTestCase2<WebViewStubActivity> {
private static final long TEST_TIMEOUT = 5000L;
@@ -64,7 +64,8 @@
mOnUiThread.setWebChromeClient(webChromeClient);
assertFalse(webChromeClient.hadOnProgressChanged());
- mOnUiThread.loadUrlAndWaitForCompletion(TestHtmlConstants.HELLO_WORLD_URL);
+ String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
+ mOnUiThread.loadUrlAndWaitForCompletion(url);
new PollingCheck(TEST_TIMEOUT) {
@Override
@@ -132,8 +133,8 @@
assertFalse(webChromeClient.hadOnCreateWindow());
- // load a page that opens a child window, requests focus for the child and sets a timeout
- // after which the child will be closed
+ // Load a page that opens a child window and sets a timeout after which the child
+ // will be closed.
mOnUiThread.loadUrlAndWaitForCompletion(mWebServer.
getAssetUrl(TestHtmlConstants.JS_WINDOW_URL));
@@ -143,7 +144,6 @@
return webChromeClient.hadOnCreateWindow();
}
}.run();
- assertFalse(webChromeClient.hadOnRequestFocus());
new PollingCheck(TEST_TIMEOUT) {
@Override
protected boolean check() {
@@ -388,6 +388,8 @@
transport.setWebView(childView);
resultMsg.sendToTarget();
mHadOnCreateWindow = true;
+ getActivity().addContentView(childView, new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
return true;
}
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebHistoryItemTest.java b/tests/tests/webkit/src/android/webkit/cts/WebHistoryItemTest.java
index 4f2113b..2eb81fc 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebHistoryItemTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebHistoryItemTest.java
@@ -55,7 +55,6 @@
assertEquals(1, list.getSize());
WebHistoryItem item = list.getCurrentItem();
assertNotNull(item);
- int firstId = item.getId();
assertEquals(url, item.getUrl());
assertEquals(url, item.getOriginalUrl());
assertEquals(TestHtmlConstants.HELLO_WORLD_TITLE, item.getTitle());
@@ -69,7 +68,5 @@
item = list.getCurrentItem();
assertNotNull(item);
assertEquals(TestHtmlConstants.BR_TAG_TITLE, item.getTitle());
- int secondId = item.getId();
- assertTrue(firstId != secondId);
}
}
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java b/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java
index 054217e..b9c338b 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java
@@ -25,6 +25,7 @@
import android.webkit.WebIconDatabase;
import android.webkit.WebSettings;
import android.webkit.WebSettings.TextSize;
+import android.webkit.WebStorage;
import android.webkit.WebView;
import android.webkit.cts.WebViewOnUiThread.WaitForProgressClient;
import java.io.FileOutputStream;
@@ -155,47 +156,6 @@
assertEquals(customUserAgent, mOnUiThread.getTitle());
}
- @SuppressWarnings("deprecation")
- public void testAccessUserAgent() throws Exception {
- startWebServer();
- String url = mWebServer.getUserAgentUrl();
-
- mSettings.setUserAgent(1);
- assertEquals(1, mSettings.getUserAgent());
- mOnUiThread.loadUrlAndWaitForCompletion(url);
- String userAgent1 = mOnUiThread.getTitle();
- assertNotNull(userAgent1);
-
- mSettings.setUserAgent(3);
- assertEquals(1, mSettings.getUserAgent());
- mOnUiThread.loadUrlAndWaitForCompletion(url);
- assertEquals(userAgent1, mOnUiThread.getTitle());
-
- mSettings.setUserAgent(2);
- assertEquals(2, mSettings.getUserAgent());
- mOnUiThread.loadUrlAndWaitForCompletion(url);
- String userAgent2 = mOnUiThread.getTitle();
- assertNotNull(userAgent2);
-
- mSettings.setUserAgent(3);
- assertEquals(2, mSettings.getUserAgent());
- mOnUiThread.loadUrlAndWaitForCompletion(url);
- assertEquals(userAgent2, mOnUiThread.getTitle());
-
- mSettings.setUserAgent(0);
- assertEquals(0, mSettings.getUserAgent());
- mOnUiThread.loadUrlAndWaitForCompletion(url);
- String userAgent0 = mOnUiThread.getTitle();
- assertNotNull(userAgent0);
-
- final String customUserAgent = "Cts/Test";
- mSettings.setUserAgentString(customUserAgent);
- assertEquals(-1, mSettings.getUserAgent());
- mOnUiThread.loadUrlAndWaitForCompletion(url);
- assertEquals(customUserAgent, mOnUiThread.getTitle());
- }
-
-
public void testAccessAllowFileAccess() {
// This test is not compatible with 4.0.3
if ("4.0.3".equals(Build.VERSION.RELEASE)) {
@@ -387,11 +347,9 @@
new PollingCheck(WEBVIEW_TIMEOUT) {
@Override
protected boolean check() {
- String title = mOnUiThread.getTitle();
- return title != null && title.length() > 0;
+ return "Popup blocked".equals(mOnUiThread.getTitle());
}
}.run();
- assertEquals("Popup blocked", mOnUiThread.getTitle());
mSettings.setJavaScriptCanOpenWindowsAutomatically(true);
assertTrue(mSettings.getJavaScriptCanOpenWindowsAutomatically());
@@ -399,15 +357,9 @@
new PollingCheck(WEBVIEW_TIMEOUT) {
@Override
protected boolean check() {
- String title = mOnUiThread.getTitle();
- // The title may not change immediately after loading, so
- // we have to discount the initial "Popup blocked" from the
- // previous load.
- return title != null && title.length() > 0
- && !title.equals("Popup blocked");
+ return "Popup allowed".equals(mOnUiThread.getTitle());
}
}.run();
- assertEquals("Popup allowed", mOnUiThread.getTitle());
}
public void testAccessJavaScriptEnabled() throws Exception {
@@ -417,10 +369,9 @@
new PollingCheck(WEBVIEW_TIMEOUT) {
@Override
protected boolean check() {
- return mOnUiThread.getTitle() != null;
+ return "javascript on".equals(mOnUiThread.getTitle());
}
}.run();
- assertEquals("javascript on", mOnUiThread.getTitle());
mSettings.setJavaScriptEnabled(false);
assertFalse(mSettings.getJavaScriptEnabled());
@@ -428,10 +379,10 @@
new PollingCheck(WEBVIEW_TIMEOUT) {
@Override
protected boolean check() {
- return mOnUiThread.getTitle() != null;
+ return "javascript off".equals(mOnUiThread.getTitle());
}
}.run();
- assertEquals("javascript off", mOnUiThread.getTitle());
+
}
public void testAccessLayoutAlgorithm() {
@@ -444,13 +395,6 @@
assertEquals(WebSettings.LayoutAlgorithm.SINGLE_COLUMN, mSettings.getLayoutAlgorithm());
}
- public void testAccessLightTouchEnabled() {
- assertFalse(mSettings.getLightTouchEnabled());
-
- mSettings.setLightTouchEnabled(true);
- assertTrue(mSettings.getLightTouchEnabled());
- }
-
public void testAccessMinimumFontSize() {
assertEquals(8, mSettings.getMinimumFontSize());
@@ -477,13 +421,6 @@
assertEquals(10, mSettings.getMinimumLogicalFontSize());
}
- public void testAccessNavDump() {
- assertFalse(mSettings.getNavDump());
-
- mSettings.setNavDump(true);
- assertTrue(mSettings.getNavDump());
- }
-
public void testAccessPluginsEnabled() {
assertFalse(mSettings.getPluginsEnabled());
@@ -507,13 +444,6 @@
assertFalse(mSettings.getSaveFormData());
}
- public void testAccessSavePassword() {
- assertTrue(mSettings.getSavePassword());
-
- mSettings.setSavePassword(false);
- assertFalse(mSettings.getSavePassword());
- }
-
public void testAccessTextSize() {
assertEquals(TextSize.NORMAL, mSettings.getTextSize());
@@ -646,6 +576,32 @@
}.run();
}
+ // Ideally, we need a test case for the enabled case. However, it seems that
+ // enabling the database should happen prior to navigating the first url due to
+ // some internal limitations of webview. For this reason, we only provide a
+ // test case for "disabled" behavior.
+ // Also loading as data rather than using URL should work, but it causes a
+ // security exception in JS, most likely due to cross domain access. So we load
+ // using a URL. Finally, it looks like enabling database requires creating a
+ // webChromeClient and listening to Quota callbacks, which is not documented.
+ public void testDatabaseDisabled() throws Throwable {
+ // Verify that websql database does not work when disabled.
+ startWebServer();
+
+ mOnUiThread.setWebChromeClient(new ChromeClient(mOnUiThread) {
+ @Override
+ public void onExceededDatabaseQuota(String url, String databaseId, long quota,
+ long estimatedSize, long total, WebStorage.QuotaUpdater updater) {
+ updater.updateQuota(estimatedSize);
+ }
+ });
+ mSettings.setJavaScriptEnabled(true);
+ mSettings.setDatabaseEnabled(false);
+ final String url = mWebServer.getAssetUrl(TestHtmlConstants.DATABASE_ACCESS_URL);
+ mOnUiThread.loadUrlAndWaitForCompletion(url);
+ assertEquals("No database", mOnUiThread.getTitle());
+ }
+
public void testLoadsImagesAutomatically() throws Throwable {
assertTrue(mSettings.getLoadsImagesAutomatically());
@@ -727,7 +683,7 @@
mOnUiThread.clearCache(true);
mOnUiThread.loadUrlAndWaitForCompletion(
mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL));
- assertEquals(TestHtmlConstants.WEBPAGE_NOT_AVAILABLE_TITLE, mOnUiThread.getTitle());
+ assertFalse(TestHtmlConstants.HELLO_WORLD_TITLE.equals(mOnUiThread.getTitle()));
mOnUiThread.loadDataAndWaitForCompletion(getNetworkImageHtml(), "text/html", null);
assertEquals(EMPTY_IMAGE_HEIGHT, mOnUiThread.getTitle());
mOnUiThread.loadDataAndWaitForCompletion(DATA_URL_IMAGE_HTML, "text/html", null);
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
index 6c66f52..44e95ac 100755
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
@@ -33,6 +33,8 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.os.StrictMode;
+import android.os.StrictMode.ThreadPolicy;
import android.os.SystemClock;
import android.test.ActivityInstrumentationTestCase2;
import android.test.UiThreadTest;
@@ -41,8 +43,6 @@
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
-import android.webkit.CacheManager;
-import android.webkit.CacheManager.CacheResult;
import android.webkit.ConsoleMessage;
import android.webkit.DownloadListener;
import android.webkit.JavascriptInterface;
@@ -124,6 +124,18 @@
mWebServer = new CtsTestServer(getActivity(), secure);
}
+ private void stopWebServer() throws Exception {
+ assertNotNull(mWebServer);
+ ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
+ ThreadPolicy tmpPolicy = new ThreadPolicy.Builder(oldPolicy)
+ .permitNetwork()
+ .build();
+ StrictMode.setThreadPolicy(tmpPolicy);
+ mWebServer.shutdown();
+ mWebServer = null;
+ StrictMode.setThreadPolicy(oldPolicy);
+ }
+
@UiThreadTest
public void testConstructor() {
new WebView(getActivity());
@@ -138,23 +150,11 @@
* http://en.wikipedia.org/wiki/Postal_address#United_States
* http://www.usps.com/
*/
- // full address, invalid zip code
- assertNull(WebView.findAddress("455 LARKSPUR DRIVE CALIFORNIA SPRINGS CALIFORNIA 92926"));
// full address
assertEquals("455 LARKSPUR DRIVE CALIFORNIA SPRINGS CALIFORNIA 92826",
WebView.findAddress("455 LARKSPUR DRIVE CALIFORNIA SPRINGS CALIFORNIA 92826"));
- // full address ( with abbreviated street type and state)
- assertEquals("455 LARKSPUR DR CALIFORNIA SPRINGS CA 92826",
- WebView.findAddress("455 LARKSPUR DR CALIFORNIA SPRINGS CA 92826"));
- // misspell the state ( CALIFORNIA -> CALIFONIA )
- assertNull(WebView.findAddress("455 LARKSPUR DRIVE CALIFORNIA SPRINGS CALIFONIA 92826"));
- // without optional zip code
- assertEquals("455 LARKSPUR DR CALIFORNIA SPRINGS CA",
- WebView.findAddress("455 LARKSPUR DR CALIFORNIA SPRINGS CA"));
- // house number, street name and street type are missing
- assertNull(WebView.findAddress("CALIFORNIA SPRINGS CA"));
- // city & state are missing
- assertNull(WebView.findAddress("455 LARKSPUR DR"));
+ // not an address
+ assertNull(WebView.findAddress("This is not an address: no town, no state, no zip."));
}
@SuppressWarnings("deprecation")
@@ -320,22 +320,38 @@
assertEquals(redirectUrl, mWebView.getOriginalUrl());
}
- @UiThreadTest
public void testStopLoading() throws Exception {
- assertNull(mWebView.getUrl());
- assertEquals(INITIAL_PROGRESS, mWebView.getProgress());
+ assertEquals(INITIAL_PROGRESS, mOnUiThread.getProgress());
startWebServer(false);
- String url = mWebServer.getDelayedAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
- mWebView.loadUrl(url);
- mWebView.stopLoading();
- new PollingCheck() {
- @Override
- protected boolean check() {
- return 100 == mWebView.getProgress();
+ String url = mWebServer.getDelayedAssetUrl(TestHtmlConstants.STOP_LOADING_URL);
+
+ class JsInterface {
+ private boolean mPageLoaded;
+
+ @JavascriptInterface
+ public synchronized void pageLoaded() {
+ mPageLoaded = true;
+ notify();
}
- }.run();
- assertNull(mWebView.getUrl());
+ public synchronized boolean getPageLoaded() {
+ return mPageLoaded;
+ }
+ }
+
+ JsInterface jsInterface = new JsInterface();
+
+ mOnUiThread.getSettings().setJavaScriptEnabled(true);
+ mOnUiThread.addJavascriptInterface(jsInterface, "javabridge");
+ mOnUiThread.loadUrl(url);
+ mOnUiThread.stopLoading();
+
+ // We wait to see that the onload callback in the HTML is not fired.
+ synchronized (jsInterface) {
+ jsInterface.wait(3000);
+ }
+
+ assertFalse(jsInterface.getPageLoaded());
}
@UiThreadTest
@@ -606,7 +622,7 @@
}
}.run();
assertEquals(mWebView, listener.webView);
- assertNotNull(listener.picture);
+ assertNull(listener.picture);
final int oldCallCount = listener.callCount;
final String newUrl = mWebServer.getAssetUrl(TestHtmlConstants.SMALL_IMG_URL);
@@ -619,66 +635,6 @@
}.run();
}
- public void testSaveAndRestorePicture() throws Throwable {
- mWebView.setBackgroundColor(Color.CYAN);
- startWebServer(false);
- final String url = mWebServer.getAssetUrl(TestHtmlConstants.BLANK_PAGE_URL);
- mOnUiThread.loadUrlAndWaitForCompletion(url);
- getInstrumentation().waitForIdleSync();
-
- final Bundle bundle = new Bundle();
- final File f = getActivity().getFileStreamPath("snapshot");
- if (f.exists()) {
- f.delete();
- }
-
- try {
- assertTrue(bundle.isEmpty());
- assertEquals(0, f.length());
- assertTrue(mOnUiThread.savePicture(bundle, f));
-
- // File saving is done in a separate thread.
- new PollingCheck() {
- @Override
- protected boolean check() {
- return f.length() > 0;
- }
- }.run();
-
- assertFalse(bundle.isEmpty());
-
- Picture p = Picture.createFromStream(new FileInputStream(f));
- Bitmap b = Bitmap.createBitmap(p.getWidth(), p.getHeight(), Config.ARGB_8888);
- p.draw(new Canvas(b));
- assertBitmapFillWithColor(b, Color.CYAN);
-
- mOnUiThread.setBackgroundColor(Color.WHITE);
- mOnUiThread.reloadAndWaitForCompletion();
- getInstrumentation().waitForIdleSync();
-
- runTestOnUiThread(new Runnable() {
- @Override
- public void run() {
- Bitmap b = Bitmap.createBitmap(mWebView.getWidth(), mWebView.getHeight(),
- Config.ARGB_8888);
- mWebView.draw(new Canvas(b));
- assertBitmapFillWithColor(b, Color.WHITE);
-
- // restorePicture is only supported in software rendering
- mWebView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
- assertTrue(mWebView.restorePicture(bundle, f));
- }
- });
- getInstrumentation().waitForIdleSync();
- // Cannot test whether the picture has been restored successfully.
- // Drawing the webview into a canvas will draw white, but on the display it is cyan
- } finally {
- if (f.exists()) {
- f.delete();
- }
- }
- }
-
@UiThreadTest
public void testAccessHttpAuthUsernamePassword() {
try {
@@ -741,28 +697,10 @@
}
}
- @UiThreadTest
- public void testSavePassword() {
- WebViewDatabase db = WebViewDatabase.getInstance(getActivity());
- try {
- db.clearUsernamePassword();
-
- String host = "http://localhost:8080";
- String userName = "user";
- String password = "password";
- assertFalse(db.hasUsernamePassword());
- mWebView.savePassword(host, userName, password);
- assertTrue(db.hasUsernamePassword());
- } finally {
- db.clearUsernamePassword();
- }
- }
-
public void testLoadData() throws Throwable {
final String HTML_CONTENT =
"<html><head><title>Hello,World!</title></head><body></body>" +
"</html>";
- assertNull(mOnUiThread.getTitle());
mOnUiThread.loadDataAndWaitForCompletion(HTML_CONTENT,
"text/html", null);
assertEquals("Hello,World!", mOnUiThread.getTitle());
@@ -788,14 +726,13 @@
@UiThreadTest
public void testLoadDataWithBaseUrl() throws Throwable {
- assertNull(mWebView.getTitle());
assertNull(mWebView.getUrl());
String imgUrl = TestHtmlConstants.SMALL_IMG_URL; // relative
// Check that we can access relative URLs and that reported URL is supplied history URL.
startWebServer(false);
String baseUrl = mWebServer.getAssetUrl("foo.html");
- String historyUrl = "random";
+ String historyUrl = "http://www.example.com/";
String dbPath = getActivity().getFilesDir().toString() + "/icons";
mIconDb = WebIconDatabase.getInstance();
mIconDb.open(dbPath);
@@ -824,7 +761,7 @@
mOnUiThread.loadDataWithBaseURLAndWaitForCompletion(baseUrl,
"<html><body><img src=\"" + imgUrl + "\"/></body></html>",
"text/html", "UTF-8", null);
- assertTrue(mWebServer.getLastRequestUrl().endsWith(imgUrl));
+ assertTrue("last request is " + mWebServer.getLastRequestUrl(), mWebServer.getLastRequestUrl().endsWith(imgUrl));
assertEquals("about:blank", mWebView.getUrl());
// Test that JavaScript can access content from the same origin as the base URL.
@@ -842,8 +779,8 @@
mOnUiThread.loadDataWithBaseURLAndWaitForCompletion("data:foo",
"<html><body>bar</body></html>", "text/html", "UTF-8",
historyUrl);
- assertTrue(mWebView.getUrl().indexOf("data:text/html,") == 0);
- assertTrue(mWebView.getUrl().indexOf("bar") > 0);
+ assertTrue("URL: " + mWebView.getUrl(), mWebView.getUrl().indexOf("data:text/html") == 0);
+ assertTrue("URL: " + mWebView.getUrl(), mWebView.getUrl().indexOf("bar") > 0);
}
@UiThreadTest
@@ -1024,40 +961,6 @@
}
@UiThreadTest
- public void testClearCache() throws Exception {
- final File cacheFileBaseDir = CacheManager.getCacheFileBaseDir();
- mWebView.clearCache(true);
- assertEquals(0, cacheFileBaseDir.list().length);
-
- startWebServer(false);
- final String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
- mOnUiThread.loadUrlAndWaitForCompletion(url);
- new PollingCheck(TEST_TIMEOUT) {
- @Override
- protected boolean check() {
- CacheResult result = CacheManager.getCacheFile(url, null);
- return result != null;
- }
- }.run();
- int cacheFileCount = cacheFileBaseDir.list().length;
- assertTrue(cacheFileCount > 0);
-
- mWebView.clearCache(false);
- // the cache files are still there
- // can not check other effects of the method
- assertEquals(cacheFileCount, cacheFileBaseDir.list().length);
-
- mWebView.clearCache(true);
- // check the files are deleted
- new PollingCheck(TEST_TIMEOUT) {
- @Override
- protected boolean check() {
- return cacheFileBaseDir.list().length == 0;
- }
- }.run();
- }
-
- @UiThreadTest
public void testPlatformNotifications() {
WebView.enablePlatformNotifications();
WebView.disablePlatformNotifications();
@@ -1123,7 +1026,7 @@
// focus on first link
handler.reset();
- getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_DOWN);
+ getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_TAB);
mOnUiThread.requestFocusNodeHref(hrefMsg);
new PollingCheck() {
@Override
@@ -1148,7 +1051,7 @@
handler.reset();
final Message hrefMsg2 = new Message();
hrefMsg2.setTarget(handler);
- getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_DOWN);
+ getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_TAB);
mOnUiThread.requestFocusNodeHref(hrefMsg2);
new PollingCheck() {
@Override
@@ -1475,21 +1378,30 @@
}
@UiThreadTest
- public void testSetAndGetCertificate() {
- assertNull(mWebView.getCertificate());
- SslCertificate certificate = new SslCertificate("foo", "bar", new Date(42), new Date(43));
- mWebView.setCertificate(certificate);
- assertEquals(certificate, mWebView.getCertificate());
- }
-
- @UiThreadTest
public void testInsecureSiteClearsCertificate() throws Throwable {
- final SslCertificate certificate =
- new SslCertificate("foo", "bar", new Date(42), new Date(43));
+ final class MockWebViewClient extends WaitForLoadedClient {
+ public MockWebViewClient() {
+ super(mOnUiThread);
+ }
+ @Override
+ public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
+ handler.proceed();
+ }
+ }
+
+ startWebServer(true);
+ mOnUiThread.setWebViewClient(new MockWebViewClient());
+ mOnUiThread.loadUrlAndWaitForCompletion(
+ mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL));
+ SslCertificate cert = mWebView.getCertificate();
+ assertNotNull(cert);
+ assertEquals("Android", cert.getIssuedTo().getUName());
+
+ stopWebServer();
+
startWebServer(false);
- final String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
- mWebView.setCertificate(certificate);
- mOnUiThread.loadUrlAndWaitForCompletion(url);
+ mOnUiThread.loadUrlAndWaitForCompletion(
+ mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL));
assertNull(mWebView.getCertificate());
}
@@ -1505,20 +1417,48 @@
}
}
+ startWebServer(false);
+ mOnUiThread.loadUrlAndWaitForCompletion(
+ mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL));
+ assertNull(mWebView.getCertificate());
+
+ stopWebServer();
+
startWebServer(true);
- final String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
mOnUiThread.setWebViewClient(new MockWebViewClient());
- mWebView.setCertificate(null);
- mOnUiThread.loadUrlAndWaitForCompletion(url);
+ mOnUiThread.loadUrlAndWaitForCompletion(
+ mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL));
SslCertificate cert = mWebView.getCertificate();
assertNotNull(cert);
assertEquals("Android", cert.getIssuedTo().getUName());
}
@UiThreadTest
- public void testClearSslPreferences() {
- // FIXME: Implement this. See http://b/5378046.
- mWebView.clearSslPreferences();
+ public void testClearSslPreferences() throws Throwable {
+ // Load the first page. We expect a call to
+ // WebViewClient.onReceivedSslError().
+ final SslErrorWebViewClient webViewClient = new SslErrorWebViewClient();
+ startWebServer(true);
+ final String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
+ mOnUiThread.setWebViewClient(webViewClient);
+ mOnUiThread.clearSslPreferences();
+ mOnUiThread.loadUrlAndWaitForCompletion(url);
+ assertTrue(webViewClient.wasOnReceivedSslErrorCalled());
+
+ // Load the page again. We expect another call to
+ // WebViewClient.onReceivedSslError() since we cleared sslpreferences.
+ mOnUiThread.clearSslPreferences();
+ webViewClient.resetWasOnReceivedSslErrorCalled();
+ mOnUiThread.loadUrlAndWaitForCompletion(url);
+ assertTrue(webViewClient.wasOnReceivedSslErrorCalled());
+ assertEquals(TestHtmlConstants.HELLO_WORLD_TITLE, mOnUiThread.getTitle());
+
+ // Load the page once again, without clearing the sslpreferences.
+ // Make sure we do not get the callback.
+ webViewClient.resetWasOnReceivedSslErrorCalled();
+ mOnUiThread.loadUrlAndWaitForCompletion(url);
+ assertFalse(webViewClient.wasOnReceivedSslErrorCalled());
+ assertEquals(TestHtmlConstants.HELLO_WORLD_TITLE, mOnUiThread.getTitle());
}
public void testOnReceivedSslError() throws Throwable {
@@ -1685,15 +1625,13 @@
// WebViewClient.shouldOverrideUrlLoading() returns false, so
// the WebView will load the new URL.
mOnUiThread.setDownloadListener(listener);
+ mWebView.getSettings().setJavaScriptEnabled(true);
mOnUiThread.loadDataAndWaitForCompletion(
- "<html><body><a href=\"" + url
- + "\">link</a></body></html>",
+ "<html><body onload=\"window.location = \'" + url + "\'\"></body></html>",
"text/html", null);
// Wait for layout to complete before setting focus.
getInstrumentation().waitForIdleSync();
- assertTrue(mOnUiThread.requestFocus(View.FOCUS_DOWN, null));
- getInstrumentation().waitForIdleSync();
- getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER);
+
new PollingCheck(TEST_TIMEOUT) {
@Override
protected boolean check() {
@@ -1719,22 +1657,34 @@
mWebView.setMapTrackballToArrowKeys(true);
}
- @UiThreadTest
public void testSetNetworkAvailable() throws Exception {
- WebSettings settings = mWebView.getSettings();
+ WebSettings settings = mOnUiThread.getSettings();
settings.setJavaScriptEnabled(true);
startWebServer(false);
+
String url = mWebServer.getAssetUrl(TestHtmlConstants.NETWORK_STATE_URL);
mOnUiThread.loadUrlAndWaitForCompletion(url);
- assertEquals("ONLINE", mWebView.getTitle());
+ assertEquals("ONLINE", mOnUiThread.getTitle());
mWebView.setNetworkAvailable(false);
- mOnUiThread.reloadAndWaitForCompletion();
- assertEquals("OFFLINE", mWebView.getTitle());
+
+ // Wait for the DOM to receive notification of the network state change.
+ new PollingCheck(TEST_TIMEOUT) {
+ @Override
+ protected boolean check() {
+ return mOnUiThread.getTitle().equals("OFFLINE");
+ }
+ }.run();
mWebView.setNetworkAvailable(true);
- mOnUiThread.reloadAndWaitForCompletion();
- assertEquals("ONLINE", mWebView.getTitle());
+
+ // Wait for the DOM to receive notification of the network state change.
+ new PollingCheck(TEST_TIMEOUT) {
+ @Override
+ protected boolean check() {
+ return mOnUiThread.getTitle().equals("ONLINE");
+ }
+ }.run();
}
public void testSetWebChromeClient() throws Throwable {
@@ -1887,7 +1837,7 @@
private void moveFocusDown() throws Throwable {
// send down key and wait for idle
- sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_TAB);
// waiting for idle isn't always sufficient for the key to be fully processed
Thread.sleep(500);
}
@@ -2012,6 +1962,7 @@
// Note that this class is not thread-safe.
final class SslErrorWebViewClient extends WaitForLoadedClient {
private boolean mWasOnReceivedSslErrorCalled;
+ private String mErrorUrl;
public SslErrorWebViewClient() {
super(mOnUiThread);
@@ -2019,6 +1970,7 @@
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
mWasOnReceivedSslErrorCalled = true;
+ mErrorUrl = error.getUrl();
handler.proceed();
}
public void resetWasOnReceivedSslErrorCalled() {
@@ -2027,5 +1979,8 @@
public boolean wasOnReceivedSslErrorCalled() {
return mWasOnReceivedSslErrorCalled;
}
+ public String errorUrl() {
+ return mErrorUrl;
+ }
}
}
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebView_WebViewTransportTest.java b/tests/tests/webkit/src/android/webkit/cts/WebView_WebViewTransportTest.java
index eaa5e69..dd784be 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebView_WebViewTransportTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebView_WebViewTransportTest.java
@@ -16,16 +16,22 @@
package android.webkit.cts;
-import android.test.AndroidTestCase;
+import android.test.ActivityInstrumentationTestCase2;
import android.test.UiThreadTest;
import android.webkit.WebView;
import android.webkit.WebView.WebViewTransport;
-public class WebView_WebViewTransportTest extends AndroidTestCase {
+public class WebView_WebViewTransportTest
+ extends ActivityInstrumentationTestCase2<WebViewStubActivity> {
+
+ public WebView_WebViewTransportTest() {
+ super("com.android.cts.stub", WebViewStubActivity.class);
+ }
+
@UiThreadTest
public void testAccessWebView() {
- WebView webView = new WebView(mContext);
+ WebView webView = getActivity().getWebView();
WebViewTransport transport = webView.new WebViewTransport();
assertNull(transport.getWebView());
diff --git a/tests/tests/widget/src/android/widget/cts/GridLayoutTest.java b/tests/tests/widget/src/android/widget/cts/GridLayoutTest.java
new file mode 100644
index 0000000..638fe96
--- /dev/null
+++ b/tests/tests/widget/src/android/widget/cts/GridLayoutTest.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.cts;
+
+import android.content.Context;
+import android.test.ActivityInstrumentationTestCase;
+import android.util.AttributeSet;
+import android.util.Xml;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsoluteLayout;
+import android.widget.Button;
+import android.widget.GridLayout;
+import android.widget.TextView;
+import com.android.cts.stub.R;
+import org.xmlpull.v1.XmlPullParser;
+
+import static android.view.ViewGroup.LAYOUT_MODE_OPTICAL_BOUNDS;
+import static android.widget.GridLayout.spec;
+
+/**
+ * Test {@link android.widget.GridLayout}.
+ */
+public class GridLayoutTest extends ActivityInstrumentationTestCase<GridLayoutStubActivity> {
+
+ // The size of the off-screen test container in which we we will testing layout.
+ public static final int MAX_X = 2000;
+ public static final int MAX_Y = 2000;
+
+ private static abstract class Alignment {
+ String name;
+ int gravity;
+
+ abstract int getValue(View v);
+
+ protected Alignment(String name, int gravity) {
+ this.name = name;
+ this.gravity = gravity;
+ }
+ }
+
+ private static final Alignment[] HORIZONTAL_ALIGNMENTS = {
+ new Alignment("LEFT", Gravity.LEFT) {
+ @Override
+ int getValue(View v) {
+ return v.getLeft();
+ }
+ },
+ new Alignment("CENTER", Gravity.CENTER_HORIZONTAL) {
+ @Override
+ int getValue(View v) {
+ return (v.getLeft() + v.getRight()) / 2;
+ }
+ },
+ new Alignment("RIGHT", Gravity.RIGHT) {
+ @Override
+ int getValue(View v) {
+ return v.getRight();
+ }
+ },
+ new Alignment("FILL", Gravity.FILL_HORIZONTAL) {
+ @Override
+ int getValue(View v) {
+ return v.getWidth();
+ }
+ }
+ };
+
+ private static final Alignment[] VERTICAL_ALIGNMENTS = {
+ new Alignment("TOP", Gravity.TOP) {
+ @Override
+ int getValue(View v) {
+ return v.getTop();
+ }
+ },
+ new Alignment("CENTER", Gravity.CENTER_VERTICAL) {
+ @Override
+ int getValue(View v) {
+ return (v.getTop() + v.getBottom()) / 2;
+ }
+ },
+ new Alignment("BASELINE", Gravity.NO_GRAVITY) {
+ @Override
+ int getValue(View v) {
+ return v.getTop() + v.getBaseline();
+ }
+ },
+ new Alignment("BOTTOM", Gravity.BOTTOM) {
+ @Override
+ int getValue(View v) {
+ return v.getBottom();
+ }
+ },
+ new Alignment("FILL", Gravity.FILL_VERTICAL) {
+ @Override
+ int getValue(View v) {
+ return v.getHeight();
+ }
+ }
+ };
+
+ private Context mContext;
+
+ public GridLayoutTest() {
+ super("com.android.cts.stub", GridLayoutStubActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mContext = getInstrumentation().getTargetContext();
+ }
+
+ public void testConstructor() {
+ new GridLayout(mContext);
+
+ new GridLayout(mContext, null);
+
+ XmlPullParser parser = mContext.getResources().getXml(R.layout.gridlayout_layout);
+ AttributeSet attrs = Xml.asAttributeSet(parser);
+ new GridLayout(mContext, attrs);
+
+ try {
+ new GridLayout(null, null);
+ fail("should throw NullPointerException.");
+ } catch (NullPointerException e) {
+ }
+ }
+
+ public void testCheckLayoutParams() {
+ GridLayout gridLayout = new GridLayout(mContext);
+
+ gridLayout.addView(new TextView(mContext), new AbsoluteLayout.LayoutParams(0, 0, 0, 0));
+
+ gridLayout.addView(new TextView(mContext), new GridLayout.LayoutParams(
+ GridLayout.spec(0),
+ GridLayout.spec(0)));
+
+ }
+
+ public void testGenerateDefaultLayoutParams() {
+ GridLayout gridLayout = new GridLayout(mContext);
+ ViewGroup.LayoutParams lp = gridLayout.generateLayoutParams(null);
+ assertNotNull(lp);
+ assertTrue(lp instanceof GridLayout.LayoutParams);
+ assertEquals(ViewGroup.LayoutParams.WRAP_CONTENT, lp.width);
+ assertEquals(ViewGroup.LayoutParams.WRAP_CONTENT, lp.height);
+ }
+
+ private View[][] populate(GridLayout container) {
+ Context context = container.getContext();
+ int N = VERTICAL_ALIGNMENTS.length;
+ int M = HORIZONTAL_ALIGNMENTS.length;
+
+ View[][] table = new View[N + 1][M + 1];
+
+ {
+ TextView v = new TextView(context);
+ GridLayout.LayoutParams lp = new GridLayout.LayoutParams(spec(0), spec(0));
+ lp.setGravity(Gravity.CENTER);
+ v.setText("*");
+ container.addView(v, lp);
+ }
+ for (int i = 0; i < N; i++) {
+ Alignment va = VERTICAL_ALIGNMENTS[i];
+ int row = i + 1;
+ GridLayout.LayoutParams lp = new GridLayout.LayoutParams(spec(row), spec(0));
+ lp.setGravity(va.gravity | Gravity.CENTER_HORIZONTAL);
+ TextView v = new TextView(context);
+ v.setGravity(Gravity.CENTER);
+ v.setText(va.name);
+ container.addView(v, lp);
+ table[row][0] = v;
+ }
+ for (int j = 0; j < M; j++) {
+ Alignment ha = HORIZONTAL_ALIGNMENTS[j];
+ int col = j + 1;
+ GridLayout.LayoutParams lp = new GridLayout.LayoutParams(spec(0), spec(col));
+ lp.setGravity(Gravity.CENTER_VERTICAL | ha.gravity);
+ TextView v = new TextView(context);
+ v.setGravity(Gravity.CENTER);
+ v.setText(ha.name);
+ container.addView(v, lp);
+ table[0][col] = v;
+ }
+ for (int i = 0; i < N; i++) {
+ for (int j = 0; j < M; j++) {
+ Alignment ha = HORIZONTAL_ALIGNMENTS[j];
+ Alignment va = VERTICAL_ALIGNMENTS[i];
+ int row = i + 1;
+ int col = j + 1;
+ GridLayout.LayoutParams lp = new GridLayout.LayoutParams(spec(row), spec(col));
+ lp.setGravity(va.gravity | ha.gravity);
+ TextView v = new Button(context);
+ v.setText("X");
+ v.setTextSize(10 + 5 * row * col);
+ container.addView(v, lp);
+ table[row][col] = v;
+ }
+ }
+ return table;
+ }
+
+ private void testAlignment(int row, int col, Alignment a, View v0, View v1, String group) {
+ int a0 = a.getValue(v0);
+ int a1 = a.getValue(v1);
+ assertEquals("View at row " + row + ", column " + col + " was not " + a.name +
+ " aligned with its title " + group + ";",
+ a0, a1);
+ }
+
+ private void test(GridLayout p, View[][] table) {
+ p.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
+ p.layout(0, 0, MAX_X, MAX_Y);
+
+ int N = VERTICAL_ALIGNMENTS.length;
+ int M = HORIZONTAL_ALIGNMENTS.length;
+
+ // test all horizontal alignments in each column
+ for (int j = 0; j < M; j++) {
+ int col = j + 1;
+ View v0 = table[0][col];
+ Alignment alignment = HORIZONTAL_ALIGNMENTS[j];
+ for (int i = 0; i < N; i++) {
+ int row = i + 1;
+ testAlignment(row, col, alignment, v0, table[row][col], "column");
+ }
+ }
+
+ // test all vertical alignments in each row
+ for (int i = 0; i < N; i++) {
+ int row = i + 1;
+ View v0 = table[row][0];
+ Alignment alignment = VERTICAL_ALIGNMENTS[i];
+ for (int j = 0; j < M; j++) {
+ int col = j + 1;
+ testAlignment(row, col, alignment, v0, table[row][col], "row");
+ }
+ }
+ }
+ public void testAlignment() {
+ GridLayout p = new GridLayout(mContext);
+ View[][] table = populate(p);
+ test(p, table);
+ //p.setLayoutMode(ViewGroup.LAYOUT_MODE_OPTICAL_BOUNDS);
+ //test(p, table);
+ }
+}
diff --git a/tests/tests/widget/src/android/widget/cts/ViewFlipperTest.java b/tests/tests/widget/src/android/widget/cts/ViewFlipperTest.java
index bcb417a..f223466 100644
--- a/tests/tests/widget/src/android/widget/cts/ViewFlipperTest.java
+++ b/tests/tests/widget/src/android/widget/cts/ViewFlipperTest.java
@@ -93,6 +93,7 @@
// wait for a longer time to make sure the view flipping is completed.
waitForViewFlipping(FLIP_INTERVAL + 200);
+ getInstrumentation().waitForIdleSync();
runTestOnUiThread(new Runnable() {
public void run() {
ViewFlipper viewFlipper =
@@ -108,6 +109,7 @@
});
waitForViewFlipping(FLIP_INTERVAL + 200);
+ getInstrumentation().waitForIdleSync();
runTestOnUiThread(new Runnable() {
public void run() {
ViewFlipper viewFlipper =
diff --git a/tests/uiautomator/Android.mk b/tests/uiautomator/Android.mk
index 68b1dc2..d0d4b8e 100644
--- a/tests/uiautomator/Android.mk
+++ b/tests/uiautomator/Android.mk
@@ -22,7 +22,7 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_MODULE := CtsUiAutomatorTests
-LOCAL_JAVA_LIBRARIES := uiautomator_sdk_v17
+LOCAL_JAVA_LIBRARIES := uiautomator.core
LOCAL_PROGUARD_ENABLED := disabled
LOCAL_CTS_TEST_APK := CtsUiAutomatorApp
LOCAL_CTS_TEST_APP_PACKAGE := com.android.cts.uiautomator
diff --git a/tests/uiautomator/src/com/android/cts/uiautomatortest/CtsUiAutomatorTest.java b/tests/uiautomator/src/com/android/cts/uiautomatortest/CtsUiAutomatorTest.java
index 5de9805..ce6c02d 100644
--- a/tests/uiautomator/src/com/android/cts/uiautomatortest/CtsUiAutomatorTest.java
+++ b/tests/uiautomator/src/com/android/cts/uiautomatortest/CtsUiAutomatorTest.java
@@ -15,6 +15,7 @@
*/
package com.android.cts.uiautomatortest;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.RemoteException;
import android.os.SystemClock;
@@ -54,6 +55,7 @@
protected void setUp() throws Exception {
super.setUp();
// Make sure the test app is always running
+ UiDevice.getInstance().waitForIdle();
if (!new UiObject(new UiSelector().packageName(PKG_NAME)).exists())
runShellCommand(LAUNCH_APP);
}
@@ -195,14 +197,17 @@
}
/**
- * Test if a the content changed due to an action can be verified
+ * Test when a node's state is changed due to an action, it is updated in the accessibility
+ * hierarchy.
*
* @throws UiObjectNotFoundException
*/
public void testSelectAfterContentChanged() throws UiObjectNotFoundException {
openTest("Test 2");
- getObjectByText("Before").click();
- getObjectByText("After").click();
+ UiObject dynaButton = getObjectByText("Before");
+ dynaButton.click();
+ assertTrue("Button state change is not refreshed in accessibility hierarchy",
+ getObjectByText("After").exists());
}
/**
@@ -288,12 +293,12 @@
int totalDelay = Integer.parseInt(timeDiff);
// Cumulative waits in this test should add up to at minimum 30 seconds
- assertFalse("Timeout for wait-for-idle is too short. Expecting minimum 10 seconds",
+ assertFalse("Timeout for wait-for-idle is too short. Expecting minimum 30 seconds",
totalDelay < 30 * 1000);
// allow for tolerance in time measurements due to differences between
// device speeds
- assertFalse("Timeout for wait-for-idle is too long. Expecting maximum 15 seconds",
+ assertFalse("Timeout for wait-for-idle is too long. Expecting maximum 60 seconds",
totalDelay > 60 * 1000);
}
@@ -324,26 +329,28 @@
UiObject textView = new UiObject(new UiSelector().textContains("["));
textView.swipeLeft(10);
- assertTrue("UiObject swipe left", "[ 2 ]".equals(textView.getText()));
+ assertTrue("UiObject swipe left 1->2", "[ 2 ]".equals(textView.getText()));
textView.swipeLeft(10);
- assertTrue("UiObject swipe left", "[ 3 ]".equals(textView.getText()));
+ assertTrue("UiObject swipe left 2->3", "[ 3 ]".equals(textView.getText()));
textView.swipeLeft(10);
- assertTrue("UiObject swipe left", "[ 4 ]".equals(textView.getText()));
+ assertTrue("UiObject swipe left 3->4", "[ 4 ]".equals(textView.getText()));
textView.swipeRight(10);
- assertTrue("UiObject swipe right", "[ 3 ]".equals(textView.getText()));
+ assertTrue("UiObject swipe right 3<-4", "[ 3 ]".equals(textView.getText()));
textView.swipeRight(10);
- assertTrue("UiObject swipe right", "[ 2 ]".equals(textView.getText()));
+ assertTrue("UiObject swipe right 2<-3", "[ 2 ]".equals(textView.getText()));
textView.swipeRight(10);
- assertTrue("UiObject swipe right", "[ 1 ]".equals(textView.getText()));
+ assertTrue("UiObject swipe right 1<-2", "[ 1 ]".equals(textView.getText()));
Rect tb = textView.getBounds();
UiDevice.getInstance().swipe(tb.right - 20, tb.centerY(), tb.left + 20, tb.centerY(), 50);
- assertTrue("UiDevice swipe", "[ 2 ]".equals(textView.getText()));
+
+ SystemClock.sleep(100);
+ assertTrue("UiDevice raw swipe 1->2", "[ 2 ]".equals(textView.getText()));
}
/**
@@ -370,45 +377,10 @@
}
/**
- * The view contains a WebView with static content. This test uses the text
- * traversal feature of pressing down arrows to read the view's contents
- *
+ * Test when an object does not exist, an exception is thrown
* @throws UiObjectNotFoundException
*/
- /*// broken in MR1
- public void testWebViewTextTraversal() throws UiObjectNotFoundException {
- openTest("Test 6");
- UiObject webView = new UiObject(new UiSelector().className(android.webkit.WebView.class
- .getName()));
- webView.clickTopLeft();
- UiDevice device = UiDevice.getInstance();
- device.clearLastTraversedText();
-
- device.pressDPadDown();
- String text = device.getLastTraversedText();
- assertTrue("Read regular text", text.contains("This is test <b>6</b>"));
-
- device.pressDPadDown();
- text = device.getLastTraversedText();
- assertTrue("Anchor text", text.contains("<a"));
-
- device.pressDPadDown();
- text = device.getLastTraversedText();
- assertTrue("h5 text", text.contains("h5"));
-
- device.pressDPadDown();
- text = device.getLastTraversedText();
- assertTrue("Anchor text", text.contains("<a"));
-
- device.pressDPadDown();
- text = device.getLastTraversedText();
- assertTrue("h4 text", text.contains("h4"));
- }*/
-
- /**
- * Test when an object does not exist, an exception is thrown
- */
- public void testExceptionObjectNotFound() {
+ public void testExceptionObjectNotFound() throws UiObjectNotFoundException {
UiSelector selector = new UiSelector().text("Nothing should be found");
UiSelector child = new UiSelector().className("Nothing");
UiObject obj = new UiObject(selector.childSelector(child));
@@ -760,6 +732,7 @@
* @since API Level 17
*/
public void testSelectorLongClickableProperty() throws UiObjectNotFoundException {
+ openTest("Test 2");
UiObject button3 = new UiObject(new UiSelector().className(
android.widget.Button.class).longClickable(true).instance(2));
button3.longClick();
@@ -781,6 +754,236 @@
}
/**
+ * Verifies the 'Resource-Id' property of UiSelector
+ *
+ * @throws UiObjectNotFoundException
+ * @since API Level 18
+ */
+ public void testSelectorResourceId() throws UiObjectNotFoundException {
+ openTest("Test 5");
+ UiSelector toggleSelector =
+ new UiSelector().resourceId("com.android.cts.uiautomator:id/test_5_toggleButton");
+ UiObject toggleButton = new UiObject(toggleSelector);
+ assertTrue("Object with selector resource-id not found", toggleButton.exists());
+ assertTrue("Incorrect object for selector resource-id returned",
+ "OFF".equals(toggleButton.getText()) || "ON".equals(toggleButton.getText()));
+ }
+
+ /**
+ * Performs a pinch out from the center of a view to its edges and listens to
+ * the motion events to make sure the starting and ending points of both pointers
+ * are correct.
+ *
+ * @throws UiObjectNotFoundException
+ * @since API Level 18
+ */
+ public void testPinchOut() throws UiObjectNotFoundException {
+ openTest("Test 12");
+
+ UiObject screen = new UiObject(
+ new UiSelector().description("Details View"));
+
+ // get the current view dimensions
+ Rect screenRect = screen.getBounds();
+
+ // perform the pinch for 100% of the view dimensions starting form
+ // the center out to the edges.
+ screen.pinchOut(100, 30);
+
+ // dialog with the detected pointers motion coordinates is displayed.
+ UiObject results = new UiObject(new UiSelector().className(
+ android.widget.ScrollView.class).childSelector(new UiSelector().className(
+ android.widget.TextView.class)));
+ String allPointers = results.getText();
+ new UiObject(new UiSelector().text("OK")).click(); // dismiss dialog
+
+ // parse pointer 1
+ Point p1s = parsePointerCoordinates(allPointers, 0, 0); // start
+ Point p1e = parsePointerCoordinates(allPointers, 0, 1); // end
+ // parse pointer 2
+ Point p2s = parsePointerCoordinates(allPointers, 1, 0); // start
+ Point p2e = parsePointerCoordinates(allPointers, 1, 1); // end
+
+ assertTrue("All Y axis coordinates for pointer 1 must be the same", p1s.y == p1e.y);
+ assertTrue("All Y axis coordinates for pointer 2 must be the same", p2s.y == p2e.y);
+ assertTrue("All Y axis coordinates for both pointers must be the same", p1s.y == p2s.y);
+ assertTrue("Pinch must be in center of target view", p2s.y == screenRect.centerY());
+
+ assertTrue("Touch-down X coordinate for pointer 1 is invalid",
+ withinMarginOfError(0.1f, screenRect.centerX(), p1s.x));
+
+ assertTrue("Touch-down X coordinate for pointer 2 is invalid",
+ withinMarginOfError(0.1f, screenRect.centerX(), p2s.x));
+
+ assertTrue("Touch-up X coordinate for pointer 1 is invalid",
+ withinMarginOfError(0.1f, screenRect.centerX() - screenRect.left,
+ screenRect.centerX() - p1e.x));
+
+ assertTrue("Touch-up X coordinate for pointer 2 is invalid",
+ withinMarginOfError(0.1f, screenRect.right, p2e.x));
+ }
+
+ /**
+ * Performs a pinch in from the edges of a view to its center and listens to
+ * the motion events to make sure the starting and ending points of both pointers
+ * are correct.
+ *
+ * @throws UiObjectNotFoundException
+ * @since API Level 18
+ */
+ public void testPinchIn() throws UiObjectNotFoundException {
+ openTest("Test 12");
+
+ UiObject screen = new UiObject(
+ new UiSelector().description("Details View"));
+
+ // get the current view dimensions
+ Rect screenRect = screen.getBounds();
+
+ // perform the pinch for 100% of the view dimensions starting form
+ // the edges in towards the center.
+ screen.pinchIn(100, 30);
+
+ // dialog with the detected pointers motion coordinates is displayed.
+ UiObject results = new UiObject(new UiSelector().className(
+ android.widget.ScrollView.class).childSelector(new UiSelector().className(
+ android.widget.TextView.class)));
+ String allPointers = results.getText();
+ new UiObject(new UiSelector().text("OK")).click(); // dismiss dialog
+
+ // parse pointer 1
+ Point p1s = parsePointerCoordinates(allPointers, 0, 0); // start
+ Point p1e = parsePointerCoordinates(allPointers, 0, 1); // end
+ // parse pointer 2
+ Point p2s = parsePointerCoordinates(allPointers, 1, 0); // start
+ Point p2e = parsePointerCoordinates(allPointers, 1, 1); // end
+
+ assertTrue("All Y axis coordinates for pointer 1 must be the same", p1s.y == p1e.y);
+ assertTrue("All Y axis coordinates for pointer 2 must be the same", p2s.y == p2e.y);
+ assertTrue("All Y axis coordinates for both pointers must be the same", p1s.y == p2s.y);
+ assertTrue("Pinch must be in center of target view", p2s.y == screenRect.centerY());
+
+ assertTrue("Touch-down X coordinate for pointer 1 is invalid",
+ withinMarginOfError(0.1f, screenRect.centerX() - screenRect.left,
+ screenRect.centerX() - p1s.x));
+
+ assertTrue("Touch-down X coordinate for pointer 2 is invalid",
+ withinMarginOfError(0.1f, screenRect.right, p2s.x));
+
+ assertTrue("Touch-up X coordinate for pointer 1 is invalid",
+ withinMarginOfError(0.1f, screenRect.centerX(), p1e.x));
+
+ assertTrue("Touch-up X coordinate for pointer 2 is invalid",
+ withinMarginOfError(0.1f, screenRect.centerX(), p2e.x));
+ }
+
+ /**
+ * Performs a drag and drop operation from one UiObject to another UiObject
+ *
+ * @throws UiObjectNotFoundException
+ * @since API Level 18
+ */
+ public void testDragToObject() throws UiObjectNotFoundException {
+ openTest("Test 5");
+
+ UiObject imageButton = new UiObject(new UiSelector().description("Image button"));
+ UiObject starsBar = new UiObject(new UiSelector().className(android.widget.RatingBar.class));
+
+ Rect starsBarRect = starsBar.getBounds();
+ Rect imageButtonRect = imageButton.getBounds();
+ imageButton.dragTo(starsBar, 30);
+
+ // dialog with the detected pointers motion coordinates is displayed.
+ UiObject results = new UiObject(new UiSelector().className(
+ android.widget.ScrollView.class).childSelector(new UiSelector().className(
+ android.widget.TextView.class)));
+ String allPointers = results.getText();
+ new UiObject(new UiSelector().text("OK")).click(); // dismiss dialog
+
+ // parse pointer 1
+ Point p1s = parsePointerCoordinates(allPointers, 0, 0); // start
+ Point p1e = parsePointerCoordinates(allPointers, 0, 1); // end
+
+ assertTrue("Invalid touch starting.X reported",
+ withinMarginOfError(0.05f, imageButtonRect.centerX(), p1s.x));
+ assertTrue("Invalid touch starting.Y reported",
+ withinMarginOfError(0.05f, imageButtonRect.centerY(), p1s.y));
+ assertTrue("Invalid touch ending.X reported",
+ withinMarginOfError(0.05f, starsBarRect.centerX(), p1e.x));
+ assertTrue("Invalid touch ending.Y reported",
+ withinMarginOfError(0.05f, starsBarRect.centerY(), p1e.y));
+ }
+
+ /**
+ * Performs a drag and drop operation from one UiObject to a specified coordinates
+ *
+ * @throws UiObjectNotFoundException
+ * @since API Level 18
+ */
+ public void testDragToCoordinates() throws UiObjectNotFoundException {
+ openTest("Test 5");
+
+ UiObject imageButton = new UiObject(new UiSelector().description("Image button"));
+ UiObject starsBar = new UiObject(new UiSelector().className(android.widget.RatingBar.class));
+
+ Rect starsBarRect = starsBar.getBounds();
+ Rect imageButtonRect = imageButton.getBounds();
+ imageButton.dragTo(starsBarRect.centerX(), starsBarRect.centerY(), 30);
+
+ // dialog with the detected pointers motion coordinates is displayed.
+ UiObject results = new UiObject(new UiSelector().className(
+ android.widget.ScrollView.class).childSelector(new UiSelector().className(
+ android.widget.TextView.class)));
+ String allPointers = results.getText();
+ new UiObject(new UiSelector().text("OK")).click(); // dismiss dialog
+
+ // parse pointer 1
+ Point p1s = parsePointerCoordinates(allPointers, 0, 0); // start
+ Point p1e = parsePointerCoordinates(allPointers, 0, 1); // end
+
+ assertTrue("Invalid touch starting.X reported",
+ withinMarginOfError(0.05f, imageButtonRect.centerX(), p1s.x));
+ assertTrue("Invalid touch starting.Y reported",
+ withinMarginOfError(0.05f, imageButtonRect.centerY(), p1s.y));
+ assertTrue("Invalid touch ending.X reported",
+ withinMarginOfError(0.05f, starsBarRect.centerX(), p1e.x));
+ assertTrue("Invalid touch ending.Y reported",
+ withinMarginOfError(0.05f, starsBarRect.centerY(), p1e.y));
+ }
+
+ /**
+ * Detect if actual value is within the allowable margin of error of the expected value.
+ *
+ * Used essentially with actual values that may vary from the expected values such in the
+ * cases of touch and pinch and touch and swipe where the starting or ending points may
+ * not exactly match the expected value.
+ *
+ * @param marginPrecent is values between 0 and 1
+ * @param expected
+ * @param actual
+ * @return true if actual is within the allowed range from expected
+ */
+ private boolean withinMarginOfError(float marginPrecent, int expected, int actual) {
+ int m = (int) (marginPrecent * expected);
+ return actual >= expected - m && actual <= expected + m;
+ }
+
+ /**
+ * Parses a string containing starting to ending coordinates of one or more pointers.
+ *
+ * @param allPointers is a raw string with coordinates from all detected pointers
+ * @param pointerNumber is the desired pointer to be parsed
+ * @param edge is the 0 for the start or 1 for the end of the swipe
+ * @return Point containing the start or end coordinates of the specified pointer number
+ */
+ private Point parsePointerCoordinates(String allPointers, int pointerNumber, int edge) {
+ String pointers[] = allPointers.split("\n");
+ String coordinates = pointers[pointerNumber].split(":")[edge];
+ String xy[] = coordinates.split(",");
+ return new Point(Integer.parseInt(xy[0]), Integer.parseInt(xy[1]));
+ }
+
+ /**
* Private helper to open test views. Also covers UiScrollable tests
*
* @param name
diff --git a/suite/pts/tools/Android.mk b/tests/uiautomator/test-apps/Android.mk
similarity index 98%
rename from suite/pts/tools/Android.mk
rename to tests/uiautomator/test-apps/Android.mk
index c141484..e790e1e 100644
--- a/suite/pts/tools/Android.mk
+++ b/tests/uiautomator/test-apps/Android.mk
@@ -1,4 +1,3 @@
-#
# Copyright (C) 2012 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,6 +11,5 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-#
include $(call all-subdir-makefiles)
diff --git a/tests/uiautomator/test-apps/CtsUiAutomatorApp/Android.mk b/tests/uiautomator/test-apps/CtsUiAutomatorApp/Android.mk
index e30816a..fe4342a 100644
--- a/tests/uiautomator/test-apps/CtsUiAutomatorApp/Android.mk
+++ b/tests/uiautomator/test-apps/CtsUiAutomatorApp/Android.mk
@@ -1,4 +1,3 @@
-#
# Copyright (C) 2012 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,7 +11,6 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-#
LOCAL_PATH:= $(call my-dir)
@@ -31,4 +29,3 @@
LOCAL_PROGUARD_ENABLED := disabled
include $(BUILD_PACKAGE)
-
diff --git a/tests/uiautomator/test-apps/CtsUiAutomatorApp/AndroidManifest.xml b/tests/uiautomator/test-apps/CtsUiAutomatorApp/AndroidManifest.xml
index 9b3d8b3..87c2d82 100644
--- a/tests/uiautomator/test-apps/CtsUiAutomatorApp/AndroidManifest.xml
+++ b/tests/uiautomator/test-apps/CtsUiAutomatorApp/AndroidManifest.xml
@@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
-
<!--
* Copyright (C) 2011 The Android Open Source Project
*
@@ -44,6 +43,6 @@
android:name="android.support.PARENT_ACTIVITY"
android:value="FragmentActivity" />
</activity>
- </application>
+ </application>
</manifest>
diff --git a/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/layout/activity_main.xml b/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/layout/activity_main.xml
index f0e3bc9..72b68f3 100644
--- a/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/layout/activity_main.xml
+++ b/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/layout/activity_main.xml
@@ -28,4 +28,4 @@
android:layout_centerVertical="true"
android:text="@string/hello_world" />
-</RelativeLayout>
\ No newline at end of file
+</RelativeLayout>
diff --git a/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/layout/test_results_detail_fragment.xml b/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/layout/test_results_detail_fragment.xml
index 28ed6dd..e8b8c05 100644
--- a/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/layout/test_results_detail_fragment.xml
+++ b/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/layout/test_results_detail_fragment.xml
@@ -19,6 +19,7 @@
android:id="@+id/test_results_detail_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:contentDescription="@string/title_test_view_detail"
android:orientation="vertical" >
<TextView
diff --git a/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/menu/activity_main.xml b/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/menu/activity_main.xml
index 73c4db8..c54c287 100644
--- a/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/menu/activity_main.xml
+++ b/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/menu/activity_main.xml
@@ -23,4 +23,4 @@
android:showAsAction="never"
android:title="@string/menu_settings"/>
-</menu>
\ No newline at end of file
+</menu>
diff --git a/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/values/strings.xml b/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/values/strings.xml
index 78b829f..04f0df4 100644
--- a/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/values/strings.xml
+++ b/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/values/strings.xml
@@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
-
<!--
* Copyright (C) 2011 The Android Open Source Project
*
@@ -58,5 +57,7 @@
<string name="title_activity_test5_detail">Test5DetailActivity</string>
<string name="title_activity_test6_detail">Test6DetailActivity</string>
<string name="test_5_Widgets_collection">Widgets Collection</string>
-
+ <string name="generic_item_touch_dialog_title">Multi-touch test dialog</string>
+ <string name="drag_item_touch_dialog_title">Drag and drop test dialog</string>
+ <string name="title_test_view_detail">Details View</string>
</resources>
diff --git a/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/values/styles.xml b/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/values/styles.xml
index 119c259..bce88ae 100644
--- a/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/values/styles.xml
+++ b/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/values/styles.xml
@@ -19,4 +19,4 @@
<style name="AppTheme" parent="android:Theme.Light" />
-</resources>
\ No newline at end of file
+</resources>
diff --git a/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/MainActivity.java b/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/MainActivity.java
index 8d4f144..81833b7 100644
--- a/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/MainActivity.java
+++ b/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/MainActivity.java
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package com.android.cts.uiautomator;
import android.content.Intent;
diff --git a/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/Test2DetailFragment.java b/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/Test2DetailFragment.java
index 4eade4b..4fb322f 100644
--- a/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/Test2DetailFragment.java
+++ b/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/Test2DetailFragment.java
@@ -30,7 +30,6 @@
public class Test2DetailFragment extends Fragment {
public static final String ARG_ITEM_ID = "item_id";
private Button mButton1, mButton2, mButton3, mDynaButton;
- private boolean mDynaButtonAfter = false;
public Test2DetailFragment() {
}
@@ -144,32 +143,11 @@
mDynaButton.setOnClickListener(new Button.OnClickListener() {
@Override
public void onClick(View v) {
- if (getActivity().getString(R.string.buttonBefore).equals(mDynaButton.getText())) {
- mDynaButton.setText(R.string.buttonAfter);
- mDynaButton
- .setContentDescription(getActivity().getString(R.string.buttonAfter));
- mDynaButtonAfter = true;
- } else {
- mDynaButton.setText(R.string.buttonBefore);
- mDynaButton.setContentDescription(getActivity()
- .getString(R.string.buttonBefore));
- mDynaButtonAfter = false;
- }
+ mDynaButton.setText(R.string.buttonAfter);
+ mDynaButton.setContentDescription(getString(R.string.buttonAfter));
}
});
- if (savedState != null && savedState.getBoolean("DynaButtonAfter")) {
- mDynaButton.setText(R.string.buttonAfter);
- mDynaButton.setContentDescription(getActivity().getString(R.string.buttonAfter));
- mDynaButtonAfter = true;
- }
return rootView;
}
-
- @Override
- public void onSaveInstanceState(Bundle savedInstanceState) {
- super.onSaveInstanceState(savedInstanceState);
- // Save UI state changes to the savedInstanceState.
- savedInstanceState.putBoolean("DynaButtonAfter", mDynaButtonAfter);
- }
}
diff --git a/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/Test5DetailFragment.java b/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/Test5DetailFragment.java
index 0f88d3c..e2dd156 100644
--- a/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/Test5DetailFragment.java
+++ b/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/Test5DetailFragment.java
@@ -16,9 +16,11 @@
package com.android.cts.uiautomator;
+import android.app.AlertDialog;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
+import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
@@ -31,6 +33,15 @@
public static final String ARG_ITEM_ID = "item_id";
+ class PointerEvent {
+ int startX;
+ int startY;
+ int endX;
+ int endY;
+ }
+
+ private final PointerEvent mPointerEvent = new PointerEvent();
+
public Test5DetailFragment() {
}
@@ -66,6 +77,75 @@
}
});
+ imageButton.setOnTouchListener(new ImageButton.OnTouchListener() {
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ resetTouchResults();
+ collectStartAction(event, v);
+ } else if (event.getAction() == MotionEvent.ACTION_UP) {
+ collectEndAction(event, v);
+ displayTouchResults();
+ }
+ return false;
+ }
+ });
+
return rootView;
}
+
+ private void displayTouchResults() {
+ StringBuilder output = new StringBuilder();
+
+ output.append(String.format("%d,%d:%d,%d\n",
+ mPointerEvent.startX, mPointerEvent.startY, mPointerEvent.endX,
+ mPointerEvent.endY));
+
+ // display the submitted text
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ builder.setTitle(R.string.drag_item_touch_dialog_title);
+ builder.setPositiveButton(R.string.OK, null);
+ builder.setMessage(output.toString());
+ AlertDialog diag = builder.create();
+ diag.show();
+ }
+
+ /**
+ * Clears all collected pointer results
+ */
+ private void resetTouchResults() {
+ mPointerEvent.startX = mPointerEvent.startY =
+ mPointerEvent.endX = mPointerEvent.endY = -1;
+ }
+
+ /**
+ * Collects pointer touch information converting from relative to absolute before
+ * storing it as starting touch coordinates.
+ *
+ * @param event
+ * @param view
+ * @param pointerIndex
+ */
+ private void collectStartAction(MotionEvent event, View view) {
+ int offsetInScreen[] = new int[2];
+ view.getLocationOnScreen(offsetInScreen);
+ mPointerEvent.startX = (int)(event.getX() + offsetInScreen[0]);
+ mPointerEvent.startY = (int)(event.getY() + offsetInScreen[1]);
+ }
+
+ /**
+ * Collects pointer touch information converting from relative to absolute before
+ * storing it as ending touch coordinates.
+ *
+ * @param event
+ * @param view
+ * @param pointerIndex
+ */
+ private void collectEndAction(MotionEvent event, View view) {
+ int offsetInScreen[] = new int[2];
+ view.getLocationOnScreen(offsetInScreen);
+ mPointerEvent.endX = (int)(event.getX() + offsetInScreen[0]);
+ mPointerEvent.endY = (int)(event.getY() + offsetInScreen[1]);
+ }
}
diff --git a/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/TestGenericDetailFragment.java b/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/TestGenericDetailFragment.java
index ab36d04..a7215c3 100644
--- a/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/TestGenericDetailFragment.java
+++ b/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/TestGenericDetailFragment.java
@@ -16,9 +16,11 @@
package com.android.cts.uiautomator;
+import android.app.AlertDialog;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
+import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
@@ -27,6 +29,15 @@
public static final String ARG_ITEM_ID = "item_id";
TestItems.TestItem mItem;
+ private class PointerEvent {
+ int startX;
+ int startY;
+ int endX;
+ int endY;
+ }
+
+ private final PointerEvent[] mPointerEvents = new PointerEvent[10];
+
public TestGenericDetailFragment() {
}
@@ -44,6 +55,122 @@
if (mItem != null) {
((TextView) rootView.findViewById(R.id.testResultsTextView)).setText(mItem.mName);
}
+
+ // listen to touch events to verify the multiPointerGesture APIs
+ // Since API Level 18
+ rootView.setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+
+ switch(event.getAction() & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_DOWN:
+ // Reset any collected touch coordinate results on the primary touch down
+ resetTouchResults();
+ // collect this event
+ collectStartAction(event, v, 0);
+ break;
+
+ case MotionEvent.ACTION_POINTER_DOWN:
+ // collect this event
+ collectStartAction(event, v, getPointerIndex(event));
+ break;
+
+ case MotionEvent.ACTION_POINTER_UP:
+ // collect this event
+ collectEndAction(event, v, getPointerIndex(event));
+ break;
+
+ case MotionEvent.ACTION_UP:
+ // collect this event
+ collectEndAction(event, v, 0);
+ // on the primary touch up display results collected for all pointers
+ displayTouchResults();
+ break;
+ }
+ return true;
+ }
+ });
+
return rootView;
}
+
+ /**
+ * Displays collected results from all pointers into a dialog view in the following
+ * format: "startX,startY:endX,endY" where each line represent data for a pointer if
+ * multiple pointers (fingers) were detected.
+ */
+ private void displayTouchResults() {
+ StringBuilder output = new StringBuilder();
+ for (int x = 0; x < mPointerEvents.length; x++) {
+ if (mPointerEvents[x].startX == -1)
+ break;
+
+ output.append(String.format("%d,%d:%d,%d\n",
+ mPointerEvents[x].startX, mPointerEvents[x].startY, mPointerEvents[x].endX,
+ mPointerEvents[x].endY));
+ }
+
+ // display the submitted text
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ builder.setTitle(R.string.generic_item_touch_dialog_title);
+ builder.setPositiveButton(R.string.OK, null);
+ builder.setMessage(output.toString());
+ AlertDialog diag = builder.create();
+ diag.show();
+ }
+
+ /**
+ * Clears all collected pointer results
+ */
+ private void resetTouchResults() {
+ for (int x = 0; x < mPointerEvents.length; x++) {
+ if (mPointerEvents[x] == null)
+ mPointerEvents[x] = new PointerEvent();
+ mPointerEvents[x].startX = mPointerEvents[x].startY =
+ mPointerEvents[x].endX = mPointerEvents[x].endY = -1;
+ }
+ }
+
+ /**
+ * Collects pointer touch information converting from relative to absolute before
+ * storing it as starting touch coordinates.
+ *
+ * @param event
+ * @param view
+ * @param pointerIndex
+ */
+ private void collectStartAction(MotionEvent event, View view, int pointerIndex) {
+ int offsetInScreen[] = new int[2];
+ view.getLocationOnScreen(offsetInScreen);
+ mPointerEvents[getPointerId(event)].startX =
+ (int)(event.getX(pointerIndex) + offsetInScreen[0]);
+ mPointerEvents[getPointerId(event)].startY =
+ (int)(event.getY(pointerIndex) + offsetInScreen[1]);
+ }
+
+ /**
+ * Collects pointer touch information converting from relative to absolute before
+ * storing it as ending touch coordinates.
+ *
+ * @param event
+ * @param view
+ * @param pointerIndex
+ */
+ private void collectEndAction(MotionEvent event, View view, int pointerIndex) {
+ int offsetInScreen[] = new int[2];
+ view.getLocationOnScreen(offsetInScreen);
+ mPointerEvents[getPointerId(event)].endX =
+ (int)(event.getX(pointerIndex) + offsetInScreen[0]);
+ mPointerEvents[getPointerId(event)].endY =
+ (int)(event.getY(pointerIndex) + offsetInScreen[1]);
+ }
+
+ private int getPointerId(MotionEvent event) {
+ return event.getPointerId(getPointerIndex(event));
+ }
+
+ private int getPointerIndex(MotionEvent event) {
+ return ((event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
+ >> MotionEvent.ACTION_POINTER_INDEX_SHIFT);
+ }
}
diff --git a/tools/cts-java-scanner/src/com/android/cts/javascanner/DocletRunner.java b/tools/cts-java-scanner/src/com/android/cts/javascanner/DocletRunner.java
index ba87156..2afc4fa 100644
--- a/tools/cts-java-scanner/src/com/android/cts/javascanner/DocletRunner.java
+++ b/tools/cts-java-scanner/src/com/android/cts/javascanner/DocletRunner.java
@@ -72,6 +72,7 @@
// PTS adds PtsAndroidTestCase
sourcePath.add("./cts/suite/pts/deviceTests/ptsutil/src");
sourcePath.add("./cts/libs/util/src");
+ sourcePath.add("./frameworks/testing/uiautomator/library/testrunner-src");
sourcePath.add(sourceDir.toString());
return join(sourcePath, ":");
}
diff --git a/tools/cts-native-scanner/Android.mk b/tools/cts-native-scanner/Android.mk
index f8b1629..8bcff1c 100644
--- a/tools/cts-native-scanner/Android.mk
+++ b/tools/cts-native-scanner/Android.mk
@@ -34,10 +34,5 @@
$(copy-file-to-new-target)
$(hide) chmod 755 $@
-# the other stuff
-# ============================================================
-subdirs := $(addprefix $(LOCAL_PATH)/,$(addsuffix /Android.mk, \
- src \
- ))
-
-include $(subdirs)
+# Build all sub-directories
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tools/cts-native-scanner/src/com/android/cts/nativescanner/TestScanner.java b/tools/cts-native-scanner/src/com/android/cts/nativescanner/TestScanner.java
index 9411566..0a1b207 100644
--- a/tools/cts-native-scanner/src/com/android/cts/nativescanner/TestScanner.java
+++ b/tools/cts-native-scanner/src/com/android/cts/nativescanner/TestScanner.java
@@ -30,12 +30,14 @@
* Scanner of C++ gTest source files.
*
* It looks for test declarations and outputs a file following this format:
- *
- * class:TestClass1
- * method:testMethod1
- * method:testMethod2
- * class:TestClass2
- * method:testMethod1
+ * suite:TestSuite
+ * case:TestCase1
+ * test:Test1
+ * test:Test2
+ * suite:TestSuite
+ * case:TestCase2
+ * test:Test1
+ * test:Test2
*
*/
class TestScanner {
@@ -71,38 +73,36 @@
if (file.isDirectory()) {
scanDir(file, testNames);
} else {
- scanFile(file, testNames);
+ scanFile(new Scanner(file), testNames);
}
}
}
- // We want to find lines like class SLObjectCreationTest : public ::testing::Test { ...
- // and extract the "SLObjectCreationTest" as group #1
- private static final Pattern CLASS_REGEX =
- Pattern.compile("\\s*class\\s+(\\w+).*");
// We want to find lines like TEST_F(SLObjectCreationTest, testAudioPlayerFromFdCreation) { ...
- // and extract the "testAudioPlayerFromFdCreation" as group #1
+ // and extract the "SLObjectCreationTest" as group #1,
+ // "testAudioPlayerFromFdCreation" as group #2
private static final Pattern METHOD_REGEX =
- Pattern.compile("\\s*TEST_F\\(\\w+,\\s*(\\w+)\\).*");
+ Pattern.compile("\\s*TEST_F\\((\\w+),\\s*(\\w+)\\).*");
- private void scanFile(File file, List<String> testNames) throws FileNotFoundException {
- Scanner scanner = null;
+ public void scanFile(Scanner scanner, List<String> testNames) {
try {
- scanner = new Scanner(file);
+ String lastCase = "";
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
- Matcher matcher = CLASS_REGEX.matcher(line);
- if (matcher.matches()) {
- testNames.add("suite:" + mTestSuite);
- testNames.add("case:" + matcher.group(1));
+
+ Matcher matcher = METHOD_REGEX.matcher(line);
+
+ if (!matcher.matches()) {
continue;
}
- matcher = METHOD_REGEX.matcher(line);
- if (matcher.matches()) {
- testNames.add("test:" + matcher.group(1));
- continue;
+ if (!lastCase.equals(matcher.group(1))) {
+ testNames.add("suite:" + mTestSuite);
+ testNames.add("case:" + matcher.group(1));
+ lastCase = matcher.group(1);
}
+
+ testNames.add("test:" + matcher.group(2));
}
} finally {
if (scanner != null) {
diff --git a/suite/pts/hostTests/ptshostutil/Android.mk b/tools/cts-native-scanner/tests/Android.mk
similarity index 71%
copy from suite/pts/hostTests/ptshostutil/Android.mk
copy to tools/cts-native-scanner/tests/Android.mk
index 14e786a..6044b0d 100644
--- a/suite/pts/hostTests/ptshostutil/Android.mk
+++ b/tools/cts-native-scanner/tests/Android.mk
@@ -1,4 +1,4 @@
-# Copyright (C) 2012 The Android Open Source Project
+# Copyright 2012 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -16,14 +16,13 @@
include $(CLEAR_VARS)
-LOCAL_SRC_FILES := \
- $(call all-java-files-under, src)
+# Only compile source java files in this lib
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed-prebuilt ddmlib-prebuilt junit
-
+LOCAL_MODULE := cts-native-scanner-tests
LOCAL_MODULE_TAGS := optional
-
-LOCAL_MODULE := ptshostutil
+LOCAL_JAVA_LIBRARIES := ddmlib-prebuilt tradefed-prebuilt cts-native-scanner
include $(BUILD_HOST_JAVA_LIBRARY)
+
diff --git a/tools/cts-native-scanner/tests/run_unit_tests.sh b/tools/cts-native-scanner/tests/run_unit_tests.sh
new file mode 100755
index 0000000..a42e42b
--- /dev/null
+++ b/tools/cts-native-scanner/tests/run_unit_tests.sh
@@ -0,0 +1,36 @@
+#!/bin/bash
+
+# Copyright 2012 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# helper script for running the cts-tradefed unit tests
+
+checkFile() {
+ if [ ! -f "$1" ]; then
+ echo "Unable to locate $1"
+ exit
+ fi;
+}
+
+JAR_DIR=${ANDROID_HOST_OUT}/framework
+JARS="ddmlib-prebuilt.jar tradefed-prebuilt.jar hosttestlib.jar cts-native-scanner.jar cts-native-scanner-tests.jar"
+
+for JAR in $JARS; do
+ checkFile ${JAR_DIR}/${JAR}
+ JAR_PATH=${JAR_PATH}:${JAR_DIR}/${JAR}
+done
+
+java $RDBG_FLAG \
+ -cp ${JAR_PATH} com.android.tradefed.command.Console run singleCommand host -n --class com.android.cts.nativescanner.UnitTests "$@"
+
diff --git a/tools/cts-native-scanner/tests/src/com/android/cts/nativescanner/TestScannerTest.java b/tools/cts-native-scanner/tests/src/com/android/cts/nativescanner/TestScannerTest.java
new file mode 100644
index 0000000..18732fd
--- /dev/null
+++ b/tools/cts-native-scanner/tests/src/com/android/cts/nativescanner/TestScannerTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.nativescanner;
+
+import com.android.cts.nativescanner.TestScanner;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.io.StringReader;
+import java.lang.StringBuilder;
+import java.util.Scanner;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+/**
+ * Unit tests for {@link TestScanner}.
+ */
+public class TestScannerTest extends TestCase {
+
+ public void testScanFile() {
+ TestScanner testScanner = new TestScanner(new File("unused"), "TestSuite");
+
+ String newLine = System.getProperty("line.separator");
+ StringBuilder sb = new StringBuilder();
+ sb.append("foobar" + newLine); // ignored
+ sb.append("TEST_F(TestCase1, TestName1)" + newLine); // valid
+ sb.append("TEST_F(TestCase1, TestName2)" + newLine); // valid
+ sb.append("TEST_F(TestCase2, TestName1) foo" + newLine); // valid
+ sb.append("TEST_F(TestCase2, TestName1 foo)" + newLine); // ignored
+ sb.append("foo TEST_F(TestCase2, TestName1)" + newLine); // ignored
+
+ List<String> names = new ArrayList<String>();
+ Scanner scanner = new Scanner(new StringReader(sb.toString()));
+ testScanner.scanFile(scanner, names);
+ Iterator it = names.iterator();
+
+ assertEquals("suite:TestSuite", it.next());
+ assertEquals("case:TestCase1", it.next());
+ assertEquals("test:TestName1", it.next());
+ assertEquals("test:TestName2", it.next());
+ assertEquals("suite:TestSuite", it.next());
+ assertEquals("case:TestCase2", it.next());
+ assertEquals("test:TestName1", it.next());
+ assertFalse(it.hasNext());
+ scanner.close();
+ }
+}
diff --git a/tools/cts-native-scanner/tests/src/com/android/cts/nativescanner/UnitTests.java b/tools/cts-native-scanner/tests/src/com/android/cts/nativescanner/UnitTests.java
new file mode 100644
index 0000000..13d4e5b
--- /dev/null
+++ b/tools/cts-native-scanner/tests/src/com/android/cts/nativescanner/UnitTests.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.nativescanner;
+
+import com.android.cts.nativescanner.TestScannerTest;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * A test suite for all cts-native-scanner unit tests.
+ * <p/>
+ * All tests listed here should be self-contained, and do not require any external dependencies
+ */
+public class UnitTests extends TestSuite {
+
+ public UnitTests() {
+ super();
+
+ // result package
+ addTestSuite(TestScannerTest.class);
+ }
+
+ public static Test suite() {
+ return new UnitTests();
+ }
+}
diff --git a/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/CtsXmlGenerator.java b/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/CtsXmlGenerator.java
index ce4fdd7..c5b253a 100644
--- a/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/CtsXmlGenerator.java
+++ b/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/CtsXmlGenerator.java
@@ -49,6 +49,8 @@
String instrumentation = null;
String testType = null;
String jarPath = null;
+ String appNameSpace = null;
+ String targetNameSpace = null;
for (int i = 0; i < args.length; i++) {
if ("-p".equals(args[i])) {
@@ -68,15 +70,17 @@
"Missing value for expectation store")));
} else if ("-o".equals(args[i])) {
outputPath = getArg(args, ++i, "Missing value for output file");
+ } else if ("-a".equals(args[i])) {
+ appNameSpace = getArg(args, ++i, "Missing value for app name space");
+ } else if ("-r".equals(args[i])) {
+ targetNameSpace = getArg(args, ++i, "Missing value for target name space");
} else {
System.err.println("Unsupported flag: " + args[i]);
usage(args);
}
}
- String appNameSpace = null;
String runner = null;
- String targetNameSpace = null;
if (manifestFile != null) {
Document manifest = DocumentBuilderFactory.newInstance().newDocumentBuilder()
diff --git a/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/Test.java b/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/Test.java
index a9dd5e0..93b838b 100644
--- a/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/Test.java
+++ b/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/Test.java
@@ -16,7 +16,7 @@
package com.android.cts.xmlgenerator;
-public class Test {
+public class Test implements Comparable<Test> {
private String mName;
private int mTimeout;
@@ -32,4 +32,9 @@
public int getTimeout() {
return mTimeout;
}
+
+ @Override
+ public int compareTo(Test another) {
+ return getName().compareTo(another.getName());
+ }
}
diff --git a/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/TestCase.java b/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/TestCase.java
index a9ea6e1..ed09b8e 100644
--- a/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/TestCase.java
+++ b/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/TestCase.java
@@ -20,7 +20,7 @@
import java.util.Collections;
import java.util.List;
-class TestCase {
+class TestCase implements Comparable<TestCase> {
private final String mName;
@@ -41,4 +41,9 @@
public Collection<Test> getTests() {
return Collections.unmodifiableCollection(mTests);
}
+
+ @Override
+ public int compareTo(TestCase another) {
+ return getName().compareTo(another.getName());
+ }
}
diff --git a/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/TestSuite.java b/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/TestSuite.java
index 3c29603..466f8d7 100644
--- a/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/TestSuite.java
+++ b/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/TestSuite.java
@@ -22,7 +22,7 @@
import java.util.List;
import java.util.Map;
-class TestSuite {
+class TestSuite implements Comparable<TestSuite> {
private final String mName;
@@ -61,4 +61,9 @@
public Collection<TestCase> getCases() {
return Collections.unmodifiableCollection(mCases);
}
+
+ @Override
+ public int compareTo(TestSuite another) {
+ return getName().compareTo(another.getName());
+ }
}
diff --git a/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/XmlGenerator.java b/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/XmlGenerator.java
index 8a49cf3..7b1996d 100644
--- a/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/XmlGenerator.java
+++ b/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/XmlGenerator.java
@@ -24,7 +24,10 @@
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
/**
* Generator of TestPackage XML files for native tests.
@@ -141,7 +144,8 @@
private void writeTestSuites(PrintWriter writer, Collection<TestSuite> suites,
StringBuilder nameCollector) {
- for (TestSuite suite : suites) {
+ Collection<TestSuite> sorted = sortCollection(suites);
+ for (TestSuite suite : sorted) {
writer.append("<TestSuite name=\"").append(suite.getName()).println("\">");
String namePart = suite.getName();
@@ -161,7 +165,8 @@
private void writeTestCases(PrintWriter writer, Collection<TestCase> cases,
StringBuilder nameCollector) {
- for (TestCase testCase : cases) {
+ Collection<TestCase> sorted = sortCollection(cases);
+ for (TestCase testCase : sorted) {
String name = testCase.getName();
writer.append("<TestCase name=\"").append(name).println("\">");
nameCollector.append('.').append(name);
@@ -176,7 +181,8 @@
private void writeTests(PrintWriter writer, Collection<Test> tests,
StringBuilder nameCollector) {
- for (Test test : tests) {
+ Collection<Test> sorted = sortCollection(tests);
+ for (Test test : sorted) {
nameCollector.append('#').append(test.getName());
writer.append("<Test name=\"").append(test.getName()).append("\"");
if (isKnownFailure(mExpectations, nameCollector.toString())) {
@@ -192,6 +198,12 @@
}
}
+ private <E extends Comparable<E>> Collection<E> sortCollection(Collection<E> col) {
+ List<E> list = new ArrayList<E>(col);
+ Collections.sort(list);
+ return list;
+ }
+
public static boolean isKnownFailure(ExpectationStore expectationStore, String testName) {
return expectationStore != null && expectationStore.get(testName) != Expectation.SUCCESS;
}
diff --git a/tools/tradefed-host/.classpath b/tools/tradefed-host/.classpath
index 275ecf9..0b1866d 100644
--- a/tools/tradefed-host/.classpath
+++ b/tools/tradefed-host/.classpath
@@ -3,10 +3,9 @@
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="res"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
- <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3"/>
- <classpathentry combineaccessrules="false" kind="src" path="/ddmlib"/>
- <classpathentry combineaccessrules="false" kind="src" path="/tradefederation"/>
<classpathentry exported="true" kind="var" path="CTS_SRC_ROOT/out/host/common/obj/JAVA_LIBRARIES/ctsdeviceinfolib_intermediates/javalib.jar"/>
<classpathentry exported="true" kind="var" path="CTS_SRC_ROOT/out/host/common/obj/JAVA_LIBRARIES/hosttestlib_intermediates/javalib.jar"/>
+ <classpathentry kind="var" path="CTS_SRC_ROOT/out/host/common/obj/JAVA_LIBRARIES/ddmlib-prebuilt_intermediates/ddmlib-prebuilt.jar" sourcepath="/SDK_SRC_ROOT"/>
+ <classpathentry kind="var" path="CTS_SRC_ROOT/out/host/common/obj/JAVA_LIBRARIES/tradefed-prebuilt_intermediates/tradefed-prebuilt.jar" sourcepath="/TRADEFED_ROOT/tools/tradefederation/src"/>
<classpathentry kind="output" path="bin"/>
</classpath>
diff --git a/tools/tradefed-host/.gitignore b/tools/tradefed-host/.gitignore
new file mode 100644
index 0000000..ba077a4
--- /dev/null
+++ b/tools/tradefed-host/.gitignore
@@ -0,0 +1 @@
+bin
diff --git a/tools/tradefed-host/Android.mk b/tools/tradefed-host/Android.mk
index 7af7993..e8965ab 100644
--- a/tools/tradefed-host/Android.mk
+++ b/tools/tradefed-host/Android.mk
@@ -17,13 +17,16 @@
include $(CLEAR_VARS)
# Only compile source java files in this lib.
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, src) \
+ $(call all-java-files-under, ../../suite/pts/hostTests/ptshostutil/src)
+
LOCAL_JAVA_RESOURCE_DIRS := res
LOCAL_MODULE := cts-tradefed
LOCAL_MODULE_TAGS := optional
LOCAL_JAVA_LIBRARIES := ddmlib-prebuilt tradefed-prebuilt hosttestlib
-LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceinfolib
+LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceinfolib ptscommonutilhost
include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/tools/tradefed-host/res/report/cts_result.xsd b/tools/tradefed-host/res/report/cts_result.xsd
index 5e471d5..3f7f384 100644
--- a/tools/tradefed-host/res/report/cts_result.xsd
+++ b/tools/tradefed-host/res/report/cts_result.xsd
@@ -16,8 +16,8 @@
-->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
- targetNamespace="http://compatibility.android.com/cts_result/1.13"
- xmlns="http://compatibility.android.com/cts_result/1.13"
+ targetNamespace="http://compatibility.android.com/cts_result/1.15"
+ xmlns="http://compatibility.android.com/cts_result/1.15"
elementFormDefault="qualified">
<xs:element name="TestResult">
@@ -32,6 +32,7 @@
<xs:attribute name="endtime" type="xs:string"/>
<xs:attribute name="testPlan" type="xs:string"/>
<xs:attribute name="version" type="xs:string"/>
+ <xs:attribute name="suite" type="xs:string"/>
</xs:complexType>
</xs:element>
@@ -195,6 +196,29 @@
<xs:attribute name="priority" type="xs:string"/>
</xs:complexType>
+<xs:simpleType name="unitType">
+ <xs:restriction base="xs:string">
+ <xs:enumeration value="none"/>
+ <xs:enumeration value="ms"/>
+ <xs:enumeration value="fps"/>
+ <xs:enumeration value="ops"/>
+ <xs:enumeration value="kbps"/>
+ <xs:enumeration value="mbps"/>
+ <xs:enumeration value="byte"/>
+ <xs:enumeration value="count"/>
+ <xs:enumeration value="score"/>
+ </xs:restriction>
+</xs:simpleType>
+
+<xs:simpleType name="scoreTypeType">
+ <xs:restriction base="xs:string">
+ <xs:enumeration value="higherBetter"/>
+ <xs:enumeration value="lowerBetter"/>
+ <xs:enumeration value="neutral"/>
+ <xs:enumeration value="warning"/>
+ </xs:restriction>
+</xs:simpleType>
+
<xs:complexType name="testType">
<xs:sequence>
<xs:element name="FailedScene" minOccurs="0" maxOccurs="1">
@@ -205,6 +229,35 @@
<xs:attribute name="message" type="xs:string"/>
</xs:complexType>
</xs:element>
+ <xs:element name="Summary" minOccurs="0" maxOccurs="1">
+ <xs:complexType>
+ <xs:simpleContent>
+ <xs:extension base="xs:decimal">
+ <xs:attribute name="message" type="xs:string" use="required" />
+ <xs:attribute name="scoreType" type="scoreTypeType" use="required" />
+ <xs:attribute name="unit" type="unitType" use="required" />
+ <xs:attribute name="target" type="xs:decimal" />
+ </xs:extension>
+ </xs:simpleContent>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="Details" minOccurs="0" maxOccurs="1">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element name="ValueArray" minOccurs="0" maxOccurs="unbounded">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element name="Value" type="xs:decimal" minOccurs="0" maxOccurs="unbounded" />
+ </xs:sequence>
+ <xs:attribute name="source" type="xs:string" use="required" />
+ <xs:attribute name="message" type="xs:string" use="required" />
+ <xs:attribute name="scoreType" type="scoreTypeType" use="required" />
+ <xs:attribute name="unit" type="unitType" use="required" />
+ </xs:complexType>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
</xs:sequence>
<xs:attribute name="name" type="xs:string" use="required"/>
<xs:attribute name="result" type="resultType" use="required"/>
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/build/CtsBuildHelper.java b/tools/tradefed-host/src/com/android/cts/tradefed/build/CtsBuildHelper.java
index 95ec69f..61b4b43 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/build/CtsBuildHelper.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/build/CtsBuildHelper.java
@@ -30,18 +30,12 @@
public class CtsBuildHelper {
static final String CTS_DIR_NAME = "android-cts";
- static final String PTS_DIR_NAME = "android-pts";
- static private boolean mCtsMode = true;
- private final String mSuiteName;
+ private final String mSuiteName = "CTS";
/** The root location of the extracted CTS package */
private final File mRootDir;
/** the {@link CTS_DIR_NAME} directory */
private final File mCtsDir;
- public static void changeToPtsMode() {
- mCtsMode = false;
- }
-
/**
* Creates a {@link CtsBuildHelper}.
*
@@ -50,8 +44,7 @@
*/
public CtsBuildHelper(File rootDir) {
mRootDir = rootDir;
- mSuiteName = mCtsMode? "CTS" : "PTS";
- mCtsDir = new File(mRootDir, mCtsMode ? CTS_DIR_NAME : PTS_DIR_NAME);
+ mCtsDir = new File(mRootDir, CTS_DIR_NAME);
}
/**
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/command/CtsConsole.java b/tools/tradefed-host/src/com/android/cts/tradefed/command/CtsConsole.java
index f014135..40145dd 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/command/CtsConsole.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/command/CtsConsole.java
@@ -245,12 +245,7 @@
return mCtsBuild;
}
- public static void main(String[] args) throws InterruptedException {
- // change to PTS mode before anything else
- String ptsMode = System.getProperty("PTS");
- if ((ptsMode != null) && ptsMode.equals("1")) {
- CtsBuildHelper.changeToPtsMode();
- }
+ public static void main(String[] args) throws InterruptedException, ConfigurationException {
Console console = new CtsConsole();
Console.startConsole(console, args);
}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsXmlResultReporter.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsXmlResultReporter.java
index a2a31e0..2f62d71 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsXmlResultReporter.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsXmlResultReporter.java
@@ -58,7 +58,7 @@
private static final String LOG_TAG = "CtsXmlResultReporter";
static final String TEST_RESULT_FILE_NAME = "testResult.xml";
- private static final String CTS_RESULT_FILE_VERSION = "1.13";
+ private static final String CTS_RESULT_FILE_VERSION = "1.14";
private static final String[] CTS_RESULT_RESOURCES = {"cts_result.xsl", "cts_result.css",
"logo.gif", "newrule-green.png"};
@@ -97,6 +97,7 @@
private boolean mIsDeviceInfoRun = false;
private ResultReporter mReporter;
private File mLogDir;
+ private String mSuiteName;
private static final String PTS_PERFORMANCE_EXCEPTION = "com.android.pts.util.PtsException";
private static final Pattern mPtsLogPattern = Pattern.compile(
@@ -141,8 +142,8 @@
mStartTime = getTimestamp();
logResult("Created result dir %s", mReportDir.getName());
}
-
- mReporter = new ResultReporter(mResultServer, ctsBuildHelper.getSuiteName());
+ mSuiteName = ctsBuildHelper.getSuiteName();
+ mReporter = new ResultReporter(mResultServer, mSuiteName);
// TODO: allow customization of log dir
// create a unique directory for saving logs, with same name as result dir
@@ -358,6 +359,7 @@
serializer.attribute(ns, STARTTIME_ATTR, startTime);
serializer.attribute(ns, "endtime", endTime);
serializer.attribute(ns, "version", CTS_RESULT_FILE_VERSION);
+ serializer.attribute(ns, "suite", mSuiteName);
mResults.serialize(serializer);
// TODO: not sure why, but the serializer doesn't like this statement
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/Test.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/Test.java
index dd4f31f..72e2e27 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/result/Test.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/Test.java
@@ -15,6 +15,7 @@
*/
package com.android.cts.tradefed.result;
+import com.android.ddmlib.Log;
import com.android.tradefed.result.TestResult;
import org.kxml2.io.KXmlSerializer;
@@ -27,7 +28,6 @@
* Data structure that represents a "Test" result XML element.
*/
class Test extends AbstractXmlPullParser {
-
static final String TAG = "Test";
private static final String NAME_ATTR = "name";
private static final String MESSAGE_ATTR = "message";
@@ -36,7 +36,17 @@
private static final String RESULT_ATTR = "result";
private static final String SCENE_TAG = "FailedScene";
private static final String STACK_TAG = "StackTrace";
+ private static final String SUMMARY_TAG = "Summary";
private static final String DETAILS_TAG = "Details";
+ private static final String VALUEARRAY_TAG = "ValueArray";
+ private static final String VALUE_TAG = "Value";
+ private static final String TARGET_ATTR = "target";
+ private static final String SCORETYPE_ATTR = "scoreType";
+ private static final String UNIT_ATTR = "unit";
+ private static final String SOURCE_ATTR = "source";
+ // separators for the message from PTS
+ private static final String LOG_SEPARATOR = "\\+\\+\\+";
+ private static final String LOG_ELEM_SEPARATOR = "\\|";
private String mName;
private CtsTestStatus mResult;
@@ -44,7 +54,8 @@
private String mEndTime;
private String mMessage;
private String mStackTrace;
- // details passed from pts
+ // summary and details passed from pts
+ private String mSummary;
private String mDetails;
/**
@@ -109,6 +120,14 @@
mMessage = getFailureMessageFromStackTrace(mStackTrace);
}
+ public String getSummary() {
+ return mSummary;
+ }
+
+ public void setSummary(String summary) {
+ mSummary = summary;
+ }
+
public String getDetails() {
return mDetails;
}
@@ -147,17 +166,115 @@
serializer.text(mStackTrace);
serializer.endTag(CtsXmlResultReporter.ns, STACK_TAG);
}
- if (mDetails != null) {
- serializer.startTag(CtsXmlResultReporter.ns, DETAILS_TAG);
- serializer.text(mDetails);
- serializer.endTag(CtsXmlResultReporter.ns, DETAILS_TAG);
- }
serializer.endTag(CtsXmlResultReporter.ns, SCENE_TAG);
}
+ if (mSummary != null) {
+ // <Summary message = "screen copies per sec" scoretype="higherBetter" unit="fps">
+ // 23938.82978723404</Summary>
+ PerfResultSummary summary = parseSummary(mSummary);
+ if (summary != null) {
+ serializer.startTag(CtsXmlResultReporter.ns, SUMMARY_TAG);
+ serializer.attribute(CtsXmlResultReporter.ns, MESSAGE_ATTR, summary.mMessage);
+ if (summary.mTarget.length() != 0 && !summary.mTarget.equals(" ")) {
+ serializer.attribute(CtsXmlResultReporter.ns, TARGET_ATTR, summary.mTarget);
+ }
+ serializer.attribute(CtsXmlResultReporter.ns, SCORETYPE_ATTR, summary.mType);
+ serializer.attribute(CtsXmlResultReporter.ns, UNIT_ATTR, summary.mUnit);
+ serializer.text(summary.mValue);
+ serializer.endTag(CtsXmlResultReporter.ns, SUMMARY_TAG);
+ // add details only if summary is present
+ // <Details>
+ // <ValueArray source=”com.android.pts.dram.BandwidthTest#doRunMemcpy:98”
+ // message=”measure1” unit="ms" scoretype="higherBetter">
+ // <Value>0.0</Value>
+ // <Value>0.1</Value>
+ // </ValueArray>
+ // </Details>
+ if (mDetails != null) {
+ PerfResultDetail[] ds = parseDetails(mDetails);
+ serializer.startTag(CtsXmlResultReporter.ns, DETAILS_TAG);
+ for (PerfResultDetail d : ds) {
+ if (d == null) {
+ continue;
+ }
+ serializer.startTag(CtsXmlResultReporter.ns, VALUEARRAY_TAG);
+ serializer.attribute(CtsXmlResultReporter.ns, SOURCE_ATTR, d.mSource);
+ serializer.attribute(CtsXmlResultReporter.ns, MESSAGE_ATTR,
+ d.mMessage);
+ serializer.attribute(CtsXmlResultReporter.ns, SCORETYPE_ATTR, d.mType);
+ serializer.attribute(CtsXmlResultReporter.ns, UNIT_ATTR, d.mUnit);
+ for (String v : d.mValues) {
+ if (v == null) {
+ continue;
+ }
+ serializer.startTag(CtsXmlResultReporter.ns, VALUE_TAG);
+ serializer.text(v);
+ serializer.endTag(CtsXmlResultReporter.ns, VALUE_TAG);
+ }
+ serializer.endTag(CtsXmlResultReporter.ns, VALUEARRAY_TAG);
+ }
+ serializer.endTag(CtsXmlResultReporter.ns, DETAILS_TAG);
+ }
+ }
+ }
serializer.endTag(CtsXmlResultReporter.ns, TAG);
}
/**
+ * class containing performance result.
+ */
+ public static class PerfResultCommon {
+ public String mMessage;
+ public String mType;
+ public String mUnit;
+ }
+
+ private class PerfResultSummary extends PerfResultCommon {
+ public String mTarget;
+ public String mValue;
+ }
+
+ private class PerfResultDetail extends PerfResultCommon {
+ public String mSource;
+ public String[] mValues;
+ }
+
+ private PerfResultSummary parseSummary(String summary) {
+ String[] elems = summary.split(LOG_ELEM_SEPARATOR);
+ PerfResultSummary r = new PerfResultSummary();
+ if (elems.length < 5) {
+ Log.w(TAG, "wrong message " + summary);
+ return null;
+ }
+ r.mMessage = elems[0];
+ r.mTarget = elems[1];
+ r.mType = elems[2];
+ r.mUnit = elems[3];
+ r.mValue = elems[4];
+ return r;
+ }
+
+ private PerfResultDetail[] parseDetails(String details) {
+ String[] arrays = details.split(LOG_SEPARATOR);
+ PerfResultDetail[] rs = new PerfResultDetail[arrays.length];
+ for (int i = 0; i < arrays.length; i++) {
+ String[] elems = arrays[i].split(LOG_ELEM_SEPARATOR);
+ if (elems.length < 5) {
+ Log.w(TAG, "wrong message " + arrays[i]);
+ continue;
+ }
+ PerfResultDetail r = new PerfResultDetail();
+ r.mSource = elems[0];
+ r.mMessage = elems[1];
+ r.mType = elems[2];
+ r.mUnit = elems[3];
+ r.mValues = elems[4].split(" ");
+ rs[i] = r;
+ }
+ return rs;
+ }
+
+ /**
* Strip out any invalid XML characters that might cause the report to be unviewable.
* http://www.w3.org/TR/REC-xml/#dt-character
*/
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/TestPackageResult.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/TestPackageResult.java
index ce1c2f3..015a7ae 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/result/TestPackageResult.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/TestPackageResult.java
@@ -283,7 +283,7 @@
public void reportPerformanceResult(TestIdentifier test, CtsTestStatus status, String summary, String details) {
Test result = findTest(test);
result.setResultStatus(status);
- result.setMessage(summary);
+ result.setSummary(summary);
result.setDetails(details);
}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/targetprep/CtsRootDeviceSetup.java b/tools/tradefed-host/src/com/android/cts/tradefed/targetprep/CtsRootDeviceSetup.java
index 09129ca..0e768e2 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/targetprep/CtsRootDeviceSetup.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/targetprep/CtsRootDeviceSetup.java
@@ -66,7 +66,10 @@
// perform CTS setup steps that only work if adb is root
SettingsToggler.setSecureInt(device, "mock_location", 1);
enableDeviceAdmin(device, buildHelper);
-
+ // This is chrome specific setting to disable the first screen.
+ // For other browser, it will not do anything.
+ device.executeShellCommand(
+ "echo \"chrome --disable-fre\" > /data/local/chrome-command-line");
// end root setup steps
} catch (FileNotFoundException e) {
throw new TargetSetupError("Invalid CTS installation", e);
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/targetprep/SettingsToggler.java b/tools/tradefed-host/src/com/android/cts/tradefed/targetprep/SettingsToggler.java
index 69d4c8a..ff6c4f4 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/targetprep/SettingsToggler.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/targetprep/SettingsToggler.java
@@ -22,52 +22,98 @@
* {@link SettingsToggler} sets settings by using the "adb shell content" command.
*/
public class SettingsToggler {
+ private static final String GROUP_SECURE = "secure";
+ private static final String GROUP_GLOBAL = "global";
+
+ /** Sets a setting by deleting and then inserting the string value. */
+ public static void setString(ITestDevice device, String group, String name, String value)
+ throws DeviceNotAvailableException {
+ deleteSetting(device, group, name);
+ device.executeShellCommand(
+ "content insert"
+ + " --uri content://settings/" + group
+ + " --bind name:s:" + name
+ + " --bind value:s:" + value);
+ }
/** Sets a secure setting by deleting and then inserting the string value. */
public static void setSecureString(ITestDevice device, String name, String value)
throws DeviceNotAvailableException {
- deleteSecure(device, name);
+ setString(device, GROUP_SECURE, name, value);
+ }
+
+ /** Sets a global setting by deleting and then inserting the string value. */
+ public static void setGlobalString(ITestDevice device, String name, String value)
+ throws DeviceNotAvailableException {
+ setString(device, GROUP_GLOBAL, name, value);
+ }
+
+ /** Sets a setting by deleting and then inserting the int value. */
+ public static void setInt(ITestDevice device, String group, String name, int value)
+ throws DeviceNotAvailableException {
+ deleteSetting(device, group, name);
device.executeShellCommand(
"content insert"
- + " --uri content://settings/secure"
+ + " --uri content://settings/" + group
+ " --bind name:s:" + name
- + " --bind value:s:" + value);
+ + " --bind value:i:" + value);
}
/** Sets a secure setting by deleting and then inserting the int value. */
public static void setSecureInt(ITestDevice device, String name, int value)
throws DeviceNotAvailableException {
- deleteSecure(device, name);
+ setInt(device, GROUP_SECURE, name, value);
+ }
+
+ /** Sets a global setting by deleting and then inserting the int value. */
+ public static void setGlobalInt(ITestDevice device, String name, int value)
+ throws DeviceNotAvailableException {
+ setInt(device, GROUP_GLOBAL, name, value);
+ }
+
+ public static void updateString(ITestDevice device, String group, String name, String value)
+ throws DeviceNotAvailableException {
device.executeShellCommand(
- "content insert"
- + " --uri content://settings/secure"
- + " --bind name:s:" + name
- + " --bind value:i:" + value);
+ "content update"
+ + " --uri content://settings/" + group
+ + " --bind value:s:" + value
+ + " --where \"name='" + name + "'\"");
}
public static void updateSecureString(ITestDevice device, String name, String value)
throws DeviceNotAvailableException {
+ updateString(device, GROUP_SECURE, name, value);
+ }
+
+ public static void updateGlobalString(ITestDevice device, String name, String value)
+ throws DeviceNotAvailableException {
+ updateString(device, GROUP_GLOBAL, name, value);
+ }
+
+ public static void updateInt(ITestDevice device, String group, String name, int value)
+ throws DeviceNotAvailableException {
device.executeShellCommand(
"content update"
- + " --uri content://settings/secure"
- + " --bind value:s:" + value
+ + " --uri content://settings/" + group
+ + " --bind value:i:" + value
+ " --where \"name='" + name + "'\"");
}
public static void updateSecureInt(ITestDevice device, String name, int value)
throws DeviceNotAvailableException {
- device.executeShellCommand(
- "content update"
- + " --uri content://settings/secure"
- + " --bind value:i:" + value
- + " --where \"name='" + name + "'\"");
+ updateInt(device, GROUP_SECURE, name, value);
}
- private static void deleteSecure(ITestDevice device, String name)
+ public static void updateGlobalInt(ITestDevice device, String name, int value)
+ throws DeviceNotAvailableException {
+ updateInt(device, GROUP_GLOBAL, name, value);
+ }
+
+ private static void deleteSetting(ITestDevice device, String group, String name)
throws DeviceNotAvailableException {
device.executeShellCommand(
"content delete"
- + " --uri content://settings/secure"
+ + " --uri content://settings/" + group
+ " --where \"name='" + name + "'\"");
}
}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/AccessibilityServiceTestRunner.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/AccessibilityServiceTestRunner.java
index 7490f18..d333943 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/AccessibilityServiceTestRunner.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/AccessibilityServiceTestRunner.java
@@ -67,7 +67,7 @@
}
private void afterTest() throws DeviceNotAvailableException {
- AccessibilityTestRunner.disableAccessibilityAndServicesAndTouchExploration(getDevice());
+ AccessibilityTestRunner.disableAccessibilityAndServices(getDevice());
uninstallAndAssert(DELEGATING_ACCESSIBLITY_SERVICE_PACKAGE_NAME);
}
@@ -85,7 +85,7 @@
private void enableAccessibilityAndDelegatingService() throws DeviceNotAvailableException {
String componentName = DELEGATING_ACCESSIBLITY_SERVICE_PACKAGE_NAME + "/"
+ DELEGATING_ACCESSIBLITY_SERVICE_NAME;
- AccessibilityTestRunner.enableAccessibilityAndServicesAndTouchExploration(getDevice(),
+ AccessibilityTestRunner.enableAccessibilityAndServices(getDevice(),
componentName);
}
}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/AccessibilityTestRunner.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/AccessibilityTestRunner.java
index ca4f9e1..2a4239a 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/AccessibilityTestRunner.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/AccessibilityTestRunner.java
@@ -68,11 +68,11 @@
private void beforeTest() throws DeviceNotAvailableException {
installApkAndAssert(SOME_ACCESSIBLITY_SERVICES_APK);
- enableAccessibilityAndServicesAndTouchExploration();
+ enableAccessibilityAndServices();
}
private void afterTest() throws DeviceNotAvailableException {
- disableAccessibilityAndServicesAndTouchExploration(getDevice());
+ disableAccessibilityAndServices(getDevice());
uninstallAndAssert(SOME_ACCESSIBLITY_SERVICES_PACKAGE_NAME);
}
@@ -87,26 +87,27 @@
TestCase.assertNull("Error uninstalling: " + packageName, errorMessage);
}
- private void enableAccessibilityAndServicesAndTouchExploration()
- throws DeviceNotAvailableException {
+ private void enableAccessibilityAndServices() throws DeviceNotAvailableException {
String enabledServicesValue =
SOME_ACCESSIBLITY_SERVICES_PACKAGE_NAME + "/" + SPEAKING_ACCESSIBLITY_SERVICE_NAME
+ ":"
+ SOME_ACCESSIBLITY_SERVICES_PACKAGE_NAME + "/" + VIBRATING_ACCESSIBLITY_SERVICE_NAME;
- enableAccessibilityAndServicesAndTouchExploration(getDevice(), enabledServicesValue);
+ enableAccessibilityAndServices(getDevice(), enabledServicesValue);
}
- static void enableAccessibilityAndServicesAndTouchExploration(ITestDevice device, String value)
+ static void enableAccessibilityAndServices(ITestDevice device, String value)
throws DeviceNotAvailableException {
SettingsToggler.setSecureString(device, "enabled_accessibility_services", value);
+ SettingsToggler.setSecureString(device,
+ "touch_exploration_granted_accessibility_services", value);
SettingsToggler.setSecureInt(device, "accessibility_enabled", 1);
- SettingsToggler.setSecureInt(device, "touch_exploration_enabled", 1);
}
- static void disableAccessibilityAndServicesAndTouchExploration(ITestDevice device)
+ static void disableAccessibilityAndServices(ITestDevice device)
throws DeviceNotAvailableException {
SettingsToggler.updateSecureString(device, "enabled_accessibility_services", "");
+ SettingsToggler.updateSecureString(device,
+ "touch_exploration_granted_accessibility_services", "");
SettingsToggler.updateSecureInt(device, "accessibility_enabled", 0);
- SettingsToggler.updateSecureInt(device, "touch_exploration_enabled", 0);
}
}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/CtsTest.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/CtsTest.java
index 19ee3b7..9188dca 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/CtsTest.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/CtsTest.java
@@ -40,6 +40,7 @@
import com.android.tradefed.testtype.IRemoteTest;
import com.android.tradefed.testtype.IResumableTest;
import com.android.tradefed.testtype.IShardableTest;
+import com.android.tradefed.util.RunUtil;
import com.android.tradefed.util.xml.AbstractXmlParser.ParseException;
import java.io.BufferedInputStream;
@@ -149,6 +150,20 @@
"Interval between each reboot in min.")
private int mRebootIntervalMin = 30;
+ @Option(name = "screenshot-on-ui-failure", description =
+ "take a screenshot if a test fails with a message indicating a UI obstruction.")
+ private boolean mScreenshotOnUiFailures = false;
+
+ @Option(name = "logcat-on-failure", description =
+ "take a logcat snapshot on every test failure. Unlike --bugreport, this can capture" +
+ "logs even if connection with device has been lost, as well as being much more " +
+ "performant.")
+ private boolean mLogcatOnFailures = false;
+
+ @Option(name = "logcat-on-failure-size", description =
+ "The max number of logcat data in bytes to capture when --logcat-on-failure is on. " +
+ "Should be an amount that can comfortably fit in memory.")
+ private int mMaxLogcatBytes = 500 * 1024; // 500K
private long mPrevRebootTime; // last reboot time
@@ -200,12 +215,72 @@
public void testFailed(TestFailure status, TestIdentifier test, String trace) {
super.testFailed(status, test, trace);
InputStreamSource bugSource = mDevice.getBugreport();
- super.testLog(String.format("bug-%s", test.toString()), LogDataType.TEXT,
- bugSource);
+ super.testLog(String.format("bug-%s_%s", test.getClassName(), test.getTestName()),
+ LogDataType.TEXT, bugSource);
bugSource.cancel();
}
}
+ /**
+ * A {@link ResultForwarder} that will forward a logcat snapshot on each failed test.
+ */
+ private static class FailedTestLogcatGenerator extends ResultForwarder {
+ private ITestDevice mDevice;
+ private int mNumLogcatBytes;
+
+ public FailedTestLogcatGenerator(ITestInvocationListener listener, ITestDevice device,
+ int maxLogcatBytes) {
+ super(listener);
+ mDevice = device;
+ mNumLogcatBytes = maxLogcatBytes;
+ }
+
+ @Override
+ public void testFailed(TestFailure status, TestIdentifier test, String trace) {
+ super.testFailed(status, test, trace);
+ // sleep a small amount of time to ensure test failure stack trace makes it into logcat
+ // capture
+ RunUtil.getDefault().sleep(10);
+ InputStreamSource logSource = mDevice.getLogcat(mNumLogcatBytes);
+ super.testLog(String.format("logcat-%s_%s", test.getClassName(), test.getTestName()),
+ LogDataType.TEXT, logSource);
+ logSource.cancel();
+ }
+ }
+
+ /**
+ * A {@link ResultForwarder} that will forward a screenshot when it detects a failed test due
+ * to a UI obstruction.
+ */
+ private static class FailedUiTestScreenshotGenerator extends ResultForwarder {
+ private ITestDevice mDevice;
+
+ public FailedUiTestScreenshotGenerator(ITestInvocationListener listener,
+ ITestDevice device) {
+ super(listener);
+ mDevice = device;
+ }
+
+ @Override
+ public void testFailed(TestFailure status, TestIdentifier test, String trace) {
+ super.testFailed(status, test, trace);
+
+ if (trace.contains(
+ "Injecting to another application requires INJECT_EVENTS permission")) {
+ try {
+ InputStreamSource screenSource = mDevice.getScreenshot();
+ super.testLog(String.format("screenshot-%s_%s", test.getClassName(),
+ test.getTestName()), LogDataType.PNG, screenSource);
+ screenSource.cancel();
+ } catch (DeviceNotAvailableException e) {
+ // TODO: rethrow this somehow
+ CLog.e("Device %s became unavailable while capturing screenshot, %s",
+ mDevice.getSerialNumber(), e.toString());
+ }
+ }
+ }
+ }
+
/** list of remaining tests to execute */
private List<TestPackage> mRemainingTestPkgs = null;
@@ -337,6 +412,16 @@
getDevice());
listener = bugListener;
}
+ if (mScreenshotOnUiFailures) {
+ FailedUiTestScreenshotGenerator screenListener = new FailedUiTestScreenshotGenerator(
+ listener, getDevice());
+ listener = screenListener;
+ }
+ if (mLogcatOnFailures) {
+ FailedTestLogcatGenerator logcatListener = new FailedTestLogcatGenerator(
+ listener, getDevice(), mMaxLogcatBytes);
+ listener = logcatListener;
+ }
// collect and install the prerequisiteApks first, to save time when multiple test
// packages are using the same prerequisite apk (I'm looking at you, CtsTestStubs!)
@@ -424,7 +509,7 @@
}
private void rebootDevice() throws DeviceNotAvailableException {
- final int TIMEOUT_MS = 4 * 60 * 1000;
+ final int TIMEOUT_MS = 10 * 60 * 1000;
TestDeviceOptions options = mDevice.getOptions();
// store default value and increase time-out for reboot
int rebootTimeout = options.getRebootTimeout();
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/DisplayTestRunner.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/DisplayTestRunner.java
new file mode 100644
index 0000000..4c83aa9
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/DisplayTestRunner.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.tradefed.testtype;
+
+import com.android.cts.tradefed.targetprep.SettingsToggler;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.result.ITestInvocationListener;
+
+/**
+ * Running the display tests requires modification of secure settings to create an overlay display.
+ * Secure settings cannot be changed from device CTS tests since system signature permission is
+ * required. Such settings can be modified by the shell user, so a host side test is used.
+ */
+public class DisplayTestRunner extends InstrumentationApkTest {
+ private static final String OVERLAY_DISPLAY_DEVICES_SETTING_NAME = "overlay_display_devices";
+
+ // Use a non-standard pattern, must match values in tests/tests/display/.../DisplayTest.java
+ private static final String OVERLAY_DISPLAY_DEVICES_SETTING_VALUE = "1281x721/214";
+
+ @Override
+ public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
+ // CLog.e("run: About to enable overlay display.");
+ SettingsToggler.setGlobalString(getDevice(), OVERLAY_DISPLAY_DEVICES_SETTING_NAME,
+ OVERLAY_DISPLAY_DEVICES_SETTING_VALUE);
+
+ super.run(listener);
+
+ // Tear down overlay display.
+ // CLog.e("run: About to disable overlay display.");
+ SettingsToggler.setGlobalString(getDevice(), OVERLAY_DISPLAY_DEVICES_SETTING_NAME,
+ "");
+ }
+}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageDef.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageDef.java
index ffbb930..2d11657 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageDef.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageDef.java
@@ -16,6 +16,7 @@
package com.android.cts.tradefed.testtype;
+import com.android.ddmlib.Log.LogLevel;
import com.android.ddmlib.testrunner.TestIdentifier;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.testtype.IRemoteTest;
@@ -43,11 +44,15 @@
public static final String HOST_SIDE_ONLY_TEST = "hostSideOnly";
public static final String NATIVE_TEST = "native";
+ public static final String WRAPPED_NATIVE_TEST = "wrappednative";
public static final String VM_HOST_TEST = "vmHostTest";
public static final String ACCESSIBILITY_TEST =
"com.android.cts.tradefed.testtype.AccessibilityTestRunner";
public static final String ACCESSIBILITYSERVICE_TEST =
"com.android.cts.tradefed.testtype.AccessibilityServiceTestRunner";
+ public static final String DISPLAY_TEST =
+ "com.android.cts.tradefed.testtype.DisplayTestRunner";
+ public static final String UIAUTOMATOR_TEST = "uiAutomator";
private static final String SIGNATURE_TEST_METHOD = "testSignature";
private static final String SIGNATURE_TEST_CLASS = "android.tests.sigtest.SimpleSignatureTest";
@@ -219,12 +224,21 @@
return vmHostTest;
} else if (NATIVE_TEST.equals(mTestType)) {
return new GeeTest(mUri, mName);
+ } else if (WRAPPED_NATIVE_TEST.equals(mTestType)) {
+ CLog.d("Creating new wrapped native test for %s", mName);
+ return new WrappedGTest(mAppNameSpace, mUri, mName, mRunner);
} else if (ACCESSIBILITY_TEST.equals(mTestType)) {
AccessibilityTestRunner test = new AccessibilityTestRunner();
return setInstrumentationTest(test, testCaseDir);
} else if (ACCESSIBILITYSERVICE_TEST.equals(mTestType)) {
AccessibilityServiceTestRunner test = new AccessibilityServiceTestRunner();
return setInstrumentationTest(test, testCaseDir);
+ } else if (DISPLAY_TEST.equals(mTestType)) {
+ DisplayTestRunner test = new DisplayTestRunner();
+ return setInstrumentationTest(test, testCaseDir);
+ } else if (UIAUTOMATOR_TEST.equals(mTestType)) {
+ UiAutomatorJarTest uiautomatorTest = new UiAutomatorJarTest();
+ return setUiAutomatorTest(uiautomatorTest);
} else if (mIsSignatureTest) {
// TODO: hardcode the runner/class/method for now, since current package xml points to
// specialized instrumentation. Eventually this special case for signatureTest can be
@@ -285,6 +299,28 @@
}
/**
+ * Populates given {@link UiAutomatorJarTest} with data from the package xml.
+ *
+ * @param uiautomatorTest
+ * @return the populated {@link UiAutomatorJarTest} or <code>null</code>
+ */
+ private IRemoteTest setUiAutomatorTest(UiAutomatorJarTest uiautomatorTest) {
+ uiautomatorTest.setInstallArtifacts(getJarPath());
+ if (mClassName != null) {
+ if (mMethodName != null) {
+ CLog.logAndDisplay(LogLevel.WARN, "ui automator tests don't currently support" +
+ "running individual methods");
+ }
+ uiautomatorTest.addClassName(mClassName);
+ } else {
+ uiautomatorTest.addClassNames(mTestClasses);
+ }
+ uiautomatorTest.setRunName(getUri());
+ uiautomatorTest.setCaptureLogs(false);
+ return uiautomatorTest;
+ }
+
+ /**
* Filter the tests to run based on list of excluded tests, class and method name.
*
* @return the filtered collection of tests
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/UiAutomatorJarTest.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/UiAutomatorJarTest.java
new file mode 100644
index 0000000..83cc6d6
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/UiAutomatorJarTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.tradefed.testtype;
+
+import com.android.cts.tradefed.build.CtsBuildHelper;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.testtype.UiAutomatorTest;
+
+import java.io.FileNotFoundException;
+import java.util.Arrays;
+
+import junit.framework.Assert;
+
+/**
+ * A {@link UiAutomatorTest} that will install a uiautomator jar before test
+ * execution, and uninstall on execution completion.
+ */
+public class UiAutomatorJarTest extends UiAutomatorTest implements IBuildReceiver {
+
+ // TODO: expose this in parent
+ private static final String SHELL_EXE_BASE = "/data/local/tmp/";
+
+ /** the file names of the CTS jar to install */
+ private String mTestJarFileName;
+
+ private CtsBuildHelper mCtsBuild = null;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setBuild(IBuildInfo build) {
+ mCtsBuild = CtsBuildHelper.createBuildHelper(build);
+ }
+
+ /**
+ * Setter for CTS build files needed to perform the test.
+ *
+ * @param testJarName the file name of the jar containing the uiautomator tests
+ */
+ public void setInstallArtifacts(String testJarName) {
+ mTestJarFileName = testJarName;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void run(final ITestInvocationListener listener)
+ throws DeviceNotAvailableException {
+ Assert.assertNotNull("missing device", getDevice());
+ Assert.assertNotNull("missing build", mCtsBuild);
+ Assert.assertNotNull("missing jar to install", mTestJarFileName);
+
+ installJar();
+
+ super.run(listener);
+
+ uninstallJar();
+ }
+
+ private void installJar() throws DeviceNotAvailableException {
+ CLog.d("Installing %s on %s", mTestJarFileName, getDevice().getSerialNumber());
+ String fullJarPath = String.format("%s%s", SHELL_EXE_BASE, mTestJarFileName);
+ try {
+ boolean result = getDevice().pushFile(mCtsBuild.getTestApp(mTestJarFileName),
+ fullJarPath);
+ Assert.assertTrue(String.format("Failed to push file to %s", fullJarPath), result);
+ setTestJarPaths(Arrays.asList(fullJarPath));
+ } catch (FileNotFoundException e) {
+ Assert.fail(String.format("Could not find file %s", mTestJarFileName));
+ }
+ }
+
+ private void uninstallJar() throws DeviceNotAvailableException {
+ CLog.d("Uninstalling %s on %s", mTestJarFileName, getDevice().getSerialNumber());
+ String fullJarPath = String.format("%s%s", SHELL_EXE_BASE, mTestJarFileName);
+ getDevice().executeShellCommand(String.format("rm %s", fullJarPath));
+ }
+}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/WrappedGTest.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/WrappedGTest.java
new file mode 100644
index 0000000..74c15f6
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/WrappedGTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.tradefed.testtype;
+
+import com.android.cts.tradefed.build.CtsBuildHelper;
+import com.android.ddmlib.testrunner.ITestRunListener;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.testtype.IDeviceTest;
+import com.android.tradefed.testtype.IRemoteTest;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+
+/**
+ * Test runner for wrapped (native) GTests
+ */
+public class WrappedGTest implements IBuildReceiver, IDeviceTest, IRemoteTest {
+
+ private int mMaxTestTimeMs = 1 * 60 * 1000;
+
+ private CtsBuildHelper mCtsBuild;
+ private ITestDevice mDevice;
+
+ private final String mAppNameSpace;
+ private final String mRunner;
+ private final String mName;
+ private final String mUri;
+
+
+ public WrappedGTest(String appNameSpace, String uri, String name, String runner) {
+ mAppNameSpace = appNameSpace;
+ mRunner = runner;
+ mName = name;
+ mUri = uri;
+ }
+
+ @Override
+ public void setBuild(IBuildInfo buildInfo) {
+ mCtsBuild = CtsBuildHelper.createBuildHelper(buildInfo);
+ }
+
+ @Override
+ public void setDevice(ITestDevice device) {
+ mDevice = device;
+ }
+
+ @Override
+ public ITestDevice getDevice() {
+ return mDevice;
+ }
+
+ @Override
+ public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
+ if (installTest()) {
+ runTest(listener);
+ uninstallTest();
+ } else {
+ CLog.e("Failed to install test");
+ }
+ }
+
+ private boolean installTest() throws DeviceNotAvailableException {
+ try {
+ File testApp = mCtsBuild.getTestApp(String.format("%s.apk", mName));
+ String installCode = mDevice.installPackage(testApp, true);
+
+ if (installCode != null) {
+ CLog.e("Failed to install %s.apk on %s. Reason: %s", mName,
+ mDevice.getSerialNumber(), installCode);
+ return false;
+ }
+ }
+ catch (FileNotFoundException e) {
+ CLog.e("Package %s.apk not found", mName);
+ return false;
+ }
+ return true;
+ }
+
+ private void runTest(ITestRunListener listener) throws DeviceNotAvailableException {
+ WrappedGTestResultParser resultParser = new WrappedGTestResultParser(mUri, listener);
+ resultParser.setFakePackagePrefix(mUri + ".");
+ try {
+ String command = String.format("am instrument -w %s/.%s", mAppNameSpace, mRunner);
+ mDevice.executeShellCommand(command, resultParser, mMaxTestTimeMs, 0);
+ } catch (DeviceNotAvailableException e) {
+ resultParser.flush();
+ throw e;
+ } catch (RuntimeException e) {
+ resultParser.flush();
+ throw e;
+ }
+ }
+
+ private void uninstallTest() throws DeviceNotAvailableException {
+ mDevice.uninstallPackage(mAppNameSpace);
+ }
+}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/WrappedGTestResultParser.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/WrappedGTestResultParser.java
new file mode 100644
index 0000000..cc3c53a
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/WrappedGTestResultParser.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.tradefed.testtype;
+
+import com.android.ddmlib.testrunner.ITestRunListener;
+import com.android.tradefed.log.LogUtil.CLog;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.ArrayList;
+
+public class WrappedGTestResultParser extends GeeTestResultParser {
+
+ private boolean mInstrumentationError;
+
+ /**
+ * Creates the WrappedGTestResultParser.
+ *
+ * @param testRunName the test run name to provide to
+ * {@link ITestRunListener#testRunStarted(String, int)}
+ * @param listeners informed of test results as the tests are executing
+ */
+ public WrappedGTestResultParser(String testRunName, Collection<ITestRunListener> listeners) {
+ super(testRunName, listeners);
+ }
+
+ /**
+ * Creates the WrappedGTestResultParser for a single listener.
+ *
+ * @param testRunName the test run name to provide to
+ * {@link ITestRunListener#testRunStarted(String, int)}
+ * @param listener informed of test results as the tests are executing
+ */
+ public WrappedGTestResultParser(String testRunName, ITestRunListener listener) {
+ super(testRunName, listener);
+ }
+
+ /**
+ * Strips the instrumentation information and then forwards
+ * the raw gtest output to the {@link GeeTestResultParser}.
+ */
+ @Override
+ public void processNewLines(String[] lines) {
+ if (mInstrumentationError) {
+ return;
+ }
+
+ String[] gtestOutput = parseInstrumentation(lines);
+ super.processNewLines(gtestOutput);
+ }
+
+ /**
+ * Parses raw instrumentation output and returns the
+ * contained gtest output
+ *
+ * @param lines the raw instrumentation output
+ * @return the gtest output
+ */
+ public String[] parseInstrumentation(String[] lines) {
+ List<String> output = new ArrayList<String>();
+ boolean readMultiLine = false;
+ for (String line : lines) {
+
+ if (line.startsWith("INSTRUMENTATION_RESULT: ")) {
+ CLog.e("Instrumentation Error:");
+ mInstrumentationError = true;
+ }
+
+ if (mInstrumentationError) {
+ CLog.e(line);
+ continue;
+ }
+
+ if (line.startsWith("INSTRUMENTATION_STATUS: gtest=")) {
+ output.add(line.replace("INSTRUMENTATION_STATUS: gtest=", ""));
+ readMultiLine = true;
+ continue;
+ }
+
+ if (line.startsWith("INSTRUMENTATION_")) {
+ readMultiLine = false;
+ continue;
+ }
+
+ if (readMultiLine) {
+ output.add(line);
+ }
+ }
+
+ return output.toArray(new String[output.size()]);
+ }
+}
+
diff --git a/tools/tradefed-host/tests/src/com/android/cts/tradefed/UnitTests.java b/tools/tradefed-host/tests/src/com/android/cts/tradefed/UnitTests.java
index 7dfac13..3c36a3d 100644
--- a/tools/tradefed-host/tests/src/com/android/cts/tradefed/UnitTests.java
+++ b/tools/tradefed-host/tests/src/com/android/cts/tradefed/UnitTests.java
@@ -27,6 +27,7 @@
import com.android.cts.tradefed.testtype.TestPackageDefTest;
import com.android.cts.tradefed.testtype.TestPackageXmlParserTest;
import com.android.cts.tradefed.testtype.TestPlanTest;
+import com.android.cts.tradefed.testtype.WrappedGTestResultParserTest;
import junit.framework.Test;
import junit.framework.TestSuite;
@@ -57,6 +58,7 @@
addTestSuite(TestPackageDefTest.class);
addTestSuite(TestPackageXmlParserTest.class);
addTestSuite(TestPlanTest.class);
+ addTestSuite(WrappedGTestResultParserTest.class);
}
public static Test suite() {
diff --git a/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/WrappedGTestResultParserTest.java b/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/WrappedGTestResultParserTest.java
new file mode 100644
index 0000000..d386c7a
--- /dev/null
+++ b/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/WrappedGTestResultParserTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.tradefed.testtype;
+
+import com.android.ddmlib.testrunner.ITestRunListener;
+
+import junit.framework.TestCase;
+
+
+
+/**
+ * Unit tests for {@link WrappedGTestResultParser}.
+ */
+public class WrappedGTestResultParserTest extends TestCase {
+
+ private WrappedGTestResultParser mParser;
+ private final String[] INPUT1 = new String[] {
+ "INSTRUMENTATION_STATUS: gtest=[==========] Running 9 tests from 2 test cases.",
+ "INSTRUMENTATION_STATUS_CODE: 1",
+ "INSTRUMENTATION_STATUS: gtest=[ RUN ] GLTest.Test1",
+ "INSTRUMENTATION_STATUS: gtest=[ OK ] GLTest.Test1 (10 ms)",
+ "INSTRUMENTATION_STATUS: gtest=/tests/SomeTestFile.cpp:1337: Failure",
+ "Value of: 1 == 0",
+ " Actual: false",
+ "Expected: true",
+ "INSTRUMENTATION_STATUS: gtest=[ FAILED ] GLTest.Test2 (1016 ms)",
+ "INSTRUMENTATION_STATUS: gtest=[==========] 2 tests from 1 test cases ran. (17 ms total)",
+ "INSTRUMENTATION_CODE: -1"
+ };
+
+ private final String[] EXPECTED_OUTPUT1 = new String[] {
+ "[==========] Running 9 tests from 2 test cases.",
+ "[ RUN ] GLTest.Test1",
+ "[ OK ] GLTest.Test1 (10 ms)",
+ "/tests/SomeTestFile.cpp:1337: Failure",
+ "Value of: 1 == 0",
+ " Actual: false",
+ "Expected: true",
+ "[ FAILED ] GLTest.Test2 (1016 ms)",
+ "[==========] 2 tests from 1 test cases ran. (17 ms total)",
+ };
+
+ private final String[] INPUT2 = new String[] {
+ "INSTRUMENTATION_STATUS_CODE: 1",
+ "invalid text",
+ "INSTRUMENTATION_STATUS: gtest=[==========] Running 9 tests from 2 test cases.",
+ "INSTRUMENTATION_RESULT: some error",
+ "INSTRUMENTATION_STATUS: gtest=[ RUN ] GLTest.ExpectTestThatShouldBeSuccessful",
+ };
+
+ private final String[] EXPECTED_OUTPUT2 = new String[] {
+ "[==========] Running 9 tests from 2 test cases.",
+ };
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mParser = new WrappedGTestResultParser("unused", (ITestRunListener)null);
+ }
+
+ private void assertArrayEquals(String[] expected, String[] result) throws Exception {
+ if (expected == null) {
+ assertNull(result);
+ return;
+ }
+
+ assertEquals(expected.length, result.length);
+
+ for (int i = 0; i < expected.length; i++) {
+ assertEquals(expected[i], result[i]);
+ }
+ }
+
+ /**
+ * Test normal case {@link WrappedGTestResultParser#getRawGTestOutput(java.lang.String[])}
+ * with all kinds of valid input lines.
+ */
+ public void testGetRawGTestOutput_valid() throws Exception {
+ assertArrayEquals(EXPECTED_OUTPUT1, mParser.parseInstrumentation(INPUT1));
+ }
+
+ /**
+ * Test normal case {@link WrappedGTestResultParser#getRawGTestOutput(java.lang.String[])}
+ * with a instrumentation error/invalid input.
+ */
+ public void testGetRawGTestOutput_invalid() throws Exception {
+ assertArrayEquals(EXPECTED_OUTPUT2, mParser.parseInstrumentation(INPUT2));
+ }
+}
diff --git a/tools/utils/buildCts.py b/tools/utils/buildCts.py
index 8e23f13..2eda363 100755
--- a/tools/utils/buildCts.py
+++ b/tools/utils/buildCts.py
@@ -54,24 +54,20 @@
def __init__(self, argv):
"""Initialize the CtsBuilder from command line arguments."""
- if not (len(argv) == 6 or len(argv)==7):
- print 'Usage: %s <testRoot> <ctsOutputDir> <tempDir> <androidRootDir> <docletPath> [-pts]' % argv[0]
+ if len(argv) != 6:
+ print 'Usage: %s <testRoot> <ctsOutputDir> <tempDir> <androidRootDir> <docletPath>' % argv[0]
print ''
print 'testRoot: Directory under which to search for CTS tests.'
print 'ctsOutputDir: Directory in which the CTS repository should be created.'
print 'tempDir: Directory to use for storing temporary files.'
print 'androidRootDir: Root directory of the Android source tree.'
print 'docletPath: Class path where the DescriptionGenerator doclet can be found.'
- print '-pts: generate plan for PTS.'
sys.exit(1)
self.test_root = sys.argv[1]
self.out_dir = sys.argv[2]
self.temp_dir = sys.argv[3]
self.android_root = sys.argv[4]
self.doclet_path = sys.argv[5]
- self.isCts = True
- if len(argv) ==7 and sys.argv[6] == "-pts":
- self.isCts = False
self.test_repository = os.path.join(self.out_dir, 'repository/testcases')
self.plan_repository = os.path.join(self.out_dir, 'repository/plans')
@@ -102,36 +98,46 @@
for description in descriptions:
doc = tools.XmlFile(description)
packages.append(doc.GetAttr('TestPackage', 'appPackageName'))
+ # sort the list to give the same sequence based on name
+ packages.sort()
- if not self.isCts: # PTS
- plan = tools.TestPlan(packages)
- plan.Include('.*')
- plan.Exclude(r'android\.tests\.sigtest')
- self.__WritePlan(plan, 'PTS')
- return
+ ptsPattern = r'com\.android\.pts\..*'
+ plan = tools.TestPlan(packages)
+ plan.Exclude('.*')
+ plan.Include(ptsPattern)
+ self.__WritePlan(plan, 'PTS')
plan = tools.TestPlan(packages)
plan.Exclude('android\.performance.*')
self.__WritePlan(plan, 'CTS')
self.__WritePlan(plan, 'CTS-TF')
+ plan = tools.TestPlan(packages)
+ plan.Exclude(ptsPattern)
+ plan.Exclude('android\.performance.*')
+ self.__WritePlan(plan, 'SDK')
+
plan.Exclude(r'android\.tests\.sigtest')
plan.Exclude(r'android\.core.*')
self.__WritePlan(plan, 'Android')
plan = tools.TestPlan(packages)
+ plan.Exclude(ptsPattern)
plan.Include(r'android\.core\.tests.*')
self.__WritePlan(plan, 'Java')
plan = tools.TestPlan(packages)
+ plan.Exclude(ptsPattern)
plan.Include(r'android\.core\.vm-tests-tf')
self.__WritePlan(plan, 'VM-TF')
plan = tools.TestPlan(packages)
+ plan.Exclude(ptsPattern)
plan.Include(r'android\.tests\.sigtest')
self.__WritePlan(plan, 'Signature')
plan = tools.TestPlan(packages)
+ plan.Exclude(ptsPattern)
plan.Include(r'android\.tests\.appsecurity')
self.__WritePlan(plan, 'AppSecurity')
@@ -165,9 +171,8 @@
if __name__ == '__main__':
builder = CtsBuilder(sys.argv)
- if builder.isCts:
- result = builder.GenerateTestDescriptions()
- if result != 0:
- sys.exit(result)
+ result = builder.GenerateTestDescriptions()
+ if result != 0:
+ sys.exit(result)
builder.GenerateTestPlans()