Merge "AudioTrackTest: added testGetTimestamp."
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 8f50535..099cef2 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -21,7 +21,7 @@
android:versionName="4.4_r1.95">
<!-- Using 10+ for more complete NFC support... -->
- <uses-sdk android:minSdkVersion="12"></uses-sdk>
+ <uses-sdk android:minSdkVersion="19"></uses-sdk>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
diff --git a/apps/CtsVerifier/res/layout/ca_boot_notify.xml b/apps/CtsVerifier/res/layout/ca_boot_notify.xml
index 29fa549..e9309d4 100644
--- a/apps/CtsVerifier/res/layout/ca_boot_notify.xml
+++ b/apps/CtsVerifier/res/layout/ca_boot_notify.xml
@@ -19,47 +19,51 @@
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent">
+ <ScrollView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_alignParentTop="true" >
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content">
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
- <TextView
- android:id="@+id/check_cert_desc"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/caboot_check_cert_installed"/>
+ <TextView
+ android:id="@+id/check_cert_desc"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/caboot_check_cert_installed"/>
- <Button android:id="@+id/check_creds"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/caboot_check_creds" />
+ <Button android:id="@+id/check_creds"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/caboot_check_creds" />
- <TextView
- android:id="@+id/need_to_install_cert"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/caboot_if_not_installed"/>
+ <TextView
+ android:id="@+id/need_to_install_cert"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/caboot_if_not_installed"/>
- <Button android:id="@+id/install"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/caboot_install_cert" />
+ <Button android:id="@+id/install"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/caboot_install_cert" />
- <TextView
- android:id="@+id/reboot"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/caboot_reboot_desc"/>
+ <TextView
+ android:id="@+id/reboot"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/caboot_reboot_desc"/>
- <TextView
- android:id="@+id/after_reboot"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/caboot_after_boot"/>
- </LinearLayout>
+ <TextView
+ android:id="@+id/after_reboot"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/caboot_after_boot"/>
- <include layout="@layout/pass_fail_buttons" />
-
+ <include layout="@layout/pass_fail_buttons" />
+ </LinearLayout>
+ </ScrollView>
</LinearLayout>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/AccelerometerMeasurementTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/AccelerometerMeasurementTestActivity.java
index 44bed6c..5a0af28 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/AccelerometerMeasurementTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/AccelerometerMeasurementTestActivity.java
@@ -18,7 +18,8 @@
import android.hardware.Sensor;
import android.hardware.SensorManager;
-import android.hardware.cts.helpers.sensoroperations.VerifySensorOperation;
+import android.hardware.cts.helpers.sensoroperations.TestSensorOperation;
+import android.hardware.cts.helpers.sensorverification.MeanVerification;
import java.util.concurrent.TimeUnit;
@@ -74,15 +75,15 @@
*/
private void verifyMeasurements(float ... expectations) throws Throwable {
Thread.sleep(500 /*ms*/);
- VerifySensorOperation verifyMeasurements = new VerifySensorOperation(
+ TestSensorOperation verifyMeasurements = new TestSensorOperation(
getApplicationContext(),
Sensor.TYPE_ACCELEROMETER,
SensorManager.SENSOR_DELAY_FASTEST,
0 /*reportLatencyInUs*/,
100 /* event count */);
- verifyMeasurements.verifyMean(
+ verifyMeasurements.addVerification(new MeanVerification(
expectations,
- new float[]{1.95f, 1.95f, 1.95f} /* m / s^2 */);
+ new float[]{1.95f, 1.95f, 1.95f} /* m / s^2 */));
verifyMeasurements.execute();
logSuccess();
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/GyroscopeMeasurementTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/GyroscopeMeasurementTestActivity.java
index cdb5b3f..066bda4 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/GyroscopeMeasurementTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/GyroscopeMeasurementTestActivity.java
@@ -18,7 +18,8 @@
import android.hardware.Sensor;
import android.hardware.SensorManager;
-import android.hardware.cts.helpers.sensoroperations.VerifySensorOperation;
+import android.hardware.cts.helpers.sensoroperations.TestSensorOperation;
+import android.hardware.cts.helpers.sensorverification.SigNumVerification;
/**
* Semi-automated test that focuses on characteristics associated with Accelerometer measurements.
@@ -96,15 +97,15 @@
waitForUser();
Thread.sleep(500 /*ms*/);
- VerifySensorOperation verifySignum = new VerifySensorOperation(
+ TestSensorOperation verifySignum = new TestSensorOperation(
getApplicationContext(),
Sensor.TYPE_GYROSCOPE,
SensorManager.SENSOR_DELAY_FASTEST,
0 /*reportLatencyInUs*/,
100 /* event count */);
- verifySignum.verifySignum(
+ verifySignum.addVerification(new SigNumVerification(
expectations,
- new float[]{0.2f, 0.2f, 0.2f} /*noiseThreshold*/);
+ new float[]{0.2f, 0.2f, 0.2f} /*noiseThreshold*/));
verifySignum.execute();
logSuccess();
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/MagneticFieldMeasurementTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/MagneticFieldMeasurementTestActivity.java
index 115f20a..a131b2b 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/MagneticFieldMeasurementTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/MagneticFieldMeasurementTestActivity.java
@@ -19,9 +19,13 @@
import android.graphics.Color;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener2;
import android.hardware.SensorManager;
-import android.hardware.cts.helpers.SensorManagerTestVerifier;
-import android.hardware.cts.helpers.sensoroperations.VerifySensorOperation;
+import android.hardware.cts.helpers.TestSensorEventListener;
+import android.hardware.cts.helpers.TestSensorManager;
+import android.hardware.cts.helpers.sensoroperations.TestSensorOperation;
+import android.hardware.cts.helpers.sensorverification.MagnitudeVerification;
+import android.hardware.cts.helpers.sensorverification.StandardDeviationVerification;
/**
* Semi-automated test that focuses characteristics associated with Accelerometer measurements.
@@ -42,27 +46,34 @@
}
private void calibrateMagnetometer() {
- SensorManagerTestVerifier magnetometer = new SensorManagerTestVerifier(
- this.getApplicationContext(),
- Sensor.TYPE_MAGNETIC_FIELD,
- SensorManager.SENSOR_DELAY_NORMAL,
- 0 /*reportLatencyInUs*/) {
+ SensorEventListener2 listener = new SensorEventListener2() {
@Override
public void onSensorChanged(SensorEvent event) {
float values[] = event.values;
clearText();
- appendText(
- "Please calibrate the Magnetometer by moving it in 8 shapes in different " +
- "orientations.");
- appendText(
- String.format("-> (%.2f, %.2f, %.2f) uT", values[0], values[1], values[2]),
- Color.GRAY);
+ appendText("Please calibrate the Magnetometer by moving it in 8 shapes in "
+ + "different orientations.");
+ appendText(String.format("-> (%.2f, %.2f, %.2f) uT", values[0], values[1],
+ values[2]), Color.GRAY);
appendText("Then leave the device in a flat surface and press Next...\n");
}
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {}
+
+ @Override
+ public void onFlushCompleted(Sensor sensor) {}
};
- magnetometer.registerListener();
- waitForUser();
- magnetometer.unregisterListener();
+
+ TestSensorManager magnetometer = new TestSensorManager(
+ this.getApplicationContext(), Sensor.TYPE_MAGNETIC_FIELD,
+ SensorManager.SENSOR_DELAY_NORMAL, 0);
+ try {
+ magnetometer.registerListener(new TestSensorEventListener(listener));
+ waitForUser();
+ } finally {
+ magnetometer.unregisterListener();
+ }
}
/**
@@ -90,15 +101,15 @@
(SensorManager.MAGNETIC_FIELD_EARTH_MAX + SensorManager.MAGNETIC_FIELD_EARTH_MIN) / 2;
float magneticFieldEarthThreshold =
expectedMagneticFieldEarth - SensorManager.MAGNETIC_FIELD_EARTH_MIN;
- VerifySensorOperation verifyNorm = new VerifySensorOperation(
+ TestSensorOperation verifyNorm = new TestSensorOperation(
this.getApplicationContext(),
Sensor.TYPE_MAGNETIC_FIELD,
SensorManager.SENSOR_DELAY_FASTEST,
0 /*reportLatencyInUs*/,
100 /* event count */);
- verifyNorm.verifyMagnitude(
+ verifyNorm.addVerification(new MagnitudeVerification(
expectedMagneticFieldEarth,
- magneticFieldEarthThreshold);
+ magneticFieldEarthThreshold));
verifyNorm.execute();
logSuccess();
}
@@ -127,14 +138,14 @@
* the failure to help track down the issue.
*/
private void verifyStandardDeviation() throws Throwable {
- VerifySensorOperation verifyStdDev = new VerifySensorOperation(
+ TestSensorOperation verifyStdDev = new TestSensorOperation(
this.getApplicationContext(),
Sensor.TYPE_MAGNETIC_FIELD,
SensorManager.SENSOR_DELAY_FASTEST,
0 /*reportLatencyInUs*/,
100 /* event count */);
- verifyStdDev.verifyStandardDeviation(
- new float[]{2f, 2f, 2f} /* uT */);
+ verifyStdDev.addVerification(new StandardDeviationVerification(
+ new float[]{2f, 2f, 2f} /* uT */));
verifyStdDev.execute();
logSuccess();
}
diff --git a/common/Android.mk b/common/Android.mk
new file mode 100644
index 0000000..a8b6af7
--- /dev/null
+++ b/common/Android.mk
@@ -0,0 +1,15 @@
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+include $(call all-subdir-makefiles)
diff --git a/common/device-side/Android.mk b/common/device-side/Android.mk
new file mode 100644
index 0000000..a8b6af7
--- /dev/null
+++ b/common/device-side/Android.mk
@@ -0,0 +1,15 @@
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+include $(call all-subdir-makefiles)
diff --git a/common/device-side/device-setup/Android.mk b/common/device-side/device-setup/Android.mk
new file mode 100644
index 0000000..7326ccc
--- /dev/null
+++ b/common/device-side/device-setup/Android.mk
@@ -0,0 +1,27 @@
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE := cts-common-device-setup_v2
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/common/device-side/device-setup/src/android/cts/devicesetup/DeviceInfoConstants.java b/common/device-side/device-setup/src/android/cts/devicesetup/DeviceInfoConstants.java
new file mode 100644
index 0000000..5500d5e
--- /dev/null
+++ b/common/device-side/device-setup/src/android/cts/devicesetup/DeviceInfoConstants.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.devicesetup;
+
+/**
+ * Constants for device info attributes to be sent as instrumentation keys.
+ */
+public interface DeviceInfoConstants {
+
+ public static final String BUILD_ABI = "buildAbi";
+ public static final String BUILD_ABI2 = "buildAbi2";
+ public static final String BUILD_BOARD = "buildBoard";
+ public static final String BUILD_BRAND = "buildBrand";
+ public static final String BUILD_DEVICE = "buildDevice";
+ public static final String BUILD_FINGERPRINT = "buildFingerprint";
+ public static final String BUILD_ID = "buildId";
+ public static final String BUILD_MANUFACTURER = "buildManufacturer";
+ public static final String BUILD_MODEL = "buildModel";
+ public static final String BUILD_TAGS = "buildTags";
+ public static final String BUILD_TYPE = "buildType";
+ public static final String BUILD_VERSION = "buildVersion";
+
+ public static final String FEATURES = "features";
+
+ public static final String GRAPHICS_RENDERER = "graphicsRenderer";
+ public static final String GRAPHICS_VENDOR = "graphicsVendor";
+
+ public static final String IMEI = "imei";
+ public static final String IMSI = "imsi";
+
+ public static final String KEYPAD = "keypad";
+
+ public static final String LOCALES = "locales";
+
+ public static final String MULTI_USER = "multiUser";
+
+ public static final String NAVIGATION = "navigation";
+ public static final String NETWORK = "network";
+
+ public static final String OPEN_GL_ES_VERSION = "openGlEsVersion";
+ public static final String OPEN_GL_EXTENSIONS = "openGlExtensions";
+ public static final String OPEN_GL_COMPRESSED_TEXTURE_FORMATS =
+ "openGlCompressedTextureFormats";
+
+ public static final String PARTITIONS = "partitions";
+ public static final String PHONE_NUMBER = "phoneNumber";
+ public static final String PROCESSES = "processes";
+ public static final String PRODUCT_NAME = "productName";
+
+ public static final String RESOLUTION = "resolution";
+
+ public static final String SCREEN_DENSITY = "screenDensity";
+ public static final String SCREEN_DENSITY_BUCKET = "screenDensityBucket";
+ public static final String SCREEN_DENSITY_X = "screenDensityX";
+ public static final String SCREEN_DENSITY_Y = "screenDensityY";
+ public static final String SCREEN_SIZE = "screenSize";
+ public static final String SERIAL_NUMBER = "deviceId";
+ public static final String STORAGE_DEVICES = "storageDevices";
+ public static final String SYS_LIBRARIES = "systemLibraries";
+
+ public static final String TOUCH_SCREEN = "touch";
+
+ public static final String VERSION_RELEASE = "versionRelease";
+ public static final String VERSION_SDK_INT = "versionSdkInt";
+}
diff --git a/common/device-side/util/Android.mk b/common/device-side/util/Android.mk
new file mode 100644
index 0000000..5224c1f
--- /dev/null
+++ b/common/device-side/util/Android.mk
@@ -0,0 +1,29 @@
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := cts-common-util-device_v2
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE := cts-device-util_v2
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/common/device-side/util/src/android/cts/util/CtsResult.java b/common/device-side/util/src/android/cts/util/CtsResult.java
new file mode 100644
index 0000000..b8dfbce
--- /dev/null
+++ b/common/device-side/util/src/android/cts/util/CtsResult.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.cts.util;
+
+public interface CtsResult {
+ public static final int RESULT_OK = 1;
+ public static final int RESULT_FAIL = 2;
+ public void setResult(int resultCode);
+}
diff --git a/common/device-side/util/src/android/cts/util/DeviceReportLog.java b/common/device-side/util/src/android/cts/util/DeviceReportLog.java
new file mode 100644
index 0000000..75f937a
--- /dev/null
+++ b/common/device-side/util/src/android/cts/util/DeviceReportLog.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.util;
+
+import android.cts.util.ReportLog;
+
+import android.app.Instrumentation;
+import android.os.Bundle;
+import android.util.Log;
+
+/**
+ * Handles adding results to the report for device side tests.
+ *
+ * NOTE: tests MUST call {@link #submit(Instrumentation)} in the test's tearDown method.
+ */
+public class DeviceReportLog extends ReportLog {
+ private static final String TAG = DeviceReportLog.class.getSimpleName();
+ private static final String CTS_RESULT = "CTS_RESULT";
+ private static final int INST_STATUS_IN_PROGRESS = 2;
+
+ public void submit(Instrumentation instrumentation) {
+ Log.i(TAG, "submit");
+ Bundle output = new Bundle();
+ output.putSerializable(CTS_RESULT, this);
+ instrumentation.sendStatus(INST_STATUS_IN_PROGRESS, output);
+ }
+}
diff --git a/common/device-side/util/src/android/cts/util/EvaluateJsResultPollingCheck.java b/common/device-side/util/src/android/cts/util/EvaluateJsResultPollingCheck.java
new file mode 100644
index 0000000..65fb614
--- /dev/null
+++ b/common/device-side/util/src/android/cts/util/EvaluateJsResultPollingCheck.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.util;
+
+import android.webkit.ValueCallback;
+
+public class EvaluateJsResultPollingCheck extends PollingCheck
+ implements ValueCallback<String> {
+ private String mActualResult;
+ private String mExpectedResult;
+
+ public EvaluateJsResultPollingCheck(String expected) {
+ mExpectedResult = expected;
+ }
+
+ @Override
+ public synchronized boolean check() {
+ return mExpectedResult.equals(mActualResult);
+ }
+
+ @Override
+ public synchronized void onReceiveValue(String result) {
+ mActualResult = result;
+ }
+}
diff --git a/common/device-side/util/src/android/cts/util/FileCopyHelper.java b/common/device-side/util/src/android/cts/util/FileCopyHelper.java
new file mode 100644
index 0000000..8b5a8dc
--- /dev/null
+++ b/common/device-side/util/src/android/cts/util/FileCopyHelper.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.util;
+
+import android.content.Context;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+
+/**
+ * The Class FileCopyHelper is used to copy files from resources to the
+ * application directory and responsible for deleting the files.
+ *
+ * @see MediaStore_VideoTest
+ * @see MediaStore_Images_MediaTest
+ * @see MediaStore_Images_ThumbnailsTest
+ */
+public class FileCopyHelper {
+ /** The context. */
+ private Context mContext;
+
+ /** The files added. */
+ private ArrayList<String> mFilesList;
+
+ /**
+ * Instantiates a new file copy helper.
+ *
+ * @param context the context
+ */
+ public FileCopyHelper(Context context) {
+ mContext = context;
+ mFilesList = new ArrayList<String>();
+ }
+
+ /**
+ * Copy the file from the resources with a filename .
+ *
+ * @param resId the res id
+ * @param fileName the file name
+ *
+ * @return the absolute path of the destination file
+ * @throws IOException
+ */
+ public String copy(int resId, String fileName) throws IOException {
+ InputStream source = mContext.getResources().openRawResource(resId);
+ OutputStream target = mContext.openFileOutput(fileName, Context.MODE_WORLD_READABLE);
+ copyFile(source, target);
+ mFilesList.add(fileName);
+ return mContext.getFileStreamPath(fileName).getAbsolutePath();
+ }
+
+ public void copyToExternalStorage(int resId, File path) throws IOException {
+ InputStream source = mContext.getResources().openRawResource(resId);
+ OutputStream target = new FileOutputStream(path);
+ copyFile(source, target);
+ }
+
+ private void copyFile(InputStream source, OutputStream target) throws IOException {
+ try {
+ byte[] buffer = new byte[1024];
+ for (int len = source.read(buffer); len > 0; len = source.read(buffer)) {
+ target.write(buffer, 0, len);
+ }
+ } finally {
+ if (source != null) {
+ source.close();
+ }
+ if (target != null) {
+ target.close();
+ }
+ }
+ }
+
+ /**
+ * Delete all the files copied by the helper.
+ */
+ public void clear(){
+ for (String path : mFilesList) {
+ mContext.deleteFile(path);
+ }
+ }
+}
diff --git a/common/device-side/util/src/android/cts/util/PollingCheck.java b/common/device-side/util/src/android/cts/util/PollingCheck.java
new file mode 100644
index 0000000..5f2fe61
--- /dev/null
+++ b/common/device-side/util/src/android/cts/util/PollingCheck.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.util;
+
+import java.util.concurrent.Callable;
+
+import junit.framework.Assert;
+
+public abstract class PollingCheck {
+ private static final long TIME_SLICE = 50;
+ private long mTimeoutMs = 3000;
+
+ public PollingCheck() {
+ }
+
+ public PollingCheck(long timeoutMs) {
+ mTimeoutMs = timeoutMs;
+ }
+
+ protected abstract boolean check();
+
+ public void run() {
+ if (check()) {
+ return;
+ }
+
+ long timeoutMs = mTimeoutMs;
+ while (timeoutMs > 0) {
+ try {
+ Thread.sleep(TIME_SLICE);
+ } catch (InterruptedException e) {
+ Assert.fail("unexpected InterruptedException");
+ }
+
+ if (check()) {
+ return;
+ }
+
+ timeoutMs -= TIME_SLICE;
+ }
+
+ Assert.fail("unexpected timeout");
+ }
+
+ public static void check(CharSequence message, long timeoutMs, Callable<Boolean> condition)
+ throws Exception {
+ while (timeoutMs > 0) {
+ if (condition.call()) {
+ return;
+ }
+
+ Thread.sleep(TIME_SLICE);
+ timeoutMs -= TIME_SLICE;
+ }
+
+ Assert.fail(message.toString());
+ }
+}
diff --git a/common/device-side/util/src/android/cts/util/SystemUtil.java b/common/device-side/util/src/android/cts/util/SystemUtil.java
new file mode 100644
index 0000000..8849c9b
--- /dev/null
+++ b/common/device-side/util/src/android/cts/util/SystemUtil.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.util;
+
+import android.app.ActivityManager;
+import android.app.ActivityManager.MemoryInfo;
+import android.content.Context;
+import android.os.StatFs;
+
+public class SystemUtil {
+ public static long getFreeDiskSize(Context context) {
+ final StatFs statFs = new StatFs(context.getFilesDir().getAbsolutePath());
+ return (long)statFs.getAvailableBlocks() * statFs.getBlockSize();
+ }
+
+ public static long getFreeMemory(Context context) {
+ final MemoryInfo info = new MemoryInfo();
+ ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryInfo(info);
+ return info.availMem;
+ }
+
+ public static long getTotalMemory(Context context) {
+ final MemoryInfo info = new MemoryInfo();
+ ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryInfo(info);
+ return info.totalMem;
+ }
+}
diff --git a/common/device-side/util/src/android/cts/util/WatchDog.java b/common/device-side/util/src/android/cts/util/WatchDog.java
new file mode 100644
index 0000000..6511930
--- /dev/null
+++ b/common/device-side/util/src/android/cts/util/WatchDog.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.cts.util;
+
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+import android.util.Log;
+
+import junit.framework.Assert;
+
+/**
+ * class for checking if function is alive or not.
+ * panic if watch-dog is not reset over certain amount of time
+ */
+public class WatchDog implements Runnable {
+ private static final String TAG = WatchDog.class.getSimpleName();
+ private Thread mThread;
+ private Semaphore mSemaphore;
+ private volatile boolean mStopRequested;
+ private final long mTimeoutInMilliSecs;
+ private TimeoutCallback mCallback = null;
+
+ 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");
+ mStopRequested = false;
+ mSemaphore = new Semaphore(0);
+ mThread = new Thread(this);
+ mThread.start();
+ }
+
+ /** stop watch-dog */
+ public void stop() {
+ Log.i(TAG, "stop");
+ if (mThread == null) {
+ return; // already finished
+ }
+ mStopRequested = true;
+ mSemaphore.release();
+ try {
+ mThread.join();
+ } catch (InterruptedException e) {
+ // ignore
+ }
+ mThread = null;
+ mSemaphore = null;
+ }
+
+ /** resets watch-dog, thus prevent it from panic */
+ public void reset() {
+ if (!mStopRequested) { // stop requested, but rendering still on-going
+ mSemaphore.release();
+ }
+ }
+
+ @Override
+ public void run() {
+ while (!mStopRequested) {
+ try {
+ 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/common/host-side/Android.mk b/common/host-side/Android.mk
new file mode 100644
index 0000000..a8b6af7
--- /dev/null
+++ b/common/host-side/Android.mk
@@ -0,0 +1,15 @@
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+include $(call all-subdir-makefiles)
diff --git a/common/host-side/tradefed/Android.mk b/common/host-side/tradefed/Android.mk
new file mode 100644
index 0000000..5b5f15c
--- /dev/null
+++ b/common/host-side/tradefed/Android.mk
@@ -0,0 +1,59 @@
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Builds the cts tradefed host library
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+#LOCAL_JAVA_RESOURCE_DIRS := res
+
+LOCAL_MODULE := cts-tradefed_v2
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_JAVA_LIBRARIES := ddmlib-prebuilt tradefed-prebuilt hosttestlib cts-common-util-host_v2
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+###############################################################################
+# Builds the cts tradefed executable
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_PREBUILT_EXECUTABLES := cts-tradefed_v2
+
+include $(BUILD_HOST_PREBUILT)
+
+###############################################################################
+# Builds the cts tradefed tests
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, tests)
+
+LOCAL_MODULE := cts-tradefed-tests_v2
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_JAVA_LIBRARIES := ddmlib-prebuilt tradefed-prebuilt cts-tradefed_v2
+
+LOCAL_STATIC_JAVA_LIBRARIES := easymock
+
+include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/common/host-side/tradefed/cts-tradefed_v2 b/common/host-side/tradefed/cts-tradefed_v2
new file mode 100644
index 0000000..f64e273
--- /dev/null
+++ b/common/host-side/tradefed/cts-tradefed_v2
@@ -0,0 +1,16 @@
+#!/bin/bash
+#
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+echo "TODO(stuartscott): Add the wrapper to launch the executable. This will be done in the next CL"
diff --git a/common/host-side/tradefed/src/android/cts/tradefed/util/HostReportLog.java b/common/host-side/tradefed/src/android/cts/tradefed/util/HostReportLog.java
new file mode 100644
index 0000000..8857b5e
--- /dev/null
+++ b/common/host-side/tradefed/src/android/cts/tradefed/util/HostReportLog.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.tradefed.util;
+
+import android.cts.util.ReportLog;
+
+/**
+ * ReportLog for host tests
+ * Note that setTestInfo should be set before throwing report
+ */
+public class HostReportLog extends ReportLog {
+
+ private String mKey;
+
+ /**
+ * @param deviceSerial serial number of the device
+ */
+ public HostReportLog(String deviceSerial) {
+ final StackTraceElement e = Thread.currentThread().getStackTrace()[1];
+ mKey = String.format("%s#%s#%s", deviceSerial, e.getClassName(), e.getMethodName());
+ }
+
+ public void submit() {
+ ResultStore.addResult(mKey, this);
+ }
+}
diff --git a/common/host-side/tradefed/src/android/cts/tradefed/util/ResultStore.java b/common/host-side/tradefed/src/android/cts/tradefed/util/ResultStore.java
new file mode 100644
index 0000000..0e2be7c
--- /dev/null
+++ b/common/host-side/tradefed/src/android/cts/tradefed/util/ResultStore.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.cts.tradefed.util;
+
+import android.cts.util.ReportLog;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Utility class for storing Cts Results.
+ * This is necessary for host tests where test metrics cannot be passed.
+ */
+public class ResultStore {
+
+ // needs concurrent verion as there can be multiple client accessing this.
+ // But there is no additional protection for the same key as that should not happen.
+ private static final ConcurrentHashMap<String, ReportLog> mMap =
+ new ConcurrentHashMap<String, ReportLog>();
+
+ /**
+ * Stores CTS result. Existing result with the same key will be replaced.
+ * Note that key is generated in the form of device_serial#class#method_name.
+ * So there should be no concurrent test for the same (serial, class, method).
+ * @param key
+ * @param result CTS result
+ */
+ public static void addResult(String key, ReportLog result) {
+ mMap.put(key, result);
+ }
+
+ /**
+ * retrieves a CTS result for the given condition and remove it from the internal
+ * storage. If there is no result for the given condition, it will return null.
+ */
+ public static ReportLog removeResult(String key) {
+ return mMap.remove(key);
+ }
+
+}
diff --git a/common/util/Android.mk b/common/util/Android.mk
new file mode 100644
index 0000000..6b40a8d
--- /dev/null
+++ b/common/util/Android.mk
@@ -0,0 +1,39 @@
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE := cts-common-util-device_v2
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+###############################################################################
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE := cts-common-util-host_v2
+
+include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/common/util/src/android/cts/util/MeasureRun.java b/common/util/src/android/cts/util/MeasureRun.java
new file mode 100644
index 0000000..2256033
--- /dev/null
+++ b/common/util/src/android/cts/util/MeasureRun.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.util;
+
+/**
+ * Interface for measuring time for each run.
+ */
+public abstract class MeasureRun {
+ /**
+ * Called before each run. not included in time measurement.
+ */
+ public void prepare(int i) throws Exception {
+ // default empty implementation
+ };
+
+ abstract public void run(int i) throws Exception;
+}
diff --git a/common/util/src/android/cts/util/MeasureTime.java b/common/util/src/android/cts/util/MeasureTime.java
new file mode 100644
index 0000000..073ed08
--- /dev/null
+++ b/common/util/src/android/cts/util/MeasureTime.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.util;
+
+/**
+ * Provides a mechanism to measure the time taken to run a piece of code.
+ *
+ * The code will be run multiple times and the time taken by each run will returned.
+ */
+public class MeasureTime {
+ /**
+ * measure time taken for each run for given count
+ * @param count
+ * @param run
+ * @return array of time taken in each run in msec.
+ * @throws Exception
+ */
+ public static double[] measure(int count, MeasureRun run) throws Exception {
+ double[] result = new double[count];
+
+ for (int i = 0; i < count; i++) {
+ run.prepare(i);
+ long start = System.currentTimeMillis();
+ run.run(i);
+ long end = System.currentTimeMillis();
+ result[i] = end - start;
+ }
+ return result;
+ }
+}
diff --git a/common/util/src/android/cts/util/ReportLog.java b/common/util/src/android/cts/util/ReportLog.java
new file mode 100644
index 0000000..fe2a15d
--- /dev/null
+++ b/common/util/src/android/cts/util/ReportLog.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.util;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utility class to add results to the report.
+ */
+public abstract class ReportLog implements Serializable {
+
+ private Result mSummary;
+ private final List<Result> mDetails = new ArrayList<Result>();
+
+ private class Result implements Serializable {
+ private static final int DEPTH = 2;// 0:constructor, 1:addValues/setSummary, 2:caller
+ private String mLocation;
+ private String mMessage;
+ private double[] mValues;
+ private ResultType mType;
+ private ResultUnit mUnit;
+
+ private Result(String message, double[] values, ResultType type, ResultUnit unit) {
+ final StackTraceElement e = Thread.currentThread().getStackTrace()[DEPTH];
+ mLocation = String.format("%s#%s:%d",
+ e.getClassName(), e.getMethodName(), e.getLineNumber());
+ mMessage = message;
+ mValues = values;
+ mType = type;
+ mUnit = unit;
+ }
+
+ }
+
+ /**
+ * Adds an array of values to the report.
+ */
+ public void addValues(String message, double[] values, ResultType type, ResultUnit unit) {
+ mDetails.add(new Result(message, values, type, unit));
+ }
+
+ /**
+ * Adds a value to the report.
+ */
+ public void addValue(String message, double value, ResultType type, ResultUnit unit) {
+ mDetails.add(new Result(message, new double[] {value}, type, unit));
+ }
+
+ /**
+ * Sets the summary of the report.
+ */
+ public void setSummary(String message, double value, ResultType type, ResultUnit unit) {
+ mSummary = new Result(message, new double[] {value}, type, unit);
+ }
+
+}
diff --git a/common/util/src/android/cts/util/ResultType.java b/common/util/src/android/cts/util/ResultType.java
new file mode 100644
index 0000000..cb6be4d
--- /dev/null
+++ b/common/util/src/android/cts/util/ResultType.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.util;
+
+/**
+ * Enum for distinguishing results.
+ */
+public enum ResultType {
+ /** Lower score is better. */
+ LOWER_BETTER,
+ /** Higher score is better. */
+ HIGHER_BETTER,
+ /** This value is not directly correlated with score. */
+ 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/common/util/src/android/cts/util/ResultUnit.java b/common/util/src/android/cts/util/ResultUnit.java
new file mode 100644
index 0000000..964309c
--- /dev/null
+++ b/common/util/src/android/cts/util/ResultUnit.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.util;
+
+/**
+ * Enum for representing the unit of 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/common/util/src/android/cts/util/Stat.java b/common/util/src/android/cts/util/Stat.java
new file mode 100644
index 0000000..b4596c3
--- /dev/null
+++ b/common/util/src/android/cts/util/Stat.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.util;
+
+import java.util.Arrays;
+
+/**
+ * Utilities for doing statistics
+ */
+public class Stat {
+
+ /**
+ * Collection of statistical propertirs like average, max, min, and stddev
+ */
+ public static class StatResult {
+ public double mAverage;
+ public double mMin;
+ public double mMax;
+ public double mStddev;
+ public int mDataCount;
+ public StatResult(double average, double min, double max, double stddev, int dataCount) {
+ mAverage = average;
+ mMin = min;
+ mMax = max;
+ mStddev = stddev;
+ mDataCount = dataCount;
+ }
+ }
+
+ /**
+ * 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];
+ double max = data[0];
+ double eX2 = data[0] * data[0]; // will become E[X^2]
+ for (int i = 1; i < data.length; i++) {
+ average += data[i];
+ eX2 += data[i] * data[i];
+ if (data[i] > max) {
+ max = data[i];
+ }
+ if (data[i] < min) {
+ min = data[i];
+ }
+ }
+ average /= data.length;
+ eX2 /= data.length;
+ // stddev = sqrt(E[X^2] - (E[X])^2)
+ double stddev = Math.sqrt(eX2 - average * average);
+ return new StatResult(average, min, max, stddev, data.length);
+ }
+
+ /**
+ * Calculate statistics properties likes average, min, max, and stddev for the given array
+ * while rejecting outlier +/- median * rejectionThreshold.
+ * rejectionThreshold should be bigger than 0.0 and be lowerthan 1.0
+ */
+ public static StatResult getStatWithOutlierRejection(double[] data, double rejectionThreshold) {
+ double[] dataCopied = Arrays.copyOf(data, data.length);
+ Arrays.sort(dataCopied);
+ int medianIndex = dataCopied.length / 2;
+ double median;
+ if (dataCopied.length % 2 == 1) {
+ median = dataCopied[medianIndex];
+ } else {
+ median = (dataCopied[medianIndex - 1] + dataCopied[medianIndex]) / 2.0;
+ }
+ double thresholdMin = median * (1.0 - rejectionThreshold);
+ double thresholdMax = median * (1.0 + rejectionThreshold);
+
+ double average = 0.0;
+ double min = median;
+ double max = median;
+ double eX2 = 0.0; // will become E[X^2]
+ int validDataCounter = 0;
+ for (int i = 0; i < data.length; i++) {
+ if ((data[i] > thresholdMin) && (data[i] < thresholdMax)) {
+ validDataCounter++;
+ average += data[i];
+ eX2 += data[i] * data[i];
+ if (data[i] > max) {
+ max = data[i];
+ }
+ if (data[i] < min) {
+ min = data[i];
+ }
+ }
+ //TODO report rejected data
+ }
+ double stddev;
+ if (validDataCounter > 0) {
+ average /= validDataCounter;
+ eX2 /= validDataCounter;
+ // stddev = sqrt(E[X^2] - (E[X])^2)
+ stddev = Math.sqrt(eX2 - average * average);
+ } else { // both median is showing too much diff
+ average = median;
+ stddev = 0; // don't care
+ }
+
+ return new StatResult(average, min, max, stddev, validDataCounter);
+ }
+
+ /**
+ * 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++) {
+ if (data[i] < min) {
+ min = data[i];
+ }
+ }
+ 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++) {
+ if (data[i] > max) {
+ max = data[i];
+ }
+ }
+ return max;
+ }
+
+ /**
+ * Calculate rate per sec for given change happened during given timeInMSec.
+ * timeInSec with 0 value will be changed to small value to prevent divide by zero.
+ * @param change total change of quality for the given duration timeInMSec.
+ * @param timeInMSec
+ * @return
+ */
+ public static double calcRatePerSec(double change, double timeInMSec) {
+ if (timeInMSec == 0) {
+ return change * 1000.0 / 0.001; // do not allow zero
+ } else {
+ return change * 1000.0 / timeInMSec;
+ }
+ }
+
+ /**
+ * array version of calcRatePerSecArray
+ */
+ public static double[] calcRatePerSecArray(double change, double[] timeInMSec) {
+ double[] result = new double[timeInMSec.length];
+ change *= 1000.0;
+ for (int i = 0; i < timeInMSec.length; i++) {
+ if (timeInMSec[i] == 0) {
+ result[i] = change / 0.001;
+ } else {
+ result[i] = change / timeInMSec[i];
+ }
+ }
+ return result;
+ }
+
+}
diff --git a/suite/audio_quality/executable/Android.mk b/suite/audio_quality/executable/Android.mk
index dfb67d0..a37b987 100644
--- a/suite/audio_quality/executable/Android.mk
+++ b/suite/audio_quality/executable/Android.mk
@@ -29,7 +29,7 @@
LOCAL_STATIC_LIBRARIES += libutils liblog libcutils libtinyalsa libtinyxml
LOCAL_WHOLE_STATIC_LIBRARIES := libcts_audio_quality
LOCAL_CFLAGS:= -g -fno-exceptions
-LOCAL_LDFLAGS:= -g -lrt -ldl -lstdc++ -lm -fno-exceptions
+LOCAL_LDFLAGS:= -g -lrt -ldl -lstdc++ -lm -fno-exceptions -lpthread
LOCAL_MODULE:= cts_audio_quality
include $(BUILD_HOST_EXECUTABLE)
diff --git a/suite/audio_quality/test/Android.mk b/suite/audio_quality/test/Android.mk
index 9e32557..4582fe7 100644
--- a/suite/audio_quality/test/Android.mk
+++ b/suite/audio_quality/test/Android.mk
@@ -34,7 +34,7 @@
# functions and linker error happens
LOCAL_WHOLE_STATIC_LIBRARIES := libcts_audio_quality
LOCAL_CFLAGS:= -g -fno-exceptions
-LOCAL_LDFLAGS:= -g -lrt -ldl -lstdc++ -lm -fno-exceptions
+LOCAL_LDFLAGS:= -g -lrt -ldl -lstdc++ -lm -fno-exceptions -lpthread
LOCAL_MODULE:= cts_audio_quality_test
include $(BUILD_HOST_EXECUTABLE)
diff --git a/suite/cts/deviceTests/opengl/test/Android.mk b/suite/cts/deviceTests/opengl/test/Android.mk
index b571dbf..a4abe4f 100644
--- a/suite/cts/deviceTests/opengl/test/Android.mk
+++ b/suite/cts/deviceTests/opengl/test/Android.mk
@@ -27,7 +27,7 @@
#$(info $(LOCAL_SRC_FILES))
LOCAL_C_INCLUDES += external/gtest/include $(LOCAL_PATH)/../jni/graphics/
LOCAL_STATIC_LIBRARIES := libutils libcutils libgtest_host libgtest_main_host liblog
-LOCAL_LDFLAGS:= -g -lrt -ldl -lstdc++ -lm -fno-exceptions
+LOCAL_LDFLAGS:= -g -lrt -ldl -lstdc++ -lm -fno-exceptions -lpthread
LOCAL_MODULE:= cts_device_opengl_test
include $(BUILD_HOST_EXECUTABLE)
diff --git a/tests/tests/security/assets/selinux_policy.xml b/tests/assets/selinux_policy.xml
similarity index 100%
rename from tests/tests/security/assets/selinux_policy.xml
rename to tests/assets/selinux_policy.xml
diff --git a/tests/core/runner/src/com/android/cts/runner/CtsTestRunListener.java b/tests/core/runner/src/com/android/cts/runner/CtsTestRunListener.java
index b437e59..a142b29 100644
--- a/tests/core/runner/src/com/android/cts/runner/CtsTestRunListener.java
+++ b/tests/core/runner/src/com/android/cts/runner/CtsTestRunListener.java
@@ -43,8 +43,7 @@
/**
* A {@link RunListener} for CTS. Sets the system properties necessary for many
* core tests to run. This is needed because there are some core tests that need
- * writing access to the file system. We also need to set the harness Thread's
- * context ClassLoader. Otherwise some classes and resources will not be found.
+ * writing access to the file system.
* Finally, we add a means to free memory allocated by a TestCase after its
* execution.
*/
@@ -95,9 +94,6 @@
printMemory(description.getTestClass());
}
- Thread.currentThread().setContextClassLoader(
- description.getTestClass().getClass().getClassLoader());
-
mEnvironment.reset();
}
diff --git a/tests/plans/CTS-stable.xml b/tests/plans/CTS-stable.xml
index d9d06d4..c404310 100644
--- a/tests/plans/CTS-stable.xml
+++ b/tests/plans/CTS-stable.xml
@@ -15,6 +15,20 @@
<Entry uri="android.core.tests.libcore.package.com"/>
<Entry uri="android.core.tests.libcore.package.conscrypt"/>
<Entry uri="android.core.tests.libcore.package.dalvik"/>
+ <Entry uri="android.core.tests.libcore.package.harmony_annotation"/>
+ <Entry uri="android.core.tests.libcore.package.harmony_beans"/>
+ <Entry uri="android.core.tests.libcore.package.harmony_java_io"/>
+ <Entry uri="android.core.tests.libcore.package.harmony_java_lang"/>
+ <Entry uri="android.core.tests.libcore.package.harmony_java_math"/>
+ <Entry uri="android.core.tests.libcore.package.harmony_java_net"/>
+ <Entry uri="android.core.tests.libcore.package.harmony_java_nio"/>
+ <Entry uri="android.core.tests.libcore.package.harmony_java_text"/>
+ <Entry uri="android.core.tests.libcore.package.harmony_java_util"/>
+ <Entry uri="android.core.tests.libcore.package.harmony_javax_security"/>
+ <Entry uri="android.core.tests.libcore.package.harmony_logging"/>
+ <Entry uri="android.core.tests.libcore.package.harmony_prefs"/>
+ <Entry uri="android.core.tests.libcore.package.harmony_sql"/>
+ <Entry uri="android.core.tests.libcore.package.jsr166"/>
<Entry uri="android.core.tests.libcore.package.libcore"/>
<Entry uri="android.core.tests.libcore.package.org"/>
<Entry uri="android.core.tests.libcore.package.sun"/>
@@ -27,11 +41,11 @@
<Entry uri="android.dreams"/>
<Entry uri="android.drm"/>
<Entry uri="android.effect"/>
- <Entry uri="android.example"/>
<Entry uri="android.gesture"/>
<Entry uri="android.graphics"/>
<Entry uri="android.graphics2"/>
<Entry uri="android.hardware" exclude="android.hardware.camera2.cts.CameraDeviceTest#testCameraDeviceRepeatingRequest;android.hardware.camera2.cts.ImageReaderTest#testImageReaderFromCameraJpeg;android.hardware.cts.CameraGLTest#testCameraToSurfaceTextureMetadata;android.hardware.cts.CameraTest#testImmediateZoom;android.hardware.cts.CameraTest#testPreviewCallback;android.hardware.cts.CameraTest#testSmoothZoom;android.hardware.cts.CameraTest#testVideoSnapshot;android.hardware.cts.CameraGLTest#testSetPreviewTextureBothCallbacks;android.hardware.cts.CameraGLTest#testSetPreviewTexturePreviewCallback" />
+ <Entry uri="android.host.holo"/>
<Entry uri="android.holo"/>
<Entry uri="android.jni"/>
<Entry uri="android.keystore"/>
@@ -50,6 +64,7 @@
<Entry uri="android.permission2"/>
<Entry uri="android.preference"/>
<Entry uri="android.preference2"/>
+ <Entry uri="android.print"/>
<Entry uri="android.provider"/>
<Entry uri="android.renderscript"/>
<Entry uri="android.rscpp"/>
@@ -59,7 +74,6 @@
<Entry uri="android.speech"/>
<Entry uri="android.telephony"/>
<Entry uri="android.tests.appsecurity"/>
- <Entry uri="android.tests.location"/>
<Entry uri="android.tests.sigtest"/>
<Entry uri="android.text"/>
<Entry uri="android.textureview"/>
@@ -70,12 +84,10 @@
<Entry uri="android.view"/>
<Entry uri="android.webkit" exclude="android.webkit.cts.WebViewClientTest#testDoUpdateVisitedHistory;android.webkit.cts.WebViewClientTest#testLoadPage;android.webkit.cts.WebViewClientTest#testOnFormResubmission;android.webkit.cts.WebViewClientTest#testOnReceivedError;android.webkit.cts.WebViewClientTest#testOnReceivedHttpAuthRequest;android.webkit.cts.WebViewClientTest#testOnScaleChanged;android.webkit.cts.WebViewClientTest#testOnUnhandledKeyEvent;android.webkit.cts.WebViewTest#testSetInitialScale" />
<Entry uri="android.widget"/>
- <Entry uri="com.android.cts.bootup"/>
<Entry uri="com.android.cts.browserbench"/>
<Entry uri="com.android.cts.dram"/>
<Entry uri="com.android.cts.filesystemperf"/>
- <Entry uri="com.android.cts.jank.opengl"/>
- <Entry uri="com.android.cts.jank.ui"/>
+ <Entry uri="com.android.cts.jank"/>
<Entry uri="com.android.cts.opengl"/>
<Entry uri="com.android.cts.simplecpu"/>
<Entry uri="com.android.cts.ui"/>
diff --git a/tests/res/drawable/bitmapdrawable_theme.xml b/tests/res/drawable/bitmapdrawable_theme.xml
new file mode 100644
index 0000000..6df36b3
--- /dev/null
+++ b/tests/res/drawable/bitmapdrawable_theme.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
+ android:antialias="?attr/themeBoolean"
+ android:autoMirrored="?attr/themeBoolean"
+ android:dither="?attr/themeBoolean"
+ android:filter="?attr/themeBoolean"
+ android:gravity="?attr/themeGravity"
+ android:mipMap="?attr/themeBoolean"
+ android:src="?attr/themeBitmap"
+ android:tileMode="?attr/themeTileMode" />
diff --git a/tests/res/drawable/colordrawable_theme.xml b/tests/res/drawable/colordrawable_theme.xml
new file mode 100644
index 0000000..00c6fe7
--- /dev/null
+++ b/tests/res/drawable/colordrawable_theme.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<color xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?attr/themeColor" />
diff --git a/tests/res/drawable/gradientdrawable_theme.xml b/tests/res/drawable/gradientdrawable_theme.xml
new file mode 100644
index 0000000..68cec62
--- /dev/null
+++ b/tests/res/drawable/gradientdrawable_theme.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <corners
+ android:bottomLeftRadius="?attr/themeDimension"
+ android:bottomRightRadius="?attr/themeDimension"
+ android:topLeftRadius="?attr/themeDimension"
+ android:topRightRadius="?attr/themeDimension" />
+
+ <gradient
+ android:angle="?attr/themeAngle"
+ android:centerColor="?attr/themeColor"
+ android:centerX="?attr/themeFloat"
+ android:centerY="?attr/themeFloat"
+ android:endColor="?attr/themeColor"
+ android:gradientRadius="?attr/themeFloat"
+ android:startColor="?attr/themeColor"
+ android:useLevel="?attr/themeBoolean" />
+
+ <padding
+ android:bottom="?attr/themeDimension"
+ android:left="?attr/themeDimension"
+ android:right="?attr/themeDimension"
+ android:top="?attr/themeDimension" />
+
+ <size
+ android:height="?attr/themeDimension"
+ android:width="?attr/themeDimension" />
+
+ <solid android:color="?attr/themeColor" />
+
+ <stroke android:color="?attr/themeColor" />
+
+</shape>
diff --git a/tests/res/drawable/layerdrawable_theme.xml b/tests/res/drawable/layerdrawable_theme.xml
new file mode 100644
index 0000000..2a678ff
--- /dev/null
+++ b/tests/res/drawable/layerdrawable_theme.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
+ android:autoMirrored="?attr/themeBoolean" >
+
+ <item android:drawable="@drawable/bitmapdrawable_theme"/>
+ <item>
+ <nine-patch
+ android:autoMirrored="?attr/themeBoolean"
+ android:dither="?attr/themeBoolean"
+ android:src="?attr/themeNinePatch" />
+ </item>
+
+</layer-list>
\ No newline at end of file
diff --git a/tests/res/drawable/ninepatchdrawable_theme.xml b/tests/res/drawable/ninepatchdrawable_theme.xml
new file mode 100644
index 0000000..bb031a5
--- /dev/null
+++ b/tests/res/drawable/ninepatchdrawable_theme.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<nine-patch xmlns:android="http://schemas.android.com/apk/res/android"
+ android:autoMirrored="?attr/themeBoolean"
+ android:dither="?attr/themeBoolean"
+ android:src="?attr/themeNinePatch" />
diff --git a/tests/res/drawable/touchfeedbackdrawable_theme.xml b/tests/res/drawable/touchfeedbackdrawable_theme.xml
new file mode 100644
index 0000000..a2b73cd
--- /dev/null
+++ b/tests/res/drawable/touchfeedbackdrawable_theme.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<touch-feedback xmlns:android="http://schemas.android.com/apk/res/android"
+ android:tint="?attr/themeColor" />
diff --git a/tests/res/raw/rs_interpred_param.bin b/tests/res/raw/rs_interpred_param.bin
new file mode 100644
index 0000000..f228a96
--- /dev/null
+++ b/tests/res/raw/rs_interpred_param.bin
@@ -0,0 +1 @@
+0,3,358560,2240,358560,2240,0,16,0,16,64,64,0,3,3225680,1120,3225680,1120,0,16,0,16,32,32,0,3,4009680,1120,4009680,1120,0,16,0,16,32,32,0,3,358624,2240,358624,2240,0,16,0,16,64,64,0,3,3225712,1120,3225712,1120,0,16,0,16,32,32,0,3,4009712,1120,4009712,1120,0,16,0,16,32,32,0,3,358688,2240,358688,2240,0,16,0,16,64,64,0,3,3225744,1120,3225744,1120,0,16,0,16,32,32,0,3,4009744,1120,4009744,1120,0,16,0,16,32,32,0,3,358752,2240,358752,2240,0,16,0,16,64,64,0,3,3225776,1120,3225776,1120,0,16,0,16,32,32,0,3,4009776,1120,4009776,1120,0,16,0,16,32,32,0,3,358816,2240,358816,2240,0,16,0,16,64,64,0,3,3225808,1120,3225808,1120,0,16,0,16,32,32,0,3,4009808,1120,4009808,1120,0,16,0,16,32,32,0,3,358880,2240,358880,2240,0,16,0,16,64,64,0,3,3225840,1120,3225840,1120,0,16,0,16,32,32,0,3,4009840,1120,4009840,1120,0,16,0,16,32,32,0,3,358944,2240,358944,2240,0,16,0,16,64,64,0,3,3225872,1120,3225872,1120,0,16,0,16,32,32,0,3,4009872,1120,4009872,1120,0,16,0,16,32,32,0,3,359008,2240,359008,2240,0,16,0,16,64,64,0,3,3225904,1120,3225904,1120,0,16,0,16,32,32,0,3,4009904,1120,4009904,1120,0,16,0,16,32,32,0,3,359072,2240,359072,2240,0,16,0,16,64,64,0,3,3225936,1120,3225936,1120,0,16,0,16,32,32,0,3,4009936,1120,4009936,1120,0,16,0,16,32,32,0,3,359136,2240,359136,2240,0,16,0,16,64,64,0,3,3225968,1120,3225968,1120,0,16,0,16,32,32,0,3,4009968,1120,4009968,1120,0,16,0,16,32,32,0,3,359200,2240,359200,2240,0,16,0,16,64,64,0,3,3226000,1120,3226000,1120,0,16,0,16,32,32,0,3,4010000,1120,4010000,1120,0,16,0,16,32,32,0,3,359264,2240,359264,2240,0,16,0,16,64,64,0,3,3226032,1120,3226032,1120,0,16,0,16,32,32,0,3,4010032,1120,4010032,1120,0,16,0,16,32,32,0,3,359328,2240,359328,2240,0,16,0,16,64,64,0,3,3226064,1120,3226064,1120,0,16,0,16,32,32,0,3,4010064,1120,4010064,1120,0,16,0,16,32,32,0,3,359392,2240,359392,2240,0,16,0,16,64,64,0,3,3226096,1120,3226096,1120,0,16,0,16,32,32,0,3,4010096,1120,4010096,1120,0,16,0,16,32,32,0,3,359456,2240,359456,2240,0,16,0,16,64,64,0,3,3226128,1120,3226128,1120,0,16,0,16,32,32,0,3,4010128,1120,4010128,1120,0,16,0,16,32,32,0,3,359520,2240,359520,2240,0,16,0,16,64,64,0,3,3226160,1120,3226160,1120,0,16,0,16,32,32,0,3,4010160,1120,4010160,1120,0,16,0,16,32,32,0,3,359584,2240,359584,2240,0,16,0,16,64,64,0,3,3226192,1120,3226192,1120,0,16,0,16,32,32,0,3,4010192,1120,4010192,1120,0,16,0,16,32,32,0,3,359648,2240,359648,2240,0,16,0,16,64,64,0,3,3226224,1120,3226224,1120,0,16,0,16,32,32,0,3,4010224,1120,4010224,1120,0,16,0,16,32,32,0,3,359712,2240,359712,2240,0,16,0,16,64,64,0,3,3226256,1120,3226256,1120,0,16,0,16,32,32,0,3,4010256,1120,4010256,1120,0,16,0,16,32,32,0,3,359776,2240,359776,2240,0,16,0,16,64,64,0,3,3226288,1120,3226288,1120,0,16,0,16,32,32,0,3,4010288,1120,4010288,1120,0,16,0,16,32,32,0,3,359840,2240,359840,2240,0,16,0,16,64,64,0,3,3226320,1120,3226320,1120,0,16,0,16,32,32,0,3,4010320,1120,4010320,1120,0,16,0,16,32,32,0,3,359904,2240,359904,2240,0,16,0,16,64,64,0,3,3226352,1120,3226352,1120,0,16,0,16,32,32,0,3,4010352,1120,4010352,1120,0,16,0,16,32,32,0,3,359968,2240,359968,2240,0,16,0,16,64,64,0,3,3226384,1120,3226384,1120,0,16,0,16,32,32,0,3,4010384,1120,4010384,1120,0,16,0,16,32,32,0,3,360032,2240,360032,2240,0,16,0,16,64,64,0,3,3226416,1120,3226416,1120,0,16,0,16,32,32,0,3,4010416,1120,4010416,1120,0,16,0,16,32,32,0,3,360096,2240,360096,2240,0,16,0,16,64,64,0,3,3226448,1120,3226448,1120,0,16,0,16,32,32,0,3,4010448,1120,4010448,1120,0,16,0,16,32,32,0,3,360160,2240,360160,2240,0,16,0,16,64,64,0,3,3226480,1120,3226480,1120,0,16,0,16,32,32,0,3,4010480,1120,4010480,1120,0,16,0,16,32,32,0,3,360224,2240,360224,2240,0,16,0,16,64,64,0,3,3226512,1120,3226512,1120,0,16,0,16,32,32,0,3,4010512,1120,4010512,1120,0,16,0,16,32,32,0,3,360288,2240,360288,2240,0,16,0,16,64,64,0,3,3226544,1120,3226544,1120,0,16,0,16,32,32,0,3,4010544,1120,4010544,1120,0,16,0,16,32,32,0,3,360352,2240,360352,2240,0,16,0,16,64,64,0,3,3226576,1120,3226576,1120,0,16,0,16,32,32,0,3,4010576,1120,4010576,1120,0,16,0,16,32,32,0,3,360416,2240,360416,2240,0,16,0,16,64,64,0,3,3226608,1120,3226608,1120,0,16,0,16,32,32,0,3,4010608,1120,4010608,1120,0,16,0,16,32,32,0,3,501920,2240,501920,2240,0,16,0,16,64,64,0,3,3261520,1120,3261520,1120,0,16,0,16,32,32,0,3,4045520,1120,4045520,1120,0,16,0,16,32,32,0,3,501984,2240,501984,2240,0,16,0,16,64,64,0,3,3261552,1120,3261552,1120,0,16,0,16,32,32,0,3,4045552,1120,4045552,1120,0,16,0,16,32,32,0,3,502048,2240,502048,2240,0,16,0,16,64,64,0,3,3261584,1120,3261584,1120,0,16,0,16,32,32,0,3,4045584,1120,4045584,1120,0,16,0,16,32,32,0,3,502112,2240,502112,2240,0,16,0,16,64,64,0,3,3261616,1120,3261616,1120,0,16,0,16,32,32,0,3,4045616,1120,4045616,1120,0,16,0,16,32,32,0,3,502176,2240,502176,2240,0,16,0,16,64,64,0,3,3261648,1120,3261648,1120,0,16,0,16,32,32,0,3,4045648,1120,4045648,1120,0,16,0,16,32,32,0,3,502240,2240,502240,2240,0,16,0,16,64,64,0,3,3261680,1120,3261680,1120,0,16,0,16,32,32,0,3,4045680,1120,4045680,1120,0,16,0,16,32,32,0,3,502304,2240,502304,2240,0,16,0,16,64,64,0,3,3261712,1120,3261712,1120,0,16,0,16,32,32,0,3,4045712,1120,4045712,1120,0,16,0,16,32,32,0,3,502368,2240,502368,2240,0,16,0,16,64,64,0,3,3261744,1120,3261744,1120,0,16,0,16,32,32,0,3,4045744,1120,4045744,1120,0,16,0,16,32,32,0,3,502432,2240,502432,2240,0,16,0,16,64,64,0,3,3261776,1120,3261776,1120,0,16,0,16,32,32,0,3,4045776,1120,4045776,1120,0,16,0,16,32,32,0,3,502496,2240,502496,2240,0,16,0,16,64,64,0,3,3261808,1120,3261808,1120,0,16,0,16,32,32,0,3,4045808,1120,4045808,1120,0,16,0,16,32,32,0,3,502560,2240,502560,2240,0,16,0,16,64,64,0,3,3261840,1120,3261840,1120,0,16,0,16,32,32,0,3,4045840,1120,4045840,1120,0,16,0,16,32,32,0,3,502624,2240,502624,2240,0,16,0,16,64,64,0,3,3261872,1120,3261872,1120,0,16,0,16,32,32,0,3,4045872,1120,4045872,1120,0,16,0,16,32,32,0,3,502688,2240,502688,2240,0,16,0,16,64,64,0,3,3261904,1120,3261904,1120,0,16,0,16,32,32,0,3,4045904,1120,4045904,1120,0,16,0,16,32,32,0,3,502752,2240,502752,2240,0,16,0,16,64,64,0,3,3261936,1120,3261936,1120,0,16,0,16,32,32,0,3,4045936,1120,4045936,1120,0,16,0,16,32,32,0,3,502816,2240,502816,2240,0,16,0,16,64,64,0,3,3261968,1120,3261968,1120,0,16,0,16,32,32,0,3,4045968,1120,4045968,1120,0,16,0,16,32,32,0,3,502880,2240,502880,2240,0,16,0,16,64,64,0,3,3262000,1120,3262000,1120,0,16,0,16,32,32,0,3,4046000,1120,4046000,1120,0,16,0,16,32,32,0,3,502944,2240,502944,2240,0,16,0,16,64,64,0,3,3262032,1120,3262032,1120,0,16,0,16,32,32,0,3,4046032,1120,4046032,1120,0,16,0,16,32,32,0,3,503008,2240,503008,2240,0,16,0,16,64,64,0,3,3262064,1120,3262064,1120,0,16,0,16,32,32,0,3,4046064,1120,4046064,1120,0,16,0,16,32,32,0,3,503072,2240,503072,2240,0,16,0,16,64,64,0,3,3262096,1120,3262096,1120,0,16,0,16,32,32,0,3,4046096,1120,4046096,1120,0,16,0,16,32,32,0,3,503136,2240,503136,2240,0,16,0,16,64,64,0,3,3262128,1120,3262128,1120,0,16,0,16,32,32,0,3,4046128,1120,4046128,1120,0,16,0,16,32,32,0,3,503200,2240,503200,2240,0,16,0,16,64,64,0,3,3262160,1120,3262160,1120,0,16,0,16,32,32,0,3,4046160,1120,4046160,1120,0,16,0,16,32,32,0,3,503264,2240,503264,2240,0,16,0,16,64,64,0,3,3262192,1120,3262192,1120,0,16,0,16,32,32,0,3,4046192,1120,4046192,1120,0,16,0,16,32,32,0,3,503328,2240,503328,2240,0,16,0,16,64,64,0,3,3262224,1120,3262224,1120,0,16,0,16,32,32,0,3,4046224,1120,4046224,1120,0,16,0,16,32,32,0,3,503392,2240,503392,2240,0,16,0,16,64,64,0,3,3262256,1120,3262256,1120,0,16,0,16,32,32,0,3,4046256,1120,4046256,1120,0,16,0,16,32,32,0,3,503456,2240,503456,2240,0,16,0,16,64,64,0,3,3262288,1120,3262288,1120,0,16,0,16,32,32,0,3,4046288,1120,4046288,1120,0,16,0,16,32,32,0,3,503520,2240,503520,2240,0,16,0,16,64,64,0,3,3262320,1120,3262320,1120,0,16,0,16,32,32,0,3,4046320,1120,4046320,1120,0,16,0,16,32,32,0,3,503584,2240,503584,2240,0,16,0,16,64,64,0,3,3262352,1120,3262352,1120,0,16,0,16,32,32,0,3,4046352,1120,4046352,1120,0,16,0,16,32,32,0,3,503648,2240,503648,2240,0,16,0,16,64,64,0,3,3262384,1120,3262384,1120,0,16,0,16,32,32,0,3,4046384,1120,4046384,1120,0,16,0,16,32,32,0,3,503712,2240,503712,2240,0,16,0,16,64,64,0,3,3262416,1120,3262416,1120,0,16,0,16,32,32,0,3,4046416,1120,4046416,1120,0,16,0,16,32,32,0,3,503776,2240,503776,2240,0,16,0,16,64,64,0,3,3262448,1120,3262448,1120,0,16,0,16,32,32,0,3,4046448,1120,4046448,1120,0,16,0,16,32,32,0,3,645280,2240,645280,2240,0,16,0,16,64,64,0,3,3297360,1120,3297360,1120,0,16,0,16,32,32,0,3,4081360,1120,4081360,1120,0,16,0,16,32,32,0,3,645344,2240,645344,2240,0,16,0,16,64,64,0,3,3297392,1120,3297392,1120,0,16,0,16,32,32,0,3,4081392,1120,4081392,1120,0,16,0,16,32,32,0,3,645408,2240,645408,2240,0,16,0,16,64,64,0,3,3297424,1120,3297424,1120,0,16,0,16,32,32,0,3,4081424,1120,4081424,1120,0,16,0,16,32,32,0,3,645472,2240,645472,2240,0,16,0,16,64,64,0,3,3297456,1120,3297456,1120,0,16,0,16,32,32,0,3,4081456,1120,4081456,1120,0,16,0,16,32,32,0,3,645536,2240,645536,2240,0,16,0,16,64,64,0,3,3297488,1120,3297488,1120,0,16,0,16,32,32,0,3,4081488,1120,4081488,1120,0,16,0,16,32,32,0,3,645600,2240,645600,2240,0,16,0,16,64,64,0,3,3297520,1120,3297520,1120,0,16,0,16,32,32,0,3,4081520,1120,4081520,1120,0,16,0,16,32,32,0,3,645664,2240,645664,2240,0,16,0,16,64,64,0,3,3297552,1120,3297552,1120,0,16,0,16,32,32,0,3,4081552,1120,4081552,1120,0,16,0,16,32,32,0,3,645728,2240,645728,2240,0,16,0,16,64,64,0,3,3297584,1120,3297584,1120,0,16,0,16,32,32,0,3,4081584,1120,4081584,1120,0,16,0,16,32,32,0,3,645792,2240,645792,2240,0,16,0,16,64,64,0,3,3297616,1120,3297616,1120,0,16,0,16,32,32,0,3,4081616,1120,4081616,1120,0,16,0,16,32,32,0,3,645856,2240,645856,2240,0,16,0,16,64,64,0,3,3297648,1120,3297648,1120,0,16,0,16,32,32,0,3,4081648,1120,4081648,1120,0,16,0,16,32,32,0,3,645920,2240,645920,2240,0,16,0,16,64,64,0,3,3297680,1120,3297680,1120,0,16,0,16,32,32,0,3,4081680,1120,4081680,1120,0,16,0,16,32,32,0,3,645984,2240,645984,2240,0,16,0,16,64,64,0,3,3297712,1120,3297712,1120,0,16,0,16,32,32,0,3,4081712,1120,4081712,1120,0,16,0,16,32,32,0,3,646048,2240,646048,2240,0,16,0,16,64,64,0,3,3297744,1120,3297744,1120,0,16,0,16,32,32,0,3,4081744,1120,4081744,1120,0,16,0,16,32,32,0,3,646112,2240,646112,2240,0,16,0,16,64,64,0,3,3297776,1120,3297776,1120,0,16,0,16,32,32,0,3,4081776,1120,4081776,1120,0,16,0,16,32,32,0,3,646176,2240,646176,2240,0,16,0,16,32,32,0,3,3297808,1120,3297808,1120,0,16,0,16,16,16,0,3,4081808,1120,4081808,1120,0,16,0,16,16,16,0,3,646208,2240,646208,2240,0,16,0,16,32,32,0,3,3297824,1120,3297824,1120,0,16,0,16,16,16,0,3,4081824,1120,4081824,1120,0,16,0,16,16,16,0,3,717856,2240,717856,2240,0,16,0,16,32,32,0,3,3315728,1120,3315728,1120,0,16,0,16,16,16,0,3,4099728,1120,4099728,1120,0,16,0,16,16,16,4,3,717884,2240,717888,2240,64,16,0,16,16,16,4,3,3315742,1120,3315744,1120,32,16,0,16,8,8,4,3,4099742,1120,4099744,1120,32,16,0,16,8,8,0,3,717904,2240,717904,2240,0,16,0,16,16,16,0,3,3315752,1120,3315752,1120,0,16,0,16,8,8,0,3,4099752,1120,4099752,1120,0,16,0,16,8,8,0,3,753728,2240,753728,2240,0,16,0,16,8,8,0,3,3324704,1120,3324704,1120,0,16,0,16,4,4,0,3,4108704,1120,4108704,1120,0,16,0,16,4,4,0,3,753736,2240,753736,2240,0,16,0,16,8,8,0,3,3324708,1120,3324708,1120,0,16,0,16,4,4,0,3,4108708,1120,4108708,1120,0,16,0,16,4,4,2,3,769408,2240,771648,2240,0,16,80,16,8,8,2,3,3328064,1120,3329184,1120,0,16,104,16,4,4,2,3,4112064,1120,4113184,1120,0,16,104,16,4,4,2,3,769416,2240,771656,2240,128,16,208,16,4,4,6,3,769420,2240,771660,2240,192,16,144,16,4,4,2,3,778376,2240,780616,2240,128,16,208,16,4,4,6,3,778380,2240,780620,2240,192,16,144,16,4,4,6,3,3328068,1120,3329188,1120,144,16,216,16,4,4,6,3,4112068,1120,4113188,1120,144,16,216,16,4,4,0,3,753744,2240,753744,2240,0,16,0,16,8,8,0,3,3324712,1120,3324712,1120,0,16,0,16,4,4,0,3,4108712,1120,4108712,1120,0,16,0,16,4,4,0,3,753752,2240,753752,2240,0,16,0,16,8,8,0,3,3324716,1120,3324716,1120,0,16,0,16,4,4,0,3,4108716,1120,4108716,1120,0,16,0,16,4,4,4,3,771664,2240,771664,2240,64,16,0,16,8,8,4,3,3329192,1120,3329192,1120,32,16,0,16,4,4,4,3,4113192,1120,4113192,1120,32,16,0,16,4,4,2,3,769432,2240,771672,2240,0,16,80,16,4,4,2,3,769436,2240,771676,2240,0,16,80,16,4,4,2,3,780632,2240,780632,2240,0,16,64,16,4,4,0,3,780636,2240,780636,2240,0,16,0,16,4,4,2,3,3328076,1120,3329196,1120,0,16,120,16,4,4,2,3,4112076,1120,4113196,1120,0,16,120,16,4,4,6,3,644000,2240,646240,2240,144,16,192,16,64,64,6,3,3296720,1120,3297840,1120,136,16,224,16,32,32,6,3,4080720,1120,4081840,1120,136,16,224,16,32,32,4,3,646304,2240,646304,2240,240,16,128,16,64,64,4,3,3297872,1120,3297872,1120,184,16,128,16,32,32,4,3,4081872,1120,4081872,1120,184,16,128,16,32,32,0,3,646368,2240,646368,2240,0,16,0,16,64,64,0,3,3297904,1120,3297904,1120,0,16,0,16,32,32,0,3,4081904,1120,4081904,1120,0,16,0,16,32,32,0,3,646432,2240,646432,2240,0,16,0,16,64,64,0,3,3297936,1120,3297936,1120,0,16,0,16,32,32,0,3,4081936,1120,4081936,1120,0,16,0,16,32,32,0,3,646496,2240,646496,2240,0,16,0,16,64,64,0,3,3297968,1120,3297968,1120,0,16,0,16,32,32,0,3,4081968,1120,4081968,1120,0,16,0,16,32,32,0,3,646560,2240,646560,2240,0,16,0,16,64,64,0,3,3298000,1120,3298000,1120,0,16,0,16,32,32,0,3,4082000,1120,4082000,1120,0,16,0,16,32,32,0,3,646624,2240,646624,2240,0,16,0,16,64,64,0,3,3298032,1120,3298032,1120,0,16,0,16,32,32,0,3,4082032,1120,4082032,1120,0,16,0,16,32,32,0,3,646688,2240,646688,2240,0,16,0,16,64,64,0,3,3298064,1120,3298064,1120,0,16,0,16,32,32,0,3,4082064,1120,4082064,1120,0,16,0,16,32,32,0,3,646752,2240,646752,2240,0,16,0,16,64,64,0,3,3298096,1120,3298096,1120,0,16,0,16,32,32,0,3,4082096,1120,4082096,1120,0,16,0,16,32,32,0,3,646816,2240,646816,2240,0,16,0,16,64,64,0,3,3298128,1120,3298128,1120,0,16,0,16,32,32,0,3,4082128,1120,4082128,1120,0,16,0,16,32,32,0,3,646880,2240,646880,2240,0,16,0,16,64,64,0,3,3298160,1120,3298160,1120,0,16,0,16,32,32,0,3,4082160,1120,4082160,1120,0,16,0,16,32,32,0,3,646944,2240,646944,2240,0,16,0,16,64,64,0,3,3298192,1120,3298192,1120,0,16,0,16,32,32,0,3,4082192,1120,4082192,1120,0,16,0,16,32,32,0,3,647008,2240,647008,2240,0,16,0,16,64,64,0,3,3298224,1120,3298224,1120,0,16,0,16,32,32,0,3,4082224,1120,4082224,1120,0,16,0,16,32,32,0,3,647072,2240,647072,2240,0,16,0,16,64,64,0,3,3298256,1120,3298256,1120,0,16,0,16,32,32,0,3,4082256,1120,4082256,1120,0,16,0,16,32,32,0,3,647136,2240,647136,2240,0,16,0,16,64,64,0,3,3298288,1120,3298288,1120,0,16,0,16,32,32,0,3,4082288,1120,4082288,1120,0,16,0,16,32,32,0,3,788640,2240,788640,2240,0,16,0,16,64,64,0,3,3333200,1120,3333200,1120,0,16,0,16,32,32,0,3,4117200,1120,4117200,1120,0,16,0,16,32,32,0,3,788704,2240,788704,2240,0,16,0,16,64,64,0,3,3333232,1120,3333232,1120,0,16,0,16,32,32,0,3,4117232,1120,4117232,1120,0,16,0,16,32,32,0,3,788768,2240,788768,2240,0,16,0,16,64,64,0,3,3333264,1120,3333264,1120,0,16,0,16,32,32,0,3,4117264,1120,4117264,1120,0,16,0,16,32,32,0,3,788832,2240,788832,2240,0,16,0,16,64,64,0,3,3333296,1120,3333296,1120,0,16,0,16,32,32,0,3,4117296,1120,4117296,1120,0,16,0,16,32,32,0,3,788896,2240,788896,2240,0,16,0,16,64,64,0,3,3333328,1120,3333328,1120,0,16,0,16,32,32,0,3,4117328,1120,4117328,1120,0,16,0,16,32,32,0,3,788960,2240,788960,2240,0,16,0,16,64,64,0,3,3333360,1120,3333360,1120,0,16,0,16,32,32,0,3,4117360,1120,4117360,1120,0,16,0,16,32,32,0,3,789024,2240,789024,2240,0,16,0,16,64,64,0,3,3333392,1120,3333392,1120,0,16,0,16,32,32,0,3,4117392,1120,4117392,1120,0,16,0,16,32,32,0,3,789088,2240,789088,2240,0,16,0,16,64,64,0,3,3333424,1120,3333424,1120,0,16,0,16,32,32,0,3,4117424,1120,4117424,1120,0,16,0,16,32,32,0,3,789152,2240,789152,2240,0,16,0,16,64,64,0,3,3333456,1120,3333456,1120,0,16,0,16,32,32,0,3,4117456,1120,4117456,1120,0,16,0,16,32,32,0,3,789216,2240,789216,2240,0,16,0,16,64,64,0,3,3333488,1120,3333488,1120,0,16,0,16,32,32,0,3,4117488,1120,4117488,1120,0,16,0,16,32,32,0,3,789280,2240,789280,2240,0,16,0,16,64,64,0,3,3333520,1120,3333520,1120,0,16,0,16,32,32,0,3,4117520,1120,4117520,1120,0,16,0,16,32,32,0,3,789344,2240,789344,2240,0,16,0,16,64,64,0,3,3333552,1120,3333552,1120,0,16,0,16,32,32,0,3,4117552,1120,4117552,1120,0,16,0,16,32,32,0,3,789408,2240,789408,2240,0,16,0,16,64,64,0,3,3333584,1120,3333584,1120,0,16,0,16,32,32,0,3,4117584,1120,4117584,1120,0,16,0,16,32,32,0,3,789472,2240,789472,2240,0,16,0,16,16,16,0,3,3333616,1120,3333616,1120,0,16,0,16,8,8,0,3,4117616,1120,4117616,1120,0,16,0,16,8,8,0,3,789488,2240,789488,2240,0,16,0,16,16,16,0,3,3333624,1120,3333624,1120,0,16,0,16,8,8,0,3,4117624,1120,4117624,1120,0,16,0,16,8,8,0,3,825312,2240,825312,2240,0,16,0,16,16,16,0,3,3342576,1120,3342576,1120,0,16,0,16,8,8,0,3,4126576,1120,4126576,1120,0,16,0,16,8,8,0,3,825328,2240,825328,2240,0,16,0,16,16,16,0,3,3342584,1120,3342584,1120,0,16,0,16,8,8,0,3,4126584,1120,4126584,1120,0,16,0,16,8,8,0,3,789504,2240,789504,2240,0,16,0,16,16,16,0,3,3333632,1120,3333632,1120,0,16,0,16,8,8,0,3,4117632,1120,4117632,1120,0,16,0,16,8,8,4,3,798473,2240,789520,2240,112,16,0,16,16,16,4,3,3335876,1120,3333640,1120,120,16,0,16,8,8,4,3,4119876,1120,4117640,1120,120,16,0,16,8,8,2,3,823104,2240,825344,2240,128,16,176,16,16,16,2,3,3341472,1120,3342592,1120,128,16,216,16,8,8,2,3,4125472,1120,4126592,1120,128,16,216,16,8,8,6,3,832075,2240,825360,2240,192,16,192,16,16,16,6,3,3343717,1120,3342600,1120,224,16,224,16,8,8,6,3,4127717,1120,4126600,1120,224,16,224,16,8,8,6,3,858911,2240,861152,2240,80,16,112,16,32,16,6,3,3350415,1120,3351536,1120,104,16,120,16,16,8,6,3,4134415,1120,4135536,1120,104,16,120,16,16,8,6,3,894752,2240,896992,2240,32,16,64,16,32,16,6,3,3359376,1120,3360496,1120,16,16,96,16,16,8,6,3,4143376,1120,4144496,1120,16,16,96,16,16,8,2,3,858944,2240,861184,2240,128,16,176,16,8,8,2,3,3350432,1120,3351552,1120,128,16,216,16,4,4,2,3,4134432,1120,4135552,1120,128,16,216,16,4,4,6,3,863430,2240,861192,2240,144,16,192,16,8,8,6,3,3351555,1120,3351556,1120,136,16,224,16,4,4,6,3,4135555,1120,4135556,1120,136,16,224,16,4,4,2,3,876864,2240,879104,2240,128,16,176,16,8,8,2,3,3354912,1120,3356032,1120,128,16,216,16,4,4,2,3,4138912,1120,4140032,1120,128,16,216,16,4,4,2,3,876872,2240,879112,2240,128,16,176,16,8,8,2,3,3354916,1120,3356036,1120,128,16,216,16,4,4,2,3,4138916,1120,4140036,1120,128,16,216,16,4,4,6,3,863436,2240,861200,2240,208,16,224,16,16,16,6,3,3351558,1120,3351560,1120,168,16,240,16,8,8,6,3,4135558,1120,4135560,1120,168,16,240,16,8,8,2,3,894784,2240,897024,2240,128,16,176,16,16,16,2,3,3359392,1120,3360512,1120,128,16,216,16,8,8,2,3,4143392,1120,4144512,1120,128,16,216,16,8,8,2,3,894800,2240,897040,2240,128,16,176,16,8,16,2,3,3359400,1120,3360520,1120,128,16,216,16,4,8,2,3,4143400,1120,4144520,1120,128,16,216,16,4,8,6,3,894804,2240,897048,2240,192,16,176,16,8,16,6,3,3359402,1120,3360524,1120,160,16,216,16,4,8,6,3,4143402,1120,4144524,1120,160,16,216,16,4,8,4,3,798489,2240,789536,2240,112,16,0,16,8,8,4,3,3335884,1120,3333648,1120,120,16,0,16,4,4,4,3,4119884,1120,4117648,1120,120,16,0,16,4,4,6,3,816409,2240,807456,2240,240,16,160,16,8,8,6,3,3340364,1120,3338128,1120,248,16,144,16,4,4,6,3,4124364,1120,4122128,1120,248,16,144,16,4,4,0,3,807464,2240,807464,2240,128,16,128,16,8,8,0,3,3338132,1120,3338132,1120,128,16,128,16,4,4,0,3,4122132,1120,4122132,1120,128,16,128,16,4,4,2,3,787311,2240,789552,2240,0,16,112,16,16,8,6,3,3332535,1120,3333656,1120,64,16,120,16,8,4,6,3,4116535,1120,4117656,1120,64,16,120,16,8,4,6,3,809712,2240,807472,2240,160,16,240,16,16,8,6,3,3338136,1120,3338136,1120,144,16,248,16,8,4,6,3,4122136,1120,4122136,1120,144,16,248,16,8,4,6,3,834329,2240,825376,2240,240,16,160,16,16,16,6,3,3344844,1120,3342608,1120,248,16,144,16,8,8,6,3,4128844,1120,4126608,1120,248,16,144,16,8,8,0,3,825392,2240,825392,2240,128,16,128,16,8,8,0,3,3342616,1120,3342616,1120,128,16,128,16,4,4,0,3,4126616,1120,4126616,1120,128,16,128,16,4,4,6,3,827640,2240,825400,2240,176,16,192,16,8,8,6,3,3342620,1120,3342620,1120,152,16,224,16,4,4,6,3,4126620,1120,4126620,1120,152,16,224,16,4,4,0,3,843312,2240,843312,2240,128,16,128,16,4,4,6,3,852269,2240,843316,2240,240,16,160,16,4,4,6,3,861225,2240,852272,2240,240,16,160,16,4,4,6,3,861229,2240,852276,2240,240,16,160,16,4,4,6,3,3348213,1120,3347096,1120,216,16,208,16,4,4,6,3,4132213,1120,4131096,1120,216,16,208,16,4,4,6,3,845560,2240,843320,2240,176,16,192,16,8,8,6,3,3347100,1120,3347100,1120,152,16,224,16,4,4,6,3,4131100,1120,4131100,1120,152,16,224,16,4,4,2,3,791808,2240,789568,2240,0,16,112,16,16,16,2,3,3333664,1120,3333664,1120,0,16,120,16,8,8,2,3,4117664,1120,4117664,1120,0,16,120,16,8,8,6,3,794063,2240,789584,2240,112,16,64,16,8,8,6,3,3334791,1120,3333672,1120,120,16,32,16,4,4,6,3,4118791,1120,4117672,1120,120,16,32,16,4,4,6,3,794070,2240,789592,2240,96,16,112,16,4,4,2,3,787356,2240,789596,2240,0,16,80,16,4,4,6,3,803030,2240,798552,2240,96,16,112,16,4,4,2,3,796316,2240,798556,2240,0,16,80,16,4,4,6,3,3333675,1120,3333676,1120,88,16,80,16,4,4,6,3,4117675,1120,4117676,1120,88,16,80,16,4,4,6,3,811983,2240,807504,2240,240,16,192,16,8,8,6,3,3339271,1120,3338152,1120,248,16,160,16,4,4,6,3,4123271,1120,4122152,1120,248,16,160,16,4,4,6,3,811991,2240,807512,2240,112,16,64,16,8,8,6,3,3339275,1120,3338156,1120,120,16,32,16,4,4,6,3,4123275,1120,4122156,1120,120,16,32,16,4,4,2,3,827648,2240,825408,2240,128,16,160,16,16,16,2,3,3342624,1120,3342624,1120,128,16,208,16,8,8,2,3,4126624,1120,4126624,1120,128,16,208,16,8,8,0,3,825424,2240,825424,2240,128,16,128,16,16,8,0,3,3342632,1120,3342632,1120,128,16,128,16,8,4,0,3,4126632,1120,4126632,1120,128,16,128,16,8,4,2,3,845584,2240,843344,2240,128,16,160,16,16,8,2,3,3347112,1120,3347112,1120,128,16,208,16,8,4,2,3,4131112,1120,4131112,1120,128,16,208,16,8,4,6,3,863472,2240,861232,2240,176,16,192,16,16,16,6,3,3351576,1120,3351576,1120,152,16,224,16,8,8,6,3,4135576,1120,4135576,1120,152,16,224,16,8,8,4,3,897051,2240,897056,2240,144,16,128,16,8,8,4,3,3360525,1120,3360528,1120,200,16,128,16,4,4,4,3,4144525,1120,4144528,1120,200,16,128,16,4,4,6,3,897057,2240,897064,2240,224,16,176,16,8,8,6,3,3360528,1120,3360532,1120,240,16,152,16,4,4,6,3,4144528,1120,4144532,1120,240,16,152,16,4,4,6,3,912732,2240,914976,2240,192,16,176,16,8,8,6,3,3363886,1120,3365008,1120,160,16,216,16,4,4,6,3,4147886,1120,4149008,1120,160,16,216,16,4,4,6,3,914984,2240,914984,2240,192,16,208,16,8,8,6,3,3365012,1120,3365012,1120,160,16,168,16,4,4,6,3,4149012,1120,4149012,1120,160,16,168,16,4,4,6,3,897072,2240,897072,2240,48,16,96,16,16,16,6,3,3360536,1120,3360536,1120,24,16,48,16,8,8,6,3,4144536,1120,4144536,1120,24,16,48,16,8,8,6,3,861247,2240,861248,2240,224,16,224,16,16,16,6,3,3351583,1120,3351584,1120,240,16,176,16,8,8,6,3,4135583,1120,4135584,1120,240,16,176,16,8,8,4,3,863503,2240,861264,2240,192,16,128,16,16,16,6,3,3351591,1120,3351592,1120,224,16,192,16,8,8,6,3,4135591,1120,4135592,1120,224,16,192,16,8,8,6,3,894848,2240,897088,2240,80,16,80,16,8,16,6,3,3359424,1120,3360544,1120,40,16,104,16,4,8,6,3,4143424,1120,4144544,1120,40,16,104,16,4,8,6,3,894855,2240,897096,2240,96,16,96,16,8,16,6,3,3359427,1120,3360548,1120,112,16,112,16,4,8,6,3,4143427,1120,4144548,1120,112,16,112,16,4,8,6,3,894864,2240,897104,2240,32,16,80,16,16,16,6,3,3359432,1120,3360552,1120,16,16,104,16,8,8,6,3,4143432,1120,4144552,1120,16,16,104,16,8,8,6,3,787360,2240,789600,2240,144,16,192,16,8,8,6,3,3332560,1120,3333680,1120,136,16,224,16,4,4,6,3,4116560,1120,4117680,1120,136,16,224,16,4,4,0,3,789608,2240,789608,2240,128,16,128,16,4,4,0,3,789612,2240,789612,2240,128,16,128,16,4,4,6,3,800806,2240,798568,2240,240,16,224,16,4,4,6,3,800810,2240,798572,2240,240,16,224,16,4,4,6,3,3333683,1120,3333684,1120,216,16,184,16,4,4,6,3,4117683,1120,4117684,1120,216,16,184,16,4,4,2,3,814239,2240,807520,2240,128,16,192,16,8,8,6,3,3339279,1120,3338160,1120,192,16,224,16,4,4,6,3,4123279,1120,4122160,1120,192,16,224,16,4,4,2,3,809767,2240,807528,2240,0,16,64,16,8,8,6,3,3338163,1120,3338164,1120,64,16,96,16,4,4,6,3,4122163,1120,4122164,1120,64,16,96,16,4,4,2,3,787377,2240,789616,2240,128,16,224,16,16,16,6,3,3332568,1120,3333688,1120,192,16,240,16,8,8,6,3,4116568,1120,4117688,1120,192,16,240,16,8,8,6,3,827679,2240,825440,2240,176,16,160,16,16,16,6,3,3342639,1120,3342640,1120,216,16,208,16,8,8,6,3,4126639,1120,4126640,1120,216,16,208,16,8,8,4,3,827695,2240,825456,2240,160,16,128,16,8,16,6,3,3342647,1120,3342648,1120,208,16,192,16,4,8,6,3,4126647,1120,4126648,1120,208,16,192,16,4,8,6,3,823223,2240,825464,2240,64,16,48,16,8,16,6,3,3341531,1120,3342652,1120,96,16,88,16,4,8,6,3,4125531,1120,4126652,1120,96,16,88,16,4,8,2,3,787392,2240,789632,2240,0,16,64,16,16,16,2,3,3332576,1120,3333696,1120,0,16,96,16,8,8,2,3,4116576,1120,4117696,1120,0,16,96,16,8,8,6,3,787407,2240,789648,2240,64,16,64,16,16,16,6,3,3332583,1120,3333704,1120,96,16,96,16,8,8,6,3,4116583,1120,4117704,1120,96,16,96,16,8,8,6,3,823231,2240,825472,2240,64,16,48,16,8,8,6,3,3341535,1120,3342656,1120,96,16,88,16,4,4,6,3,4125535,1120,4126656,1120,96,16,88,16,4,4,2,3,823240,2240,825480,2240,0,16,64,16,4,4,2,3,823244,2240,825484,2240,0,16,64,16,4,4,6,3,832199,2240,834440,2240,64,16,48,16,4,4,6,3,832203,2240,834444,2240,64,16,48,16,4,4,6,3,3341539,1120,3342660,1120,112,16,88,16,4,4,6,3,4125539,1120,4126660,1120,112,16,88,16,4,4,2,3,841152,2240,843392,2240,0,16,64,16,8,8,2,3,3346016,1120,3347136,1120,0,16,96,16,4,4,2,3,4130016,1120,4131136,1120,0,16,96,16,4,4,6,3,841159,2240,843400,2240,32,16,32,16,8,8,6,3,3346019,1120,3347140,1120,80,16,80,16,4,4,6,3,4130019,1120,4131140,1120,80,16,80,16,4,4,6,3,818767,2240,825488,2240,64,16,80,16,8,8,6,3,3340423,1120,3342664,1120,96,16,104,16,4,4,6,3,4124423,1120,4126664,1120,96,16,104,16,4,4,2,3,829968,2240,825496,2240,0,16,48,16,4,4,2,3,829972,2240,825500,2240,0,16,48,16,4,4,6,3,829975,2240,834456,2240,64,16,48,16,4,4,6,3,829979,2240,834460,2240,64,16,48,16,4,4,6,3,3342665,1120,3342668,1120,112,16,24,16,4,4,6,3,4126665,1120,4126668,1120,112,16,24,16,4,4,6,3,841167,2240,843408,2240,64,16,48,16,8,8,6,3,3346023,1120,3347144,1120,96,16,88,16,4,4,6,3,4130023,1120,4131144,1120,96,16,88,16,4,4,6,3,841175,2240,843416,2240,64,16,48,16,8,8,6,3,3346027,1120,3347148,1120,96,16,88,16,4,4,6,3,4130027,1120,4131148,1120,96,16,88,16,4,4,4,3,863519,2240,861280,2240,64,16,0,16,16,8,6,3,3351599,1120,3351600,1120,96,16,64,16,8,4,6,3,4135599,1120,4135600,1120,96,16,64,16,8,4,0,3,879200,2240,879200,2240,0,16,0,16,16,8,0,3,3356080,1120,3356080,1120,0,16,0,16,8,4,0,3,4140080,1120,4140080,1120,0,16,0,16,8,4,4,3,863535,2240,861296,2240,64,16,0,16,8,16,6,3,3351607,1120,3351608,1120,96,16,64,16,4,8,6,3,4135607,1120,4135608,1120,96,16,64,16,4,8,4,3,863543,2240,861304,2240,64,16,0,16,8,16,6,3,3351611,1120,3351612,1120,96,16,64,16,4,8,6,3,4135611,1120,4135612,1120,96,16,64,16,4,8,6,3,894880,2240,897120,2240,32,16,80,16,8,16,6,3,3359440,1120,3360560,1120,16,16,104,16,4,8,6,3,4143440,1120,4144560,1120,16,16,104,16,4,8,6,3,894887,2240,897128,2240,64,16,96,16,8,16,6,3,3359443,1120,3360564,1120,96,16,112,16,4,8,6,3,4143443,1120,4144564,1120,96,16,112,16,4,8,6,3,894895,2240,897136,2240,192,16,224,16,16,16,6,3,3359447,1120,3360568,1120,224,16,240,16,8,8,6,3,4143447,1120,4144568,1120,224,16,240,16,8,8,6,3,859072,2240,861312,2240,48,16,64,16,8,8,6,3,3350496,1120,3351616,1120,24,16,96,16,4,4,6,3,4134496,1120,4135616,1120,24,16,96,16,4,4,2,3,859080,2240,861320,2240,0,16,96,16,8,8,2,3,3350500,1120,3351620,1120,0,16,112,16,4,4,2,3,4134500,1120,4135620,1120,0,16,112,16,4,4,6,3,876992,2240,879232,2240,48,16,64,16,4,4,6,3,876996,2240,879236,2240,48,16,64,16,4,4,2,3,885951,2240,888192,2240,0,16,64,16,4,4,2,3,885955,2240,888196,2240,0,16,64,16,4,4,6,3,3354975,1120,3356096,1120,104,16,96,16,4,4,6,3,4138975,1120,4140096,1120,104,16,96,16,4,4,6,3,879239,2240,879240,2240,144,16,144,16,8,8,6,3,3356099,1120,3356100,1120,200,16,136,16,4,4,6,3,4140099,1120,4140100,1120,200,16,136,16,4,4,6,3,859087,2240,861328,2240,64,16,112,16,8,8,6,3,3350503,1120,3351624,1120,96,16,120,16,4,4,6,3,4134503,1120,4135624,1120,96,16,120,16,4,4,6,3,859095,2240,861336,2240,64,16,48,16,4,4,6,3,859099,2240,861340,2240,64,16,112,16,4,4,6,3,868055,2240,870296,2240,64,16,48,16,4,4,6,3,868059,2240,870300,2240,64,16,112,16,4,4,6,3,3350507,1120,3351628,1120,96,16,104,16,4,4,6,3,4134507,1120,4135628,1120,96,16,104,16,4,4,6,3,877007,2240,879248,2240,64,16,112,16,8,8,6,3,3354983,1120,3356104,1120,96,16,120,16,4,4,6,3,4138983,1120,4140104,1120,96,16,120,16,4,4,6,3,877015,2240,879256,2240,112,16,64,16,8,8,6,3,3354987,1120,3356108,1120,120,16,96,16,4,4,6,3,4138987,1120,4140108,1120,120,16,96,16,4,4,0,3,897152,2240,897152,2240,0,16,0,16,16,8,0,3,3360576,1120,3360576,1120,0,16,0,16,8,4,0,3,4144576,1120,4144576,1120,0,16,0,16,8,4,6,3,912831,2240,915072,2240,64,16,96,16,16,8,6,3,3363935,1120,3365056,1120,96,16,112,16,8,4,6,3,4147935,1120,4149056,1120,96,16,112,16,8,4,6,3,897166,2240,897168,2240,64,16,64,16,16,16,6,3,3360583,1120,3360584,1120,32,16,32,16,8,8,6,3,4144583,1120,4144584,1120,32,16,32,16,8,8,4,3,787423,2240,789664,2240,176,16,128,16,32,32,6,3,3332591,1120,3333712,1120,216,16,192,16,16,16,6,3,4116591,1120,4117712,1120,216,16,192,16,16,16,4,3,789696,2240,789696,2240,176,16,128,16,32,32,4,3,3333728,1120,3333728,1120,152,16,128,16,16,16,4,3,4117728,1120,4117728,1120,152,16,128,16,16,16,6,3,859103,2240,861344,2240,64,16,112,16,16,8,6,3,3350511,1120,3351632,1120,96,16,120,16,8,4,6,3,4134511,1120,4135632,1120,96,16,120,16,8,4,6,3,877023,2240,879264,2240,64,16,112,16,16,8,6,3,3354991,1120,3356112,1120,96,16,120,16,8,4,6,3,4138991,1120,4140112,1120,96,16,120,16,8,4,6,3,859120,2240,861360,2240,32,16,112,16,16,8,6,3,3350520,1120,3351640,1120,16,16,120,16,8,4,6,3,4134520,1120,4135640,1120,16,16,120,16,8,4,6,3,877040,2240,879280,2240,160,16,192,16,16,8,6,3,3355000,1120,3356120,1120,144,16,224,16,8,4,6,3,4139000,1120,4140120,1120,144,16,224,16,8,4,6,3,897182,2240,897184,2240,192,16,192,16,8,8,6,3,3360591,1120,3360592,1120,160,16,160,16,4,4,6,3,4144591,1120,4144592,1120,160,16,160,16,4,4,0,3,897192,2240,897192,2240,0,16,0,16,8,8,0,3,3360596,1120,3360596,1120,0,16,0,16,4,4,0,3,4144596,1120,4144596,1120,0,16,0,16,4,4,6,3,915102,2240,915104,2240,64,16,64,16,8,8,6,3,3365071,1120,3365072,1120,32,16,32,16,4,4,6,3,4149071,1120,4149072,1120,32,16,32,16,4,4,6,3,915110,2240,915112,2240,192,16,192,16,8,8,6,3,3365075,1120,3365076,1120,160,16,160,16,4,4,6,3,4149075,1120,4149076,1120,160,16,160,16,4,4,6,3,897200,2240,897200,2240,32,16,16,16,8,8,6,3,3360600,1120,3360600,1120,16,16,8,16,4,4,6,3,4144600,1120,4144600,1120,16,16,8,16,4,4,6,3,894968,2240,897208,2240,160,16,192,16,8,8,6,3,3359484,1120,3360604,1120,144,16,224,16,4,4,6,3,4143484,1120,4144604,1120,144,16,224,16,4,4,6,3,915118,2240,915120,2240,192,16,192,16,4,4,0,3,915124,2240,915124,2240,128,16,128,16,4,4,6,3,924078,2240,924080,2240,192,16,192,16,4,4,0,3,924084,2240,924084,2240,128,16,128,16,4,4,6,3,3365079,1120,3365080,1120,208,16,144,16,4,4,6,3,4149079,1120,4149080,1120,208,16,144,16,4,4,6,3,912888,2240,915128,2240,160,16,192,16,8,8,6,3,3363964,1120,3365084,1120,144,16,224,16,4,4,6,3,4147964,1120,4149084,1120,144,16,224,16,4,4,0,3,861376,2240,861376,2240,0,16,0,16,16,16,0,3,3351648,1120,3351648,1120,0,16,0,16,8,8,0,3,4135648,1120,4135648,1120,0,16,0,16,8,8,4,3,861392,2240,861392,2240,48,16,0,16,16,16,4,3,3351656,1120,3351656,1120,24,16,0,16,8,8,4,3,4135656,1120,4135656,1120,24,16,0,16,8,8,0,3,897216,2240,897216,2240,0,16,0,16,8,8,0,3,3360608,1120,3360608,1120,0,16,0,16,4,4,0,3,4144608,1120,4144608,1120,0,16,0,16,4,4,6,3,894984,2240,897224,2240,32,16,64,16,8,8,6,3,3359492,1120,3360612,1120,16,16,96,16,4,4,6,3,4143492,1120,4144612,1120,16,16,96,16,4,4,0,3,915136,2240,915136,2240,0,16,0,16,8,8,0,3,3365088,1120,3365088,1120,0,16,0,16,4,4,0,3,4149088,1120,4149088,1120,0,16,0,16,4,4,6,3,912904,2240,915144,2240,32,16,64,16,8,8,6,3,3363972,1120,3365092,1120,16,16,96,16,4,4,6,3,4147972,1120,4149092,1120,16,16,96,16,4,4,6,3,897232,2240,897232,2240,48,16,112,16,16,16,6,3,3360616,1120,3360616,1120,24,16,56,16,8,8,6,3,4144616,1120,4144616,1120,24,16,56,16,8,8,0,3,789728,2240,789728,2240,0,16,0,16,64,64,0,3,3333744,1120,3333744,1120,0,16,0,16,32,32,0,3,4117744,1120,4117744,1120,0,16,0,16,32,32,0,3,789792,2240,789792,2240,0,16,0,16,64,64,0,3,3333776,1120,3333776,1120,0,16,0,16,32,32,0,3,4117776,1120,4117776,1120,0,16,0,16,32,32,0,3,789856,2240,789856,2240,0,16,0,16,64,64,0,3,3333808,1120,3333808,1120,0,16,0,16,32,32,0,3,4117808,1120,4117808,1120,0,16,0,16,32,32,0,3,789920,2240,789920,2240,0,16,0,16,64,64,0,3,3333840,1120,3333840,1120,0,16,0,16,32,32,0,3,4117840,1120,4117840,1120,0,16,0,16,32,32,0,3,789984,2240,789984,2240,0,16,0,16,64,64,0,3,3333872,1120,3333872,1120,0,16,0,16,32,32,0,3,4117872,1120,4117872,1120,0,16,0,16,32,32,0,3,790048,2240,790048,2240,0,16,0,16,64,64,0,3,3333904,1120,3333904,1120,0,16,0,16,32,32,0,3,4117904,1120,4117904,1120,0,16,0,16,32,32,0,3,790112,2240,790112,2240,0,16,0,16,64,64,0,3,3333936,1120,3333936,1120,0,16,0,16,32,32,0,3,4117936,1120,4117936,1120,0,16,0,16,32,32,0,3,790176,2240,790176,2240,0,16,0,16,64,64,0,3,3333968,1120,3333968,1120,0,16,0,16,32,32,0,3,4117968,1120,4117968,1120,0,16,0,16,32,32,0,3,790240,2240,790240,2240,0,16,0,16,64,64,0,3,3334000,1120,3334000,1120,0,16,0,16,32,32,0,3,4118000,1120,4118000,1120,0,16,0,16,32,32,0,3,790304,2240,790304,2240,0,16,0,16,64,64,0,3,3334032,1120,3334032,1120,0,16,0,16,32,32,0,3,4118032,1120,4118032,1120,0,16,0,16,32,32,0,3,790368,2240,790368,2240,0,16,0,16,64,64,0,3,3334064,1120,3334064,1120,0,16,0,16,32,32,0,3,4118064,1120,4118064,1120,0,16,0,16,32,32,0,3,790432,2240,790432,2240,0,16,0,16,64,64,0,3,3334096,1120,3334096,1120,0,16,0,16,32,32,0,3,4118096,1120,4118096,1120,0,16,0,16,32,32,0,3,790496,2240,790496,2240,0,16,0,16,64,64,0,3,3334128,1120,3334128,1120,0,16,0,16,32,32,0,3,4118128,1120,4118128,1120,0,16,0,16,32,32,0,3,932000,2240,932000,2240,0,16,0,16,64,64,0,3,3369040,1120,3369040,1120,0,16,0,16,32,32,0,3,4153040,1120,4153040,1120,0,16,0,16,32,32,0,3,932064,2240,932064,2240,0,16,0,16,64,64,0,3,3369072,1120,3369072,1120,0,16,0,16,32,32,0,3,4153072,1120,4153072,1120,0,16,0,16,32,32,0,3,932128,2240,932128,2240,0,16,0,16,64,64,0,3,3369104,1120,3369104,1120,0,16,0,16,32,32,0,3,4153104,1120,4153104,1120,0,16,0,16,32,32,0,3,932192,2240,932192,2240,0,16,0,16,64,64,0,3,3369136,1120,3369136,1120,0,16,0,16,32,32,0,3,4153136,1120,4153136,1120,0,16,0,16,32,32,0,3,932256,2240,932256,2240,0,16,0,16,64,64,0,3,3369168,1120,3369168,1120,0,16,0,16,32,32,0,3,4153168,1120,4153168,1120,0,16,0,16,32,32,0,3,932320,2240,932320,2240,0,16,0,16,64,64,0,3,3369200,1120,3369200,1120,0,16,0,16,32,32,0,3,4153200,1120,4153200,1120,0,16,0,16,32,32,0,3,932384,2240,932384,2240,0,16,0,16,64,64,0,3,3369232,1120,3369232,1120,0,16,0,16,32,32,0,3,4153232,1120,4153232,1120,0,16,0,16,32,32,0,3,932448,2240,932448,2240,0,16,0,16,64,64,0,3,3369264,1120,3369264,1120,0,16,0,16,32,32,0,3,4153264,1120,4153264,1120,0,16,0,16,32,32,0,3,932512,2240,932512,2240,0,16,0,16,64,64,0,3,3369296,1120,3369296,1120,0,16,0,16,32,32,0,3,4153296,1120,4153296,1120,0,16,0,16,32,32,0,3,932576,2240,932576,2240,0,16,0,16,64,64,0,3,3369328,1120,3369328,1120,0,16,0,16,32,32,0,3,4153328,1120,4153328,1120,0,16,0,16,32,32,0,3,932640,2240,932640,2240,0,16,0,16,64,64,0,3,3369360,1120,3369360,1120,0,16,0,16,32,32,0,3,4153360,1120,4153360,1120,0,16,0,16,32,32,0,3,932704,2240,932704,2240,0,16,0,16,64,64,0,3,3369392,1120,3369392,1120,0,16,0,16,32,32,0,3,4153392,1120,4153392,1120,0,16,0,16,32,32,0,3,932768,2240,932768,2240,0,16,0,16,64,64,0,3,3369424,1120,3369424,1120,0,16,0,16,32,32,0,3,4153424,1120,4153424,1120,0,16,0,16,32,32,2,3,930592,2240,932832,2240,0,16,64,16,32,32,2,3,3368336,1120,3369456,1120,0,16,96,16,16,16,2,3,4152336,1120,4153456,1120,0,16,96,16,16,16,2,3,932865,2240,932864,2240,128,16,208,16,16,16,6,3,3369472,1120,3369472,1120,192,16,168,16,8,8,6,3,4153472,1120,4153472,1120,192,16,168,16,8,8,6,3,932880,2240,932880,2240,208,16,192,16,8,16,6,3,3369480,1120,3369480,1120,168,16,160,16,4,8,6,3,4153480,1120,4153480,1120,168,16,160,16,4,8,6,3,932888,2240,932888,2240,208,16,192,16,8,16,6,3,3369484,1120,3369484,1120,168,16,160,16,4,8,6,3,4153484,1120,4153484,1120,168,16,160,16,4,8,0,3,968704,2240,968704,2240,0,16,0,16,16,16,0,3,3378432,1120,3378432,1120,0,16,0,16,8,8,0,3,4162432,1120,4162432,1120,0,16,0,16,8,8,0,3,968720,2240,968720,2240,0,16,0,16,8,16,0,3,3378440,1120,3378440,1120,0,16,0,16,4,8,0,3,4162440,1120,4162440,1120,0,16,0,16,4,8,4,3,968728,2240,968728,2240,176,16,128,16,8,16,4,3,3378444,1120,3378444,1120,152,16,128,16,4,8,4,3,4162444,1120,4162444,1120,152,16,128,16,4,8,2,3,1002272,2240,1004512,2240,0,16,64,16,32,32,2,3,3386256,1120,3387376,1120,0,16,96,16,16,16,2,3,4170256,1120,4171376,1120,0,16,96,16,16,16,6,3,1002303,2240,1004544,2240,112,16,16,16,16,32,6,3,3386271,1120,3387392,1120,120,16,72,16,8,16,6,3,4170271,1120,4171392,1120,120,16,72,16,8,16,0,3,1004560,2240,1004560,2240,0,16,0,16,16,32,0,3,3387400,1120,3387400,1120,0,16,0,16,8,16,0,3,4171400,1120,4171400,1120,0,16,0,16,8,16,6,3,932896,2240,932896,2240,208,16,192,16,8,16,6,3,3369488,1120,3369488,1120,168,16,160,16,4,8,6,3,4153488,1120,4153488,1120,168,16,160,16,4,8,6,3,932904,2240,932904,2240,48,16,48,16,8,16,6,3,3369492,1120,3369492,1120,24,16,24,16,4,8,6,3,4153492,1120,4153492,1120,24,16,24,16,4,8,6,3,932912,2240,932912,2240,48,16,48,16,16,8,6,3,3369496,1120,3369496,1120,24,16,24,16,8,4,6,3,4153496,1120,4153496,1120,24,16,24,16,8,4,2,3,950832,2240,950832,2240,128,16,160,16,16,8,2,3,3373976,1120,3373976,1120,128,16,144,16,8,4,2,3,4157976,1120,4157976,1120,128,16,144,16,8,4,6,3,968736,2240,968736,2240,80,16,64,16,8,16,6,3,3378448,1120,3378448,1120,40,16,32,16,4,8,6,3,4162448,1120,4162448,1120,40,16,32,16,4,8,6,3,968744,2240,968744,2240,32,16,64,16,8,16,6,3,3378452,1120,3378452,1120,16,16,32,16,4,8,6,3,4162452,1120,4162452,1120,16,16,32,16,4,8,6,3,966511,2240,968752,2240,96,16,48,16,16,16,6,3,3377335,1120,3378456,1120,112,16,88,16,8,8,6,3,4161335,1120,4162456,1120,112,16,88,16,8,8,6,3,932927,2240,932928,2240,96,16,64,16,16,16,6,3,3369503,1120,3369504,1120,112,16,32,16,8,8,6,3,4153503,1120,4153504,1120,112,16,32,16,8,8,6,3,930704,2240,932944,2240,16,16,96,16,16,16,6,3,3368392,1120,3369512,1120,8,16,112,16,8,8,6,3,4152392,1120,4153512,1120,8,16,112,16,8,8,6,3,968767,2240,968768,2240,96,16,112,16,16,8,6,3,3378463,1120,3378464,1120,112,16,56,16,8,4,6,3,4162463,1120,4162464,1120,112,16,56,16,8,4,4,3,986688,2240,986688,2240,48,16,0,16,16,8,4,3,3382944,1120,3382944,1120,24,16,0,16,8,4,4,3,4166944,1120,4166944,1120,24,16,0,16,8,4,6,3,968783,2240,968784,2240,224,16,160,16,16,16,6,3,3378471,1120,3378472,1120,240,16,144,16,8,8,6,3,4162471,1120,4162472,1120,240,16,144,16,8,8,2,3,1002336,2240,1004576,2240,0,16,96,16,16,32,2,3,3386288,1120,3387408,1120,0,16,112,16,8,16,2,3,4170288,1120,4171408,1120,0,16,112,16,8,16,2,3,1002352,2240,1004592,2240,0,16,112,16,16,32,2,3,3386296,1120,3387416,1120,0,16,120,16,8,16,2,3,4170296,1120,4171416,1120,0,16,120,16,8,16,4,3,1004608,2240,1004608,2240,176,16,128,16,8,16,4,3,3387424,1120,3387424,1120,152,16,128,16,4,8,4,3,4171424,1120,4171424,1120,152,16,128,16,4,8,6,3,1002375,2240,1004616,2240,112,16,80,16,8,16,6,3,3386307,1120,3387428,1120,120,16,104,16,4,8,6,3,4170307,1120,4171428,1120,120,16,104,16,4,8,6,3,1002383,2240,1004624,2240,240,16,208,16,16,16,6,3,3386311,1120,3387432,1120,248,16,232,16,8,8,6,3,4170311,1120,4171432,1120,248,16,232,16,8,8,2,3,1038208,2240,1040448,2240,128,16,240,16,16,16,2,3,3395264,1120,3396384,1120,128,16,248,16,8,8,2,3,4179264,1120,4180384,1120,128,16,248,16,8,8,6,3,1038223,2240,1040464,2240,240,16,208,16,8,16,6,3,3395271,1120,3396392,1120,248,16,232,16,4,8,6,3,4179271,1120,4180392,1120,248,16,232,16,4,8,6,3,1040470,2240,1040472,2240,224,16,224,16,8,16,6,3,3396395,1120,3396396,1120,176,16,176,16,4,8,6,3,4180395,1120,4180396,1120,176,16,176,16,4,8,6,3,932959,2240,932960,2240,224,16,144,16,16,8,6,3,3369519,1120,3369520,1120,240,16,136,16,8,4,6,3,4153519,1120,4153520,1120,240,16,136,16,8,4,6,3,948640,2240,950880,2240,272,16,352,16,16,8,6,3,3372880,1120,3374000,1120,264,16,368,16,8,4,6,3,4156880,1120,4158000,1120,264,16,368,16,8,4,2,3,932975,2240,932976,2240,0,16,48,16,16,16,6,3,3369527,1120,3369528,1120,64,16,24,16,8,8,6,3,4153527,1120,4153528,1120,64,16,24,16,8,8,6,3,966559,2240,968800,2240,112,16,96,16,16,8,6,3,3377359,1120,3378480,1120,120,16,112,16,8,4,6,3,4161359,1120,4162480,1120,120,16,112,16,8,4,4,3,986719,2240,986720,2240,64,16,0,16,16,8,4,3,3382959,1120,3382960,1120,96,16,0,16,8,4,4,3,4166959,1120,4166960,1120,96,16,0,16,8,4,6,3,966575,2240,968816,2240,112,16,96,16,16,8,6,3,3377367,1120,3378488,1120,120,16,112,16,8,4,6,3,4161367,1120,4162488,1120,120,16,112,16,8,4,6,3,984495,2240,986736,2240,160,16,224,16,16,8,6,3,3381847,1120,3382968,1120,208,16,240,16,8,4,6,3,4165847,1120,4166968,1120,208,16,240,16,8,4,2,3,932991,2240,932992,2240,128,16,176,16,16,16,6,3,3369535,1120,3369536,1120,192,16,152,16,8,8,6,3,4153535,1120,4153536,1120,192,16,152,16,8,8,2,3,933007,2240,933008,2240,128,16,240,16,8,8,6,3,3369543,1120,3369544,1120,192,16,184,16,4,4,6,3,4153543,1120,4153544,1120,192,16,184,16,4,4,2,3,933015,2240,933016,2240,128,16,240,16,8,8,6,3,3369547,1120,3369548,1120,192,16,184,16,4,4,6,3,4153547,1120,4153548,1120,192,16,184,16,4,4,0,3,950928,2240,950928,2240,128,16,128,16,8,8,0,3,3374024,1120,3374024,1120,128,16,128,16,4,4,0,3,4158024,1120,4158024,1120,128,16,128,16,4,4,6,3,950934,2240,950936,2240,240,16,176,16,8,8,6,3,3374027,1120,3374028,1120,184,16,152,16,4,4,6,3,4158027,1120,4158028,1120,184,16,152,16,4,4,6,3,966591,2240,968832,2240,112,16,96,16,8,8,6,3,3377375,1120,3378496,1120,120,16,112,16,4,4,6,3,4161375,1120,4162496,1120,120,16,112,16,4,4,2,3,968839,2240,968840,2240,0,16,48,16,4,4,0,3,968844,2240,968844,2240,0,16,0,16,4,4,2,3,977799,2240,977800,2240,0,16,48,16,4,4,0,3,977804,2240,977804,2240,0,16,0,16,4,4,6,3,3378499,1120,3378500,1120,96,16,16,16,4,4,6,3,4162499,1120,4162500,1120,96,16,16,16,4,4,4,3,986750,2240,986752,2240,112,16,0,16,8,8,4,3,3382975,1120,3382976,1120,56,16,0,16,4,4,4,3,4166975,1120,4166976,1120,56,16,0,16,4,4,4,3,986758,2240,986760,2240,112,16,0,16,8,8,4,3,3382979,1120,3382980,1120,56,16,0,16,4,4,4,3,4166979,1120,4166980,1120,56,16,0,16,4,4,0,3,968848,2240,968848,2240,0,16,0,16,8,8,0,3,3378504,1120,3378504,1120,0,16,0,16,4,4,0,3,4162504,1120,4162504,1120,0,16,0,16,4,4,6,3,968855,2240,968856,2240,64,16,48,16,8,8,6,3,3378507,1120,3378508,1120,96,16,24,16,4,4,6,3,4162507,1120,4162508,1120,96,16,24,16,4,4,0,3,986768,2240,986768,2240,0,16,0,16,8,8,0,3,3382984,1120,3382984,1120,0,16,0,16,4,4,0,3,4166984,1120,4166984,1120,0,16,0,16,4,4,2,3,986775,2240,986776,2240,0,16,32,16,8,8,6,3,3382987,1120,3382988,1120,64,16,16,16,4,4,6,3,4166987,1120,4166988,1120,64,16,16,16,4,4,4,3,1004640,2240,1004640,2240,144,16,128,16,16,16,4,3,3387440,1120,3387440,1120,136,16,128,16,8,8,4,3,4171440,1120,4171440,1120,136,16,128,16,8,8,6,3,1004655,2240,1004656,2240,160,16,144,16,16,8,6,3,3387447,1120,3387448,1120,208,16,136,16,8,4,6,3,4171447,1120,4171448,1120,208,16,136,16,8,4,4,3,1022576,2240,1022576,2240,16,16,0,16,16,8,4,3,3391928,1120,3391928,1120,8,16,0,16,8,4,4,3,4175928,1120,4175928,1120,8,16,0,16,8,4,6,3,1040478,2240,1040480,2240,224,16,224,16,8,16,6,3,3396399,1120,3396400,1120,176,16,176,16,4,8,6,3,4180399,1120,4180400,1120,176,16,176,16,4,8,6,3,1038247,2240,1040488,2240,160,16,208,16,8,16,6,3,3395283,1120,3396404,1120,208,16,232,16,4,8,6,3,4179283,1120,4180404,1120,208,16,232,16,4,8,0,3,1040495,2240,1040496,2240,0,16,0,16,8,16,4,3,3396407,1120,3396408,1120,64,16,0,16,4,8,4,3,4180407,1120,4180408,1120,64,16,0,16,4,8,0,3,1040503,2240,1040504,2240,0,16,0,16,8,16,4,3,3396411,1120,3396412,1120,64,16,0,16,4,8,4,3,4180411,1120,4180412,1120,64,16,0,16,4,8,6,3,1002431,2240,1004672,2240,96,16,96,16,16,16,6,3,3386335,1120,3387456,1120,112,16,112,16,8,8,6,3,4170335,1120,4171456,1120,112,16,112,16,8,8,4,3,1004686,2240,1004688,2240,112,16,0,16,8,16,4,3,3387463,1120,3387464,1120,56,16,0,16,4,8,4,3,4171463,1120,4171464,1120,56,16,0,16,4,8,6,3,1002455,2240,1004696,2240,96,16,80,16,8,16,6,3,3386347,1120,3387468,1120,112,16,104,16,4,8,6,3,4170347,1120,4171468,1120,112,16,104,16,4,8,0,3,1040511,2240,1040512,2240,0,16,0,16,16,16,4,3,3396415,1120,3396416,1120,64,16,0,16,8,8,4,3,4180415,1120,4180416,1120,64,16,0,16,8,8,6,3,1038286,2240,1040528,2240,64,16,112,16,16,16,6,3,3395303,1120,3396424,1120,32,16,120,16,8,8,6,3,4179303,1120,4180424,1120,32,16,120,16,8,8,6,3,933022,2240,933024,2240,64,16,64,16,8,8,6,3,3369551,1120,3369552,1120,32,16,32,16,4,4,6,3,4153551,1120,4153552,1120,32,16,32,16,4,4,6,3,933030,2240,933032,2240,64,16,64,16,4,4,6,3,933034,2240,933036,2240,64,16,64,16,4,4,0,3,941992,2240,941992,2240,0,16,0,16,4,4,0,3,941996,2240,941996,2240,0,16,0,16,4,4,6,3,3369555,1120,3369556,1120,80,16,16,16,4,4,6,3,4153555,1120,4153556,1120,80,16,16,16,4,4,6,3,948702,2240,950944,2240,320,16,368,16,8,8,6,3,3372911,1120,3374032,1120,288,16,376,16,4,4,6,3,4156911,1120,4158032,1120,288,16,376,16,4,4,6,3,950950,2240,950952,2240,32,16,80,16,8,8,6,3,3374035,1120,3374036,1120,16,16,40,16,4,4,6,3,4158035,1120,4158036,1120,16,16,40,16,4,4,6,3,933038,2240,933040,2240,288,16,336,16,8,16,6,3,3369559,1120,3369560,1120,272,16,296,16,4,8,6,3,4153559,1120,4153560,1120,272,16,296,16,4,8,0,3,933048,2240,933048,2240,0,16,0,16,8,16,0,3,3369564,1120,3369564,1120,0,16,0,16,4,8,0,3,4153564,1120,4153564,1120,0,16,0,16,4,8,6,3,968862,2240,968864,2240,192,16,160,16,16,8,6,3,3378511,1120,3378512,1120,160,16,144,16,8,4,6,3,4162511,1120,4162512,1120,160,16,144,16,8,4,6,3,986782,2240,986784,2240,64,16,32,16,16,8,6,3,3382991,1120,3382992,1120,32,16,16,16,8,4,6,3,4166991,1120,4166992,1120,32,16,16,16,8,4,6,3,968878,2240,968880,2240,16,16,32,16,8,16,6,3,3378519,1120,3378520,1120,8,16,16,16,4,8,6,3,4162519,1120,4162520,1120,8,16,16,16,4,8,0,3,968888,2240,968888,2240,0,16,0,16,8,16,0,3,3378524,1120,3378524,1120,0,16,0,16,4,8,0,3,4162524,1120,4162524,1120,0,16,0,16,4,8,4,3,933056,2240,933056,2240,16,16,0,16,16,32,4,3,3369568,1120,3369568,1120,8,16,0,16,8,16,4,3,4153568,1120,4153568,1120,8,16,0,16,8,16,4,3,933072,2240,933072,2240,16,16,0,16,16,32,4,3,3369576,1120,3369576,1120,8,16,0,16,8,16,4,3,4153576,1120,4153576,1120,8,16,0,16,8,16,6,3,1002462,2240,1004704,2240,208,16,240,16,16,16,6,3,3386351,1120,3387472,1120,168,16,248,16,8,8,6,3,4170351,1120,4171472,1120,168,16,248,16,8,8,6,3,1004718,2240,1004720,2240,16,16,32,16,4,4,6,3,1004722,2240,1004724,2240,16,16,32,16,4,4,6,3,1011438,2240,1013680,2240,80,16,112,16,4,4,6,3,1011442,2240,1013684,2240,80,16,112,16,4,4,6,3,3387479,1120,3387480,1120,24,16,8,16,4,4,6,3,4171479,1120,4171480,1120,24,16,8,16,4,4,6,3,1004726,2240,1004728,2240,16,16,32,16,4,4,6,3,1004730,2240,1004732,2240,16,16,32,16,4,4,6,3,1013686,2240,1013688,2240,16,16,32,16,4,4,6,3,1013690,2240,1013692,2240,16,16,32,16,4,4,6,3,3387483,1120,3387484,1120,8,16,16,16,4,4,6,3,4171483,1120,4171484,1120,8,16,16,16,4,4,0,3,1022648,2240,1022648,2240,0,16,0,16,8,8,0,3,3391964,1120,3391964,1120,0,16,0,16,4,4,0,3,4175964,1120,4175964,1120,0,16,0,16,4,4,4,3,1040543,2240,1040544,2240,176,16,128,16,16,16,4,3,3396431,1120,3396432,1120,216,16,128,16,8,8,4,3,4180431,1120,4180432,1120,216,16,128,16,8,8,6,3,1038325,2240,1040568,2240,32,16,80,16,8,8,6,3,3395322,1120,3396444,1120,80,16,104,16,4,4,6,3,4179322,1120,4180444,1120,80,16,104,16,4,4,6,3,1054000,2240,1058480,2240,160,16,240,16,8,8,6,3,3399800,1120,3400920,1120,144,16,184,16,4,4,6,3,4183800,1120,4184920,1120,144,16,184,16,4,4,6,3,1056245,2240,1058488,2240,32,16,80,16,8,8,6,3,3399802,1120,3400924,1120,80,16,104,16,4,4,6,3,4183802,1120,4184924,1120,80,16,104,16,4,4,4,3,1004736,2240,1004736,2240,16,16,0,16,16,16,4,3,3387488,1120,3387488,1120,8,16,0,16,8,8,4,3,4171488,1120,4171488,1120,8,16,0,16,8,8,4,3,1004752,2240,1004752,2240,16,16,0,16,8,16,4,3,3387496,1120,3387496,1120,8,16,0,16,4,8,4,3,4171496,1120,4171496,1120,8,16,0,16,4,8,4,3,1004760,2240,1004760,2240,144,16,128,16,8,16,4,3,3387500,1120,3387500,1120,136,16,128,16,4,8,4,3,4171500,1120,4171500,1120,136,16,128,16,4,8,6,3,1038333,2240,1040576,2240,32,16,80,16,4,4,6,3,1038337,2240,1040580,2240,32,16,80,16,4,4,0,3,1049536,2240,1049536,2240,0,16,0,16,4,4,0,3,1049540,2240,1049540,2240,0,16,0,16,4,4,6,3,3395327,1120,3396448,1120,40,16,112,16,4,4,6,3,4179327,1120,4180448,1120,40,16,112,16,4,4,4,3,1040584,2240,1040584,2240,16,16,0,16,8,8,4,3,3396452,1120,3396452,1120,8,16,0,16,4,4,4,3,4180452,1120,4180452,1120,8,16,0,16,4,4,2,3,1056256,2240,1058496,2240,0,16,112,16,8,8,2,3,3399808,1120,3400928,1120,0,16,120,16,4,4,2,3,4183808,1120,4184928,1120,0,16,120,16,4,4,4,3,1058504,2240,1058504,2240,16,16,0,16,8,8,4,3,3400932,1120,3400932,1120,8,16,0,16,4,4,4,3,4184932,1120,4184932,1120,8,16,0,16,4,4,4,3,1040592,2240,1040592,2240,16,16,0,16,8,16,4,3,3396456,1120,3396456,1120,8,16,0,16,4,8,4,3,4180456,1120,4180456,1120,8,16,0,16,4,8,4,3,1040600,2240,1040600,2240,192,16,128,16,8,16,4,3,3396460,1120,3396460,1120,160,16,128,16,4,8,4,3,4180460,1120,4180460,1120,160,16,128,16,4,8,0,3,933088,2240,933088,2240,0,16,0,16,64,64,0,3,3369584,1120,3369584,1120,0,16,0,16,32,32,0,3,4153584,1120,4153584,1120,0,16,0,16,32,32,0,3,933152,2240,933152,2240,0,16,0,16,64,64,0,3,3369616,1120,3369616,1120,0,16,0,16,32,32,0,3,4153616,1120,4153616,1120,0,16,0,16,32,32,0,3,933216,2240,933216,2240,0,16,0,16,64,64,0,3,3369648,1120,3369648,1120,0,16,0,16,32,32,0,3,4153648,1120,4153648,1120,0,16,0,16,32,32,0,3,933280,2240,933280,2240,0,16,0,16,64,64,0,3,3369680,1120,3369680,1120,0,16,0,16,32,32,0,3,4153680,1120,4153680,1120,0,16,0,16,32,32,0,3,933344,2240,933344,2240,0,16,0,16,64,64,0,3,3369712,1120,3369712,1120,0,16,0,16,32,32,0,3,4153712,1120,4153712,1120,0,16,0,16,32,32,0,3,933408,2240,933408,2240,0,16,0,16,64,64,0,3,3369744,1120,3369744,1120,0,16,0,16,32,32,0,3,4153744,1120,4153744,1120,0,16,0,16,32,32,0,3,933472,2240,933472,2240,0,16,0,16,64,64,0,3,3369776,1120,3369776,1120,0,16,0,16,32,32,0,3,4153776,1120,4153776,1120,0,16,0,16,32,32,0,3,933536,2240,933536,2240,0,16,0,16,64,64,0,3,3369808,1120,3369808,1120,0,16,0,16,32,32,0,3,4153808,1120,4153808,1120,0,16,0,16,32,32,0,3,933600,2240,933600,2240,0,16,0,16,64,64,0,3,3369840,1120,3369840,1120,0,16,0,16,32,32,0,3,4153840,1120,4153840,1120,0,16,0,16,32,32,0,3,933664,2240,933664,2240,0,16,0,16,64,64,0,3,3369872,1120,3369872,1120,0,16,0,16,32,32,0,3,4153872,1120,4153872,1120,0,16,0,16,32,32,0,3,933728,2240,933728,2240,0,16,0,16,64,64,0,3,3369904,1120,3369904,1120,0,16,0,16,32,32,0,3,4153904,1120,4153904,1120,0,16,0,16,32,32,0,3,933792,2240,933792,2240,0,16,0,16,64,64,0,3,3369936,1120,3369936,1120,0,16,0,16,32,32,0,3,4153936,1120,4153936,1120,0,16,0,16,32,32,0,3,933856,2240,933856,2240,0,16,0,16,64,64,0,3,3369968,1120,3369968,1120,0,16,0,16,32,32,0,3,4153968,1120,4153968,1120,0,16,0,16,32,32,0,3,1075360,2240,1075360,2240,0,16,0,16,64,64,0,3,3404880,1120,3404880,1120,0,16,0,16,32,32,0,3,4188880,1120,4188880,1120,0,16,0,16,32,32,0,3,1075424,2240,1075424,2240,0,16,0,16,64,64,0,3,3404912,1120,3404912,1120,0,16,0,16,32,32,0,3,4188912,1120,4188912,1120,0,16,0,16,32,32,0,3,1075488,2240,1075488,2240,0,16,0,16,64,64,0,3,3404944,1120,3404944,1120,0,16,0,16,32,32,0,3,4188944,1120,4188944,1120,0,16,0,16,32,32,0,3,1075552,2240,1075552,2240,0,16,0,16,64,64,0,3,3404976,1120,3404976,1120,0,16,0,16,32,32,0,3,4188976,1120,4188976,1120,0,16,0,16,32,32,0,3,1075616,2240,1075616,2240,0,16,0,16,64,64,0,3,3405008,1120,3405008,1120,0,16,0,16,32,32,0,3,4189008,1120,4189008,1120,0,16,0,16,32,32,0,3,1075680,2240,1075680,2240,0,16,0,16,64,64,0,3,3405040,1120,3405040,1120,0,16,0,16,32,32,0,3,4189040,1120,4189040,1120,0,16,0,16,32,32,0,3,1075744,2240,1075744,2240,0,16,0,16,64,64,0,3,3405072,1120,3405072,1120,0,16,0,16,32,32,0,3,4189072,1120,4189072,1120,0,16,0,16,32,32,0,3,1075808,2240,1075808,2240,0,16,0,16,64,64,0,3,3405104,1120,3405104,1120,0,16,0,16,32,32,0,3,4189104,1120,4189104,1120,0,16,0,16,32,32,0,3,1075872,2240,1075872,2240,0,16,0,16,64,64,0,3,3405136,1120,3405136,1120,0,16,0,16,32,32,0,3,4189136,1120,4189136,1120,0,16,0,16,32,32,0,3,1075936,2240,1075936,2240,0,16,0,16,64,64,0,3,3405168,1120,3405168,1120,0,16,0,16,32,32,0,3,4189168,1120,4189168,1120,0,16,0,16,32,32,0,3,1076000,2240,1076000,2240,0,16,0,16,64,64,0,3,3405200,1120,3405200,1120,0,16,0,16,32,32,0,3,4189200,1120,4189200,1120,0,16,0,16,32,32,0,3,1076064,2240,1076064,2240,0,16,0,16,64,64,0,3,3405232,1120,3405232,1120,0,16,0,16,32,32,0,3,4189232,1120,4189232,1120,0,16,0,16,32,32,0,3,1076128,2240,1076128,2240,0,16,0,16,64,64,0,3,3405264,1120,3405264,1120,0,16,0,16,32,32,0,3,4189264,1120,4189264,1120,0,16,0,16,32,32,6,3,1073951,2240,1076192,2240,240,16,208,16,32,64,6,3,3404175,1120,3405296,1120,248,16,232,16,16,32,6,3,4188175,1120,4189296,1120,248,16,232,16,16,32,2,3,1073984,2240,1076224,2240,128,16,208,16,32,64,2,3,3404192,1120,3405312,1120,128,16,232,16,16,32,2,3,4188192,1120,4189312,1120,128,16,232,16,16,32,2,3,1074016,2240,1076256,2240,256,16,352,16,32,32,2,3,3404208,1120,3405328,1120,256,16,368,16,16,16,2,3,4188208,1120,4189328,1120,256,16,368,16,16,16,2,3,1074048,2240,1076288,2240,0,16,96,16,32,16,2,3,3404224,1120,3405344,1120,0,16,112,16,16,8,2,3,4188224,1120,4189344,1120,0,16,112,16,16,8,2,3,1109888,2240,1112128,2240,0,16,96,16,32,16,2,3,3413184,1120,3414304,1120,0,16,112,16,16,8,2,3,4197184,1120,4198304,1120,0,16,112,16,16,8,2,3,1145696,2240,1147936,2240,0,16,96,16,32,32,2,3,3422128,1120,3423248,1120,0,16,112,16,16,16,2,3,4206128,1120,4207248,1120,0,16,112,16,16,16,2,3,1145728,2240,1147968,2240,0,16,112,16,32,32,2,3,3422144,1120,3423264,1120,0,16,120,16,16,16,2,3,4206144,1120,4207264,1120,0,16,120,16,16,16,6,3,1074078,2240,1076320,2240,80,16,112,16,8,16,6,3,3404239,1120,3405360,1120,40,16,120,16,4,8,6,3,4188239,1120,4189360,1120,40,16,120,16,4,8,6,3,1074086,2240,1076328,2240,80,16,112,16,8,16,6,3,3404243,1120,3405364,1120,40,16,120,16,4,8,6,3,4188243,1120,4189364,1120,40,16,120,16,4,8,6,3,1074095,2240,1076336,2240,176,16,208,16,16,16,6,3,3404247,1120,3405368,1120,216,16,232,16,8,8,6,3,4188247,1120,4189368,1120,216,16,232,16,8,8,2,3,1109920,2240,1112160,2240,0,16,96,16,16,16,2,3,3413200,1120,3414320,1120,0,16,112,16,8,8,2,3,4197200,1120,4198320,1120,0,16,112,16,8,8,2,3,1109936,2240,1112176,2240,128,16,224,16,16,16,2,3,3413208,1120,3414328,1120,128,16,240,16,8,8,2,3,4197208,1120,4198328,1120,128,16,240,16,8,8,6,3,1074111,2240,1076352,2240,48,16,80,16,16,16,6,3,3404255,1120,3405376,1120,88,16,104,16,8,8,6,3,4188255,1120,4189376,1120,88,16,104,16,8,8,6,3,1074126,2240,1076368,2240,224,16,192,16,16,16,6,3,3404263,1120,3405384,1120,176,16,224,16,8,8,6,3,4188263,1120,4189384,1120,176,16,224,16,8,8,6,3,1109950,2240,1112192,2240,112,16,32,16,16,16,6,3,3413215,1120,3414336,1120,56,16,80,16,8,8,6,3,4197215,1120,4198336,1120,56,16,80,16,8,8,6,3,1109966,2240,1112208,2240,240,16,160,16,16,16,6,3,3413223,1120,3414344,1120,184,16,208,16,8,8,6,3,4197223,1120,4198344,1120,184,16,208,16,8,8,2,3,1145760,2240,1148000,2240,0,16,96,16,16,16,2,3,3422160,1120,3423280,1120,0,16,112,16,8,8,2,3,4206160,1120,4207280,1120,0,16,112,16,8,8,2,3,1145776,2240,1148016,2240,128,16,224,16,8,8,2,3,3422168,1120,3423288,1120,128,16,240,16,4,4,2,3,4206168,1120,4207288,1120,128,16,240,16,4,4,0,3,1148024,2240,1148024,2240,128,16,128,16,8,8,0,3,3423292,1120,3423292,1120,128,16,128,16,4,4,0,3,4207292,1120,4207292,1120,128,16,128,16,4,4,2,3,1163696,2240,1165936,2240,0,16,96,16,8,8,2,3,3426648,1120,3427768,1120,0,16,112,16,4,4,2,3,4210648,1120,4211768,1120,0,16,112,16,4,4,2,3,1161464,2240,1165944,2240,0,16,112,16,8,8,2,3,3426652,1120,3427772,1120,0,16,56,16,4,4,2,3,4210652,1120,4211772,1120,0,16,56,16,4,4,2,3,1181600,2240,1183840,2240,0,16,96,16,16,16,2,3,3431120,1120,3432240,1120,0,16,112,16,8,8,2,3,4215120,1120,4216240,1120,0,16,112,16,8,8,6,3,1181616,2240,1183856,2240,32,16,96,16,16,16,6,3,3431128,1120,3432248,1120,16,16,112,16,8,8,6,3,4215128,1120,4216248,1120,16,16,112,16,8,8,6,3,1145791,2240,1148032,2240,48,16,32,16,8,8,6,3,3422175,1120,3423296,1120,88,16,80,16,4,4,6,3,4206175,1120,4207296,1120,88,16,80,16,4,4,6,3,1145798,2240,1148040,2240,112,16,32,16,8,8,6,3,3422179,1120,3423300,1120,56,16,80,16,4,4,6,3,4206179,1120,4207300,1120,56,16,80,16,4,4,6,3,1161471,2240,1165952,2240,48,16,96,16,8,8,6,3,3426655,1120,3427776,1120,88,16,48,16,4,4,6,3,4210655,1120,4211776,1120,88,16,48,16,4,4,6,3,1163718,2240,1165960,2240,112,16,32,16,8,8,6,3,3426659,1120,3427780,1120,56,16,80,16,4,4,6,3,4210659,1120,4211780,1120,56,16,80,16,4,4,6,3,1145806,2240,1148048,2240,240,16,160,16,8,16,6,3,3422183,1120,3423304,1120,184,16,208,16,4,8,6,3,4206183,1120,4207304,1120,184,16,208,16,4,8,6,3,1145816,2240,1148056,2240,16,16,80,16,8,16,6,3,3422188,1120,3423308,1120,8,16,104,16,4,8,6,3,4206188,1120,4207308,1120,8,16,104,16,4,8,6,3,1181632,2240,1183872,2240,32,16,96,16,8,16,6,3,3431136,1120,3432256,1120,16,16,112,16,4,8,6,3,4215136,1120,4216256,1120,16,16,112,16,4,8,6,3,1183880,2240,1183880,2240,16,16,16,16,8,16,6,3,3432260,1120,3432260,1120,8,16,8,16,4,8,6,3,4216260,1120,4216260,1120,8,16,8,16,4,8,6,3,1183888,2240,1183888,2240,144,16,144,16,16,16,6,3,3432264,1120,3432264,1120,136,16,136,16,8,8,6,3,4216264,1120,4216264,1120,136,16,136,16,8,8,6,3,1074142,2240,1076384,2240,32,16,32,16,16,16,6,3,3404271,1120,3405392,1120,16,16,80,16,8,8,6,3,4188271,1120,4189392,1120,16,16,80,16,8,8,0,3,1076400,2240,1076400,2240,0,16,0,16,16,16,0,3,3405400,1120,3405400,1120,0,16,0,16,8,8,0,3,4189400,1120,4189400,1120,0,16,0,16,8,8,4,3,1109982,2240,1112224,2240,176,16,128,16,16,16,6,3,3413231,1120,3414352,1120,152,16,192,16,8,8,6,3,4197231,1120,4198352,1120,152,16,192,16,8,8,6,3,1109999,2240,1112240,2240,224,16,208,16,16,16,6,3,3413239,1120,3414360,1120,240,16,232,16,8,8,6,3,4197239,1120,4198360,1120,240,16,232,16,8,8,2,3,1074176,2240,1076416,2240,0,16,48,16,16,16,2,3,3404288,1120,3405408,1120,0,16,88,16,8,8,2,3,4188288,1120,4189408,1120,0,16,88,16,8,8,4,3,1076432,2240,1076432,2240,16,16,0,16,4,4,4,3,1076436,2240,1076436,2240,16,16,0,16,4,4,0,3,1085397,2240,1085392,2240,0,16,0,16,4,4,4,3,1085396,2240,1085396,2240,16,16,0,16,4,4,4,3,3405416,1120,3405416,1120,88,16,0,16,4,4,4,3,4189416,1120,4189416,1120,88,16,0,16,4,4,4,3,1076440,2240,1076440,2240,64,16,0,16,8,8,4,3,3405420,1120,3405420,1120,32,16,0,16,4,4,4,3,4189420,1120,4189420,1120,32,16,0,16,4,4,4,3,1094352,2240,1094352,2240,16,16,0,16,8,8,4,3,3409896,1120,3409896,1120,8,16,0,16,4,4,4,3,4193896,1120,4193896,1120,8,16,0,16,4,4,4,3,1094360,2240,1094360,2240,64,16,0,16,8,8,4,3,3409900,1120,3409900,1120,32,16,0,16,4,4,4,3,4193900,1120,4193900,1120,32,16,0,16,4,4,6,3,1110016,2240,1112256,2240,16,16,112,16,16,16,6,3,3413248,1120,3414368,1120,8,16,120,16,8,8,6,3,4197248,1120,4198368,1120,8,16,120,16,8,8,6,3,1110032,2240,1112272,2240,16,16,112,16,16,16,6,3,3413256,1120,3414376,1120,8,16,120,16,8,8,6,3,4197256,1120,4198376,1120,8,16,120,16,8,8,6,3,1145824,2240,1148064,2240,16,16,80,16,32,16,6,3,3422192,1120,3423312,1120,8,16,104,16,16,8,6,3,4206192,1120,4207312,1120,8,16,104,16,16,8,6,3,1183904,2240,1183904,2240,16,16,16,16,32,16,6,3,3432272,1120,3432272,1120,8,16,8,16,16,8,6,3,4216272,1120,4216272,1120,8,16,8,16,16,8,6,3,1145856,2240,1148096,2240,32,16,96,16,32,16,6,3,3422208,1120,3423328,1120,16,16,112,16,16,8,6,3,4206208,1120,4207328,1120,16,16,112,16,16,8,6,3,1183936,2240,1183936,2240,16,16,16,16,32,16,6,3,3432288,1120,3432288,1120,8,16,8,16,16,8,6,3,4216288,1120,4216288,1120,8,16,8,16,16,8,0,3,1076448,2240,1076448,2240,0,16,0,16,64,64,0,3,3405424,1120,3405424,1120,0,16,0,16,32,32,0,3,4189424,1120,4189424,1120,0,16,0,16,32,32,0,3,1076512,2240,1076512,2240,0,16,0,16,64,64,0,3,3405456,1120,3405456,1120,0,16,0,16,32,32,0,3,4189456,1120,4189456,1120,0,16,0,16,32,32,0,3,1076576,2240,1076576,2240,0,16,0,16,64,64,0,3,3405488,1120,3405488,1120,0,16,0,16,32,32,0,3,4189488,1120,4189488,1120,0,16,0,16,32,32,0,3,1076640,2240,1076640,2240,0,16,0,16,64,64,0,3,3405520,1120,3405520,1120,0,16,0,16,32,32,0,3,4189520,1120,4189520,1120,0,16,0,16,32,32,0,3,1076704,2240,1076704,2240,0,16,0,16,64,64,0,3,3405552,1120,3405552,1120,0,16,0,16,32,32,0,3,4189552,1120,4189552,1120,0,16,0,16,32,32,0,3,1076768,2240,1076768,2240,0,16,0,16,64,64,0,3,3405584,1120,3405584,1120,0,16,0,16,32,32,0,3,4189584,1120,4189584,1120,0,16,0,16,32,32,0,3,1076832,2240,1076832,2240,0,16,0,16,64,64,0,3,3405616,1120,3405616,1120,0,16,0,16,32,32,0,3,4189616,1120,4189616,1120,0,16,0,16,32,32,0,3,1076896,2240,1076896,2240,0,16,0,16,64,64,0,3,3405648,1120,3405648,1120,0,16,0,16,32,32,0,3,4189648,1120,4189648,1120,0,16,0,16,32,32,0,3,1076960,2240,1076960,2240,0,16,0,16,64,64,0,3,3405680,1120,3405680,1120,0,16,0,16,32,32,0,3,4189680,1120,4189680,1120,0,16,0,16,32,32,0,3,1077024,2240,1077024,2240,0,16,0,16,64,64,0,3,3405712,1120,3405712,1120,0,16,0,16,32,32,0,3,4189712,1120,4189712,1120,0,16,0,16,32,32,0,3,1077088,2240,1077088,2240,0,16,0,16,64,64,0,3,3405744,1120,3405744,1120,0,16,0,16,32,32,0,3,4189744,1120,4189744,1120,0,16,0,16,32,32,0,3,1077152,2240,1077152,2240,0,16,0,16,64,64,0,3,3405776,1120,3405776,1120,0,16,0,16,32,32,0,3,4189776,1120,4189776,1120,0,16,0,16,32,32,0,3,1077216,2240,1077216,2240,0,16,0,16,64,64,0,3,3405808,1120,3405808,1120,0,16,0,16,32,32,0,3,4189808,1120,4189808,1120,0,16,0,16,32,32,0,3,1218720,2240,1218720,2240,0,16,0,16,64,64,0,3,3440720,1120,3440720,1120,0,16,0,16,32,32,0,3,4224720,1120,4224720,1120,0,16,0,16,32,32,0,3,1218784,2240,1218784,2240,0,16,0,16,64,64,0,3,3440752,1120,3440752,1120,0,16,0,16,32,32,0,3,4224752,1120,4224752,1120,0,16,0,16,32,32,0,3,1218848,2240,1218848,2240,0,16,0,16,64,64,0,3,3440784,1120,3440784,1120,0,16,0,16,32,32,0,3,4224784,1120,4224784,1120,0,16,0,16,32,32,0,3,1218912,2240,1218912,2240,0,16,0,16,64,64,0,3,3440816,1120,3440816,1120,0,16,0,16,32,32,0,3,4224816,1120,4224816,1120,0,16,0,16,32,32,0,3,1218976,2240,1218976,2240,0,16,0,16,64,64,0,3,3440848,1120,3440848,1120,0,16,0,16,32,32,0,3,4224848,1120,4224848,1120,0,16,0,16,32,32,0,3,1219040,2240,1219040,2240,0,16,0,16,64,64,0,3,3440880,1120,3440880,1120,0,16,0,16,32,32,0,3,4224880,1120,4224880,1120,0,16,0,16,32,32,0,3,1219104,2240,1219104,2240,0,16,0,16,64,64,0,3,3440912,1120,3440912,1120,0,16,0,16,32,32,0,3,4224912,1120,4224912,1120,0,16,0,16,32,32,0,3,1219168,2240,1219168,2240,0,16,0,16,64,64,0,3,3440944,1120,3440944,1120,0,16,0,16,32,32,0,3,4224944,1120,4224944,1120,0,16,0,16,32,32,0,3,1219232,2240,1219232,2240,0,16,0,16,64,64,0,3,3440976,1120,3440976,1120,0,16,0,16,32,32,0,3,4224976,1120,4224976,1120,0,16,0,16,32,32,0,3,1219296,2240,1219296,2240,0,16,0,16,64,64,0,3,3441008,1120,3441008,1120,0,16,0,16,32,32,0,3,4225008,1120,4225008,1120,0,16,0,16,32,32,0,3,1219360,2240,1219360,2240,0,16,0,16,64,64,0,3,3441040,1120,3441040,1120,0,16,0,16,32,32,0,3,4225040,1120,4225040,1120,0,16,0,16,32,32,0,3,1219424,2240,1219424,2240,0,16,0,16,64,64,0,3,3441072,1120,3441072,1120,0,16,0,16,32,32,0,3,4225072,1120,4225072,1120,0,16,0,16,32,32,0,3,1219488,2240,1219488,2240,0,16,0,16,64,64,0,3,3441104,1120,3441104,1120,0,16,0,16,32,32,0,3,4225104,1120,4225104,1120,0,16,0,16,32,32,6,3,1217311,2240,1219552,2240,112,16,80,16,16,16,6,3,3440015,1120,3441136,1120,120,16,104,16,8,8,6,3,4224015,1120,4225136,1120,120,16,104,16,8,8,0,3,1219568,2240,1219568,2240,0,16,0,16,16,16,0,3,3441144,1120,3441144,1120,0,16,0,16,8,8,0,3,4225144,1120,4225144,1120,0,16,0,16,8,8,6,3,1253151,2240,1255392,2240,112,16,80,16,16,16,6,3,3448975,1120,3450096,1120,120,16,104,16,8,8,6,3,4232975,1120,4234096,1120,120,16,104,16,8,8,0,3,1255408,2240,1255408,2240,0,16,0,16,16,8,0,3,3450104,1120,3450104,1120,0,16,0,16,8,4,0,3,4234104,1120,4234104,1120,0,16,0,16,8,4,6,3,1271087,2240,1273328,2240,112,16,80,16,16,8,6,3,3453463,1120,3454584,1120,120,16,104,16,8,4,6,3,4237463,1120,4238584,1120,120,16,104,16,8,4,2,3,1217344,2240,1219584,2240,0,16,96,16,32,32,2,3,3440032,1120,3441152,1120,0,16,112,16,16,16,2,3,4224032,1120,4225152,1120,0,16,112,16,16,16,6,3,1288991,2240,1291232,2240,112,16,112,16,32,32,6,3,3457935,1120,3459056,1120,120,16,120,16,16,16,6,3,4241935,1120,4243056,1120,120,16,120,16,16,16,0,3,1291264,2240,1291264,2240,0,16,0,16,32,32,0,3,3459072,1120,3459072,1120,0,16,0,16,16,16,0,3,4243072,1120,4243072,1120,0,16,0,16,16,16,2,3,1217376,2240,1219616,2240,128,16,224,16,32,64,2,3,3440048,1120,3441168,1120,128,16,240,16,16,32,2,3,4224048,1120,4225168,1120,128,16,240,16,16,32,2,3,1219648,2240,1219648,2240,128,16,160,16,32,64,2,3,3441184,1120,3441184,1120,128,16,144,16,16,32,2,3,4225184,1120,4225184,1120,128,16,144,16,16,32,6,3,1217440,2240,1219680,2240,16,16,112,16,32,32,6,3,3440080,1120,3441200,1120,8,16,120,16,16,16,6,3,4224080,1120,4225200,1120,8,16,120,16,16,16,6,3,1217472,2240,1219712,2240,16,16,112,16,16,16,6,3,3440096,1120,3441216,1120,8,16,120,16,8,8,6,3,4224096,1120,4225216,1120,8,16,120,16,8,8,0,3,1219728,2240,1219728,2240,0,16,0,16,16,16,0,3,3441224,1120,3441224,1120,0,16,0,16,8,8,0,3,4225224,1120,4225224,1120,0,16,0,16,8,8,6,3,1253312,2240,1255552,2240,16,16,112,16,8,8,6,3,3449056,1120,3450176,1120,8,16,120,16,4,4,6,3,4233056,1120,4234176,1120,8,16,120,16,4,4,0,3,1255560,2240,1255560,2240,0,16,0,16,8,8,0,3,3450180,1120,3450180,1120,0,16,0,16,4,4,0,3,4234180,1120,4234180,1120,0,16,0,16,4,4,0,3,1273472,2240,1273472,2240,0,16,0,16,8,8,0,3,3454656,1120,3454656,1120,0,16,0,16,4,4,0,3,4238656,1120,4238656,1120,0,16,0,16,4,4,0,3,1273480,2240,1273480,2240,0,16,0,16,8,8,0,3,3454660,1120,3454660,1120,0,16,0,16,4,4,0,3,4238660,1120,4238660,1120,0,16,0,16,4,4,0,3,1255568,2240,1255568,2240,0,16,0,16,16,16,0,3,3450184,1120,3450184,1120,0,16,0,16,8,8,0,3,4234184,1120,4234184,1120,0,16,0,16,8,8,0,3,1291360,2240,1291360,2240,0,16,0,16,32,32,0,3,3459120,1120,3459120,1120,0,16,0,16,16,16,0,3,4243120,1120,4243120,1120,0,16,0,16,16,16,6,3,1289152,2240,1291392,2240,16,16,112,16,16,16,6,3,3458016,1120,3459136,1120,8,16,120,16,8,8,6,3,4242016,1120,4243136,1120,8,16,120,16,8,8,4,3,1291408,2240,1291408,2240,16,16,0,16,16,16,4,3,3459144,1120,3459144,1120,8,16,0,16,8,8,4,3,4243144,1120,4243144,1120,8,16,0,16,8,8,6,3,1324992,2240,1327232,2240,16,16,112,16,16,16,6,3,3466976,1120,3468096,1120,8,16,120,16,8,8,6,3,4250976,1120,4252096,1120,8,16,120,16,8,8,4,3,1327248,2240,1327248,2240,32,16,0,16,16,16,4,3,3468104,1120,3468104,1120,16,16,0,16,8,8,4,3,4252104,1120,4252104,1120,16,16,0,16,8,8,6,3,1217504,2240,1219744,2240,32,16,112,16,32,32,6,3,3440112,1120,3441232,1120,16,16,120,16,16,16,6,3,4224112,1120,4225232,1120,16,16,120,16,16,16,6,3,1217536,2240,1219776,2240,32,16,112,16,32,16,6,3,3440128,1120,3441248,1120,16,16,120,16,16,8,6,3,4224128,1120,4225248,1120,16,16,120,16,16,8,6,3,1255616,2240,1255616,2240,16,16,16,16,32,16,6,3,3450208,1120,3450208,1120,8,16,8,16,16,8,6,3,4234208,1120,4234208,1120,8,16,8,16,16,8,4,3,1291424,2240,1291424,2240,16,16,0,16,32,32,4,3,3459152,1120,3459152,1120,8,16,0,16,16,16,4,3,4243152,1120,4243152,1120,8,16,0,16,16,16,6,3,1289216,2240,1291456,2240,32,16,112,16,16,16,6,3,3458048,1120,3459168,1120,16,16,120,16,8,8,6,3,4242048,1120,4243168,1120,16,16,120,16,8,8,4,3,1291472,2240,1291472,2240,16,16,0,16,16,16,4,3,3459176,1120,3459176,1120,8,16,0,16,8,8,4,3,4243176,1120,4243176,1120,8,16,0,16,8,8,4,3,1327296,2240,1327296,2240,16,16,0,16,16,16,4,3,3468128,1120,3468128,1120,8,16,0,16,8,8,4,3,4252128,1120,4252128,1120,8,16,0,16,8,8,6,3,1325072,2240,1327312,2240,32,16,112,16,16,16,6,3,3467016,1120,3468136,1120,16,16,120,16,8,8,6,3,4251016,1120,4252136,1120,16,16,120,16,8,8,0,3,1219808,2240,1219808,2240,0,16,0,16,64,64,0,3,3441264,1120,3441264,1120,0,16,0,16,32,32,0,3,4225264,1120,4225264,1120,0,16,0,16,32,32,0,3,1219872,2240,1219872,2240,0,16,0,16,64,64,0,3,3441296,1120,3441296,1120,0,16,0,16,32,32,0,3,4225296,1120,4225296,1120,0,16,0,16,32,32,0,3,1219936,2240,1219936,2240,0,16,0,16,64,64,0,3,3441328,1120,3441328,1120,0,16,0,16,32,32,0,3,4225328,1120,4225328,1120,0,16,0,16,32,32,0,3,1220000,2240,1220000,2240,0,16,0,16,64,64,0,3,3441360,1120,3441360,1120,0,16,0,16,32,32,0,3,4225360,1120,4225360,1120,0,16,0,16,32,32,0,3,1220064,2240,1220064,2240,0,16,0,16,64,64,0,3,3441392,1120,3441392,1120,0,16,0,16,32,32,0,3,4225392,1120,4225392,1120,0,16,0,16,32,32,0,3,1220128,2240,1220128,2240,0,16,0,16,64,64,0,3,3441424,1120,3441424,1120,0,16,0,16,32,32,0,3,4225424,1120,4225424,1120,0,16,0,16,32,32,0,3,1220192,2240,1220192,2240,0,16,0,16,64,64,0,3,3441456,1120,3441456,1120,0,16,0,16,32,32,0,3,4225456,1120,4225456,1120,0,16,0,16,32,32,0,3,1220256,2240,1220256,2240,0,16,0,16,64,64,0,3,3441488,1120,3441488,1120,0,16,0,16,32,32,0,3,4225488,1120,4225488,1120,0,16,0,16,32,32,0,3,1220320,2240,1220320,2240,0,16,0,16,64,64,0,3,3441520,1120,3441520,1120,0,16,0,16,32,32,0,3,4225520,1120,4225520,1120,0,16,0,16,32,32,0,3,1220384,2240,1220384,2240,0,16,0,16,64,64,0,3,3441552,1120,3441552,1120,0,16,0,16,32,32,0,3,4225552,1120,4225552,1120,0,16,0,16,32,32,0,3,1220448,2240,1220448,2240,0,16,0,16,64,64,0,3,3441584,1120,3441584,1120,0,16,0,16,32,32,0,3,4225584,1120,4225584,1120,0,16,0,16,32,32,0,3,1220512,2240,1220512,2240,0,16,0,16,64,64,0,3,3441616,1120,3441616,1120,0,16,0,16,32,32,0,3,4225616,1120,4225616,1120,0,16,0,16,32,32,0,3,1220576,2240,1220576,2240,0,16,0,16,64,64,0,3,3441648,1120,3441648,1120,0,16,0,16,32,32,0,3,4225648,1120,4225648,1120,0,16,0,16,32,32,0,3,1362080,2240,1362080,2240,0,16,0,16,64,64,0,3,3476560,1120,3476560,1120,0,16,0,16,32,32,0,3,4260560,1120,4260560,1120,0,16,0,16,32,32,0,3,1362144,2240,1362144,2240,0,16,0,16,64,64,0,3,3476592,1120,3476592,1120,0,16,0,16,32,32,0,3,4260592,1120,4260592,1120,0,16,0,16,32,32,0,3,1362208,2240,1362208,2240,0,16,0,16,64,64,0,3,3476624,1120,3476624,1120,0,16,0,16,32,32,0,3,4260624,1120,4260624,1120,0,16,0,16,32,32,0,3,1362272,2240,1362272,2240,0,16,0,16,64,64,0,3,3476656,1120,3476656,1120,0,16,0,16,32,32,0,3,4260656,1120,4260656,1120,0,16,0,16,32,32,0,3,1362336,2240,1362336,2240,0,16,0,16,64,64,0,3,3476688,1120,3476688,1120,0,16,0,16,32,32,0,3,4260688,1120,4260688,1120,0,16,0,16,32,32,0,3,1362400,2240,1362400,2240,0,16,0,16,64,64,0,3,3476720,1120,3476720,1120,0,16,0,16,32,32,0,3,4260720,1120,4260720,1120,0,16,0,16,32,32,0,3,1362464,2240,1362464,2240,0,16,0,16,64,64,0,3,3476752,1120,3476752,1120,0,16,0,16,32,32,0,3,4260752,1120,4260752,1120,0,16,0,16,32,32,0,3,1362528,2240,1362528,2240,0,16,0,16,64,64,0,3,3476784,1120,3476784,1120,0,16,0,16,32,32,0,3,4260784,1120,4260784,1120,0,16,0,16,32,32,0,3,1362592,2240,1362592,2240,0,16,0,16,64,64,0,3,3476816,1120,3476816,1120,0,16,0,16,32,32,0,3,4260816,1120,4260816,1120,0,16,0,16,32,32,0,3,1362656,2240,1362656,2240,0,16,0,16,64,64,0,3,3476848,1120,3476848,1120,0,16,0,16,32,32,0,3,4260848,1120,4260848,1120,0,16,0,16,32,32,0,3,1362720,2240,1362720,2240,0,16,0,16,64,64,0,3,3476880,1120,3476880,1120,0,16,0,16,32,32,0,3,4260880,1120,4260880,1120,0,16,0,16,32,32,0,3,1362784,2240,1362784,2240,0,16,0,16,64,64,0,3,3476912,1120,3476912,1120,0,16,0,16,32,32,0,3,4260912,1120,4260912,1120,0,16,0,16,32,32,0,3,1362848,2240,1362848,2240,0,16,0,16,64,64,0,3,3476944,1120,3476944,1120,0,16,0,16,32,32,0,3,4260944,1120,4260944,1120,0,16,0,16,32,32,6,3,1367391,2240,1362912,2240,112,16,48,16,32,16,6,3,3478095,1120,3476976,1120,120,16,24,16,16,8,6,3,4262095,1120,4260976,1120,120,16,24,16,16,8,0,3,1398752,2240,1398752,2240,0,16,0,16,32,16,0,3,3485936,1120,3485936,1120,0,16,0,16,16,8,0,3,4269936,1120,4269936,1120,0,16,0,16,16,8,0,3,1362944,2240,1362944,2240,0,16,0,16,32,32,0,3,3476992,1120,3476992,1120,0,16,0,16,16,16,0,3,4260992,1120,4260992,1120,0,16,0,16,16,16,0,3,1434592,2240,1434592,2240,0,16,0,16,32,32,0,3,3494896,1120,3494896,1120,0,16,0,16,16,16,0,3,4278896,1120,4278896,1120,0,16,0,16,16,16,0,3,1434624,2240,1434624,2240,0,16,0,16,32,32,0,3,3494912,1120,3494912,1120,0,16,0,16,16,16,0,3,4278912,1120,4278912,1120,0,16,0,16,16,16,0,3,1362976,2240,1362976,2240,0,16,0,16,64,64,0,3,3477008,1120,3477008,1120,0,16,0,16,32,32,0,3,4261008,1120,4261008,1120,0,16,0,16,32,32,4,3,1363040,2240,1363040,2240,16,16,0,16,64,64,4,3,3477040,1120,3477040,1120,8,16,0,16,32,32,4,3,4261040,1120,4261040,1120,8,16,0,16,32,32,4,3,1363104,2240,1363104,2240,32,16,0,16,32,32,4,3,3477072,1120,3477072,1120,16,16,0,16,16,16,4,3,4261072,1120,4261072,1120,16,16,0,16,16,16,4,3,1363136,2240,1363136,2240,32,16,0,16,32,32,4,3,3477088,1120,3477088,1120,16,16,0,16,16,16,4,3,4261088,1120,4261088,1120,16,16,0,16,16,16,4,3,1434784,2240,1434784,2240,144,16,128,16,32,32,4,3,3494992,1120,3494992,1120,136,16,128,16,16,16,4,3,4278992,1120,4278992,1120,136,16,128,16,16,16,4,3,1434816,2240,1434816,2240,160,16,128,16,32,32,4,3,3495008,1120,3495008,1120,144,16,128,16,16,16,4,3,4279008,1120,4279008,1120,144,16,128,16,16,16,6,3,1363168,2240,1363168,2240,160,16,240,16,32,32,6,3,3477104,1120,3477104,1120,144,16,184,16,16,16,6,3,4261104,1120,4261104,1120,144,16,184,16,16,16,0,3,1363200,2240,1363200,2240,0,16,0,16,32,32,0,3,3477120,1120,3477120,1120,0,16,0,16,16,16,0,3,4261120,1120,4261120,1120,0,16,0,16,16,16,4,3,1432608,2240,1434848,2240,176,16,128,16,32,32,6,3,3493904,1120,3495024,1120,152,16,192,16,16,16,6,3,4277904,1120,4279024,1120,152,16,192,16,16,16,0,3,1434880,2240,1434880,2240,0,16,0,16,32,32,0,3,3495040,1120,3495040,1120,0,16,0,16,16,16,0,3,4279040,1120,4279040,1120,0,16,0,16,16,16,0,3,1363232,2240,1363232,2240,0,16,0,16,64,64,0,3,3477136,1120,3477136,1120,0,16,0,16,32,32,0,3,4261136,1120,4261136,1120,0,16,0,16,32,32,0,3,1363296,2240,1363296,2240,0,16,0,16,64,64,0,3,3477168,1120,3477168,1120,0,16,0,16,32,32,0,3,4261168,1120,4261168,1120,0,16,0,16,32,32,0,3,1363360,2240,1363360,2240,0,16,0,16,64,64,0,3,3477200,1120,3477200,1120,0,16,0,16,32,32,0,3,4261200,1120,4261200,1120,0,16,0,16,32,32,0,3,1363424,2240,1363424,2240,0,16,0,16,64,64,0,3,3477232,1120,3477232,1120,0,16,0,16,32,32,0,3,4261232,1120,4261232,1120,0,16,0,16,32,32,0,3,1363488,2240,1363488,2240,0,16,0,16,64,64,0,3,3477264,1120,3477264,1120,0,16,0,16,32,32,0,3,4261264,1120,4261264,1120,0,16,0,16,32,32,0,3,1363552,2240,1363552,2240,0,16,0,16,64,64,0,3,3477296,1120,3477296,1120,0,16,0,16,32,32,0,3,4261296,1120,4261296,1120,0,16,0,16,32,32,0,3,1363616,2240,1363616,2240,0,16,0,16,64,64,0,3,3477328,1120,3477328,1120,0,16,0,16,32,32,0,3,4261328,1120,4261328,1120,0,16,0,16,32,32,0,3,1363680,2240,1363680,2240,0,16,0,16,64,64,0,3,3477360,1120,3477360,1120,0,16,0,16,32,32,0,3,4261360,1120,4261360,1120,0,16,0,16,32,32,0,3,1363744,2240,1363744,2240,0,16,0,16,64,64,0,3,3477392,1120,3477392,1120,0,16,0,16,32,32,0,3,4261392,1120,4261392,1120,0,16,0,16,32,32,0,3,1363808,2240,1363808,2240,0,16,0,16,64,64,0,3,3477424,1120,3477424,1120,0,16,0,16,32,32,0,3,4261424,1120,4261424,1120,0,16,0,16,32,32,0,3,1363872,2240,1363872,2240,0,16,0,16,64,64,0,3,3477456,1120,3477456,1120,0,16,0,16,32,32,0,3,4261456,1120,4261456,1120,0,16,0,16,32,32,0,3,1363936,2240,1363936,2240,0,16,0,16,64,64,0,3,3477488,1120,3477488,1120,0,16,0,16,32,32,0,3,4261488,1120,4261488,1120,0,16,0,16,32,32,0,3,1505440,2240,1505440,2240,0,16,0,16,64,64,0,3,3512400,1120,3512400,1120,0,16,0,16,32,32,0,3,4296400,1120,4296400,1120,0,16,0,16,32,32,0,3,1505504,2240,1505504,2240,0,16,0,16,64,64,0,3,3512432,1120,3512432,1120,0,16,0,16,32,32,0,3,4296432,1120,4296432,1120,0,16,0,16,32,32,0,3,1505568,2240,1505568,2240,0,16,0,16,64,64,0,3,3512464,1120,3512464,1120,0,16,0,16,32,32,0,3,4296464,1120,4296464,1120,0,16,0,16,32,32,0,3,1505632,2240,1505632,2240,0,16,0,16,64,64,0,3,3512496,1120,3512496,1120,0,16,0,16,32,32,0,3,4296496,1120,4296496,1120,0,16,0,16,32,32,0,3,1505696,2240,1505696,2240,0,16,0,16,64,64,0,3,3512528,1120,3512528,1120,0,16,0,16,32,32,0,3,4296528,1120,4296528,1120,0,16,0,16,32,32,0,3,1505760,2240,1505760,2240,0,16,0,16,64,64,0,3,3512560,1120,3512560,1120,0,16,0,16,32,32,0,3,4296560,1120,4296560,1120,0,16,0,16,32,32,0,3,1505824,2240,1505824,2240,0,16,0,16,64,64,0,3,3512592,1120,3512592,1120,0,16,0,16,32,32,0,3,4296592,1120,4296592,1120,0,16,0,16,32,32,0,3,1505888,2240,1505888,2240,0,16,0,16,64,64,0,3,3512624,1120,3512624,1120,0,16,0,16,32,32,0,3,4296624,1120,4296624,1120,0,16,0,16,32,32,0,3,1505952,2240,1505952,2240,0,16,0,16,64,64,0,3,3512656,1120,3512656,1120,0,16,0,16,32,32,0,3,4296656,1120,4296656,1120,0,16,0,16,32,32,0,3,1506016,2240,1506016,2240,0,16,0,16,64,64,0,3,3512688,1120,3512688,1120,0,16,0,16,32,32,0,3,4296688,1120,4296688,1120,0,16,0,16,32,32,0,3,1506080,2240,1506080,2240,0,16,0,16,64,64,0,3,3512720,1120,3512720,1120,0,16,0,16,32,32,0,3,4296720,1120,4296720,1120,0,16,0,16,32,32,0,3,1506144,2240,1506144,2240,0,16,0,16,64,64,0,3,3512752,1120,3512752,1120,0,16,0,16,32,32,0,3,4296752,1120,4296752,1120,0,16,0,16,32,32,0,3,1506208,2240,1506208,2240,0,16,0,16,64,64,0,3,3512784,1120,3512784,1120,0,16,0,16,32,32,0,3,4296784,1120,4296784,1120,0,16,0,16,32,32,0,3,1506272,2240,1506272,2240,0,16,0,16,64,64,0,3,3512816,1120,3512816,1120,0,16,0,16,32,32,0,3,4296816,1120,4296816,1120,0,16,0,16,32,32,0,3,1506336,2240,1506336,2240,0,16,0,16,64,64,0,3,3512848,1120,3512848,1120,0,16,0,16,32,32,0,3,4296848,1120,4296848,1120,0,16,0,16,32,32,4,3,1506400,2240,1506400,2240,144,16,128,16,64,64,4,3,3512880,1120,3512880,1120,136,16,128,16,32,32,4,3,4296880,1120,4296880,1120,136,16,128,16,32,32,4,3,1506464,2240,1506464,2240,144,16,128,16,64,64,4,3,3512912,1120,3512912,1120,136,16,128,16,32,32,4,3,4296912,1120,4296912,1120,136,16,128,16,32,32,6,3,1506528,2240,1506528,2240,176,16,160,16,64,64,6,3,3512944,1120,3512944,1120,152,16,144,16,32,32,6,3,4296944,1120,4296944,1120,152,16,144,16,32,32,0,3,1506592,2240,1506592,2240,0,16,0,16,64,64,0,3,3512976,1120,3512976,1120,0,16,0,16,32,32,0,3,4296976,1120,4296976,1120,0,16,0,16,32,32,0,3,1506656,2240,1506656,2240,0,16,0,16,64,64,0,3,3513008,1120,3513008,1120,0,16,0,16,32,32,0,3,4297008,1120,4297008,1120,0,16,0,16,32,32,0,3,1506720,2240,1506720,2240,0,16,0,16,64,64,0,3,3513040,1120,3513040,1120,0,16,0,16,32,32,0,3,4297040,1120,4297040,1120,0,16,0,16,32,32,0,3,1506784,2240,1506784,2240,0,16,0,16,64,64,0,3,3513072,1120,3513072,1120,0,16,0,16,32,32,0,3,4297072,1120,4297072,1120,0,16,0,16,32,32,0,3,1506848,2240,1506848,2240,0,16,0,16,64,64,0,3,3513104,1120,3513104,1120,0,16,0,16,32,32,0,3,4297104,1120,4297104,1120,0,16,0,16,32,32,0,3,1506912,2240,1506912,2240,0,16,0,16,64,64,0,3,3513136,1120,3513136,1120,0,16,0,16,32,32,0,3,4297136,1120,4297136,1120,0,16,0,16,32,32,0,3,1506976,2240,1506976,2240,0,16,0,16,64,64,0,3,3513168,1120,3513168,1120,0,16,0,16,32,32,0,3,4297168,1120,4297168,1120,0,16,0,16,32,32,0,3,1507040,2240,1507040,2240,0,16,0,16,64,64,0,3,3513200,1120,3513200,1120,0,16,0,16,32,32,0,3,4297200,1120,4297200,1120,0,16,0,16,32,32,0,3,1507104,2240,1507104,2240,0,16,0,16,64,64,0,3,3513232,1120,3513232,1120,0,16,0,16,32,32,0,3,4297232,1120,4297232,1120,0,16,0,16,32,32,0,3,1507168,2240,1507168,2240,0,16,0,16,64,64,0,3,3513264,1120,3513264,1120,0,16,0,16,32,32,0,3,4297264,1120,4297264,1120,0,16,0,16,32,32,0,3,1507232,2240,1507232,2240,0,16,0,16,64,64,0,3,3513296,1120,3513296,1120,0,16,0,16,32,32,0,3,4297296,1120,4297296,1120,0,16,0,16,32,32,0,3,1507296,2240,1507296,2240,0,16,0,16,64,64,0,3,3513328,1120,3513328,1120,0,16,0,16,32,32,0,3,4297328,1120,4297328,1120,0,16,0,16,32,32,0,3,1648800,2240,1648800,2240,0,16,0,16,64,64,0,3,3548240,1120,3548240,1120,0,16,0,16,32,32,0,3,4332240,1120,4332240,1120,0,16,0,16,32,32,0,3,1648864,2240,1648864,2240,0,16,0,16,64,64,0,3,3548272,1120,3548272,1120,0,16,0,16,32,32,0,3,4332272,1120,4332272,1120,0,16,0,16,32,32,0,3,1648928,2240,1648928,2240,0,16,0,16,64,64,0,3,3548304,1120,3548304,1120,0,16,0,16,32,32,0,3,4332304,1120,4332304,1120,0,16,0,16,32,32,0,3,1648992,2240,1648992,2240,0,16,0,16,64,64,0,3,3548336,1120,3548336,1120,0,16,0,16,32,32,0,3,4332336,1120,4332336,1120,0,16,0,16,32,32,0,3,1649056,2240,1649056,2240,0,16,0,16,64,64,0,3,3548368,1120,3548368,1120,0,16,0,16,32,32,0,3,4332368,1120,4332368,1120,0,16,0,16,32,32,0,3,1649120,2240,1649120,2240,0,16,0,16,64,64,0,3,3548400,1120,3548400,1120,0,16,0,16,32,32,0,3,4332400,1120,4332400,1120,0,16,0,16,32,32,0,3,1649184,2240,1649184,2240,0,16,0,16,64,64,0,3,3548432,1120,3548432,1120,0,16,0,16,32,32,0,3,4332432,1120,4332432,1120,0,16,0,16,32,32,0,3,1649248,2240,1649248,2240,0,16,0,16,64,64,0,3,3548464,1120,3548464,1120,0,16,0,16,32,32,0,3,4332464,1120,4332464,1120,0,16,0,16,32,32,0,3,1649312,2240,1649312,2240,0,16,0,16,64,64,0,3,3548496,1120,3548496,1120,0,16,0,16,32,32,0,3,4332496,1120,4332496,1120,0,16,0,16,32,32,0,3,1649376,2240,1649376,2240,0,16,0,16,64,64,0,3,3548528,1120,3548528,1120,0,16,0,16,32,32,0,3,4332528,1120,4332528,1120,0,16,0,16,32,32,0,3,1649440,2240,1649440,2240,0,16,0,16,64,64,0,3,3548560,1120,3548560,1120,0,16,0,16,32,32,0,3,4332560,1120,4332560,1120,0,16,0,16,32,32,0,3,1649504,2240,1649504,2240,0,16,0,16,64,64,0,3,3548592,1120,3548592,1120,0,16,0,16,32,32,0,3,4332592,1120,4332592,1120,0,16,0,16,32,32,0,3,1649568,2240,1649568,2240,0,16,0,16,64,64,0,3,3548624,1120,3548624,1120,0,16,0,16,32,32,0,3,4332624,1120,4332624,1120,0,16,0,16,32,32,0,3,1649632,2240,1649632,2240,0,16,0,16,64,64,0,3,3548656,1120,3548656,1120,0,16,0,16,32,32,0,3,4332656,1120,4332656,1120,0,16,0,16,32,32,0,3,1649696,2240,1649696,2240,0,16,0,16,64,64,0,3,3548688,1120,3548688,1120,0,16,0,16,32,32,0,3,4332688,1120,4332688,1120,0,16,0,16,32,32,4,3,1649760,2240,1649760,2240,16,16,0,16,64,64,4,3,3548720,1120,3548720,1120,8,16,0,16,32,32,4,3,4332720,1120,4332720,1120,8,16,0,16,32,32,4,3,1649824,2240,1649824,2240,16,16,0,16,64,64,4,3,3548752,1120,3548752,1120,8,16,0,16,32,32,4,3,4332752,1120,4332752,1120,8,16,0,16,32,32,6,3,1649888,2240,1649888,2240,176,16,160,16,64,64,6,3,3548784,1120,3548784,1120,152,16,144,16,32,32,6,3,4332784,1120,4332784,1120,152,16,144,16,32,32,0,3,1649952,2240,1649952,2240,0,16,0,16,64,64,0,3,3548816,1120,3548816,1120,0,16,0,16,32,32,0,3,4332816,1120,4332816,1120,0,16,0,16,32,32,0,3,1650016,2240,1650016,2240,0,16,0,16,64,64,0,3,3548848,1120,3548848,1120,0,16,0,16,32,32,0,3,4332848,1120,4332848,1120,0,16,0,16,32,32,0,3,1650080,2240,1650080,2240,0,16,0,16,64,64,0,3,3548880,1120,3548880,1120,0,16,0,16,32,32,0,3,4332880,1120,4332880,1120,0,16,0,16,32,32,0,3,1650144,2240,1650144,2240,0,16,0,16,64,64,0,3,3548912,1120,3548912,1120,0,16,0,16,32,32,0,3,4332912,1120,4332912,1120,0,16,0,16,32,32,0,3,1650208,2240,1650208,2240,0,16,0,16,64,64,0,3,3548944,1120,3548944,1120,0,16,0,16,32,32,0,3,4332944,1120,4332944,1120,0,16,0,16,32,32,0,3,1650272,2240,1650272,2240,0,16,0,16,64,64,0,3,3548976,1120,3548976,1120,0,16,0,16,32,32,0,3,4332976,1120,4332976,1120,0,16,0,16,32,32,0,3,1650336,2240,1650336,2240,0,16,0,16,64,64,0,3,3549008,1120,3549008,1120,0,16,0,16,32,32,0,3,4333008,1120,4333008,1120,0,16,0,16,32,32,0,3,1650400,2240,1650400,2240,0,16,0,16,64,64,0,3,3549040,1120,3549040,1120,0,16,0,16,32,32,0,3,4333040,1120,4333040,1120,0,16,0,16,32,32,0,3,1650464,2240,1650464,2240,0,16,0,16,64,64,0,3,3549072,1120,3549072,1120,0,16,0,16,32,32,0,3,4333072,1120,4333072,1120,0,16,0,16,32,32,0,3,1650528,2240,1650528,2240,0,16,0,16,64,64,0,3,3549104,1120,3549104,1120,0,16,0,16,32,32,0,3,4333104,1120,4333104,1120,0,16,0,16,32,32,0,3,1650592,2240,1650592,2240,0,16,0,16,64,64,0,3,3549136,1120,3549136,1120,0,16,0,16,32,32,0,3,4333136,1120,4333136,1120,0,16,0,16,32,32,0,3,1650656,2240,1650656,2240,0,16,0,16,64,64,0,3,3549168,1120,3549168,1120,0,16,0,16,32,32,0,3,4333168,1120,4333168,1120,0,16,0,16,32,32,0,3,1792160,2240,1792160,2240,0,16,0,16,64,64,0,3,3584080,1120,3584080,1120,0,16,0,16,32,32,0,3,4368080,1120,4368080,1120,0,16,0,16,32,32,0,3,1792224,2240,1792224,2240,0,16,0,16,64,64,0,3,3584112,1120,3584112,1120,0,16,0,16,32,32,0,3,4368112,1120,4368112,1120,0,16,0,16,32,32,0,3,1792288,2240,1792288,2240,0,16,0,16,64,64,0,3,3584144,1120,3584144,1120,0,16,0,16,32,32,0,3,4368144,1120,4368144,1120,0,16,0,16,32,32,0,3,1792352,2240,1792352,2240,0,16,0,16,64,64,0,3,3584176,1120,3584176,1120,0,16,0,16,32,32,0,3,4368176,1120,4368176,1120,0,16,0,16,32,32,0,3,1792416,2240,1792416,2240,0,16,0,16,64,64,0,3,3584208,1120,3584208,1120,0,16,0,16,32,32,0,3,4368208,1120,4368208,1120,0,16,0,16,32,32,0,3,1792480,2240,1792480,2240,0,16,0,16,64,64,0,3,3584240,1120,3584240,1120,0,16,0,16,32,32,0,3,4368240,1120,4368240,1120,0,16,0,16,32,32,0,3,1792544,2240,1792544,2240,0,16,0,16,64,64,0,3,3584272,1120,3584272,1120,0,16,0,16,32,32,0,3,4368272,1120,4368272,1120,0,16,0,16,32,32,0,3,1792608,2240,1792608,2240,0,16,0,16,64,64,0,3,3584304,1120,3584304,1120,0,16,0,16,32,32,0,3,4368304,1120,4368304,1120,0,16,0,16,32,32,0,3,1792672,2240,1792672,2240,0,16,0,16,64,64,0,3,3584336,1120,3584336,1120,0,16,0,16,32,32,0,3,4368336,1120,4368336,1120,0,16,0,16,32,32,0,3,1792736,2240,1792736,2240,0,16,0,16,64,64,0,3,3584368,1120,3584368,1120,0,16,0,16,32,32,0,3,4368368,1120,4368368,1120,0,16,0,16,32,32,0,3,1792800,2240,1792800,2240,0,16,0,16,64,64,0,3,3584400,1120,3584400,1120,0,16,0,16,32,32,0,3,4368400,1120,4368400,1120,0,16,0,16,32,32,0,3,1792864,2240,1792864,2240,0,16,0,16,64,64,0,3,3584432,1120,3584432,1120,0,16,0,16,32,32,0,3,4368432,1120,4368432,1120,0,16,0,16,32,32,0,3,1792928,2240,1792928,2240,0,16,0,16,64,64,0,3,3584464,1120,3584464,1120,0,16,0,16,32,32,0,3,4368464,1120,4368464,1120,0,16,0,16,32,32,6,3,1792991,2240,1792992,2240,112,16,64,16,64,64,6,3,3584495,1120,3584496,1120,120,16,32,16,32,32,6,3,4368495,1120,4368496,1120,120,16,32,16,32,32,0,3,1793056,2240,1793056,2240,0,16,0,16,32,32,0,3,3584528,1120,3584528,1120,0,16,0,16,16,16,0,3,4368528,1120,4368528,1120,0,16,0,16,16,16,0,3,1793088,2240,1793088,2240,0,16,0,16,32,32,0,3,3584544,1120,3584544,1120,0,16,0,16,16,16,0,3,4368544,1120,4368544,1120,0,16,0,16,16,16,6,3,1864735,2240,1864736,2240,112,16,64,16,16,32,6,3,3602447,1120,3602448,1120,120,16,32,16,8,16,6,3,4386447,1120,4386448,1120,120,16,32,16,8,16,2,3,1864752,2240,1864752,2240,0,16,64,16,16,32,2,3,3602456,1120,3602456,1120,0,16,32,16,8,16,2,3,4386456,1120,4386456,1120,0,16,32,16,8,16,2,3,1864768,2240,1864768,2240,0,16,64,16,32,32,2,3,3602464,1120,3602464,1120,0,16,32,16,16,16,2,3,4386464,1120,4386464,1120,0,16,32,16,16,16,4,3,1793120,2240,1793120,2240,16,16,0,16,32,32,4,3,3584560,1120,3584560,1120,8,16,0,16,16,16,4,3,4368560,1120,4368560,1120,8,16,0,16,16,16,4,3,1793152,2240,1793152,2240,16,16,0,16,32,32,4,3,3584576,1120,3584576,1120,8,16,0,16,16,16,4,3,4368576,1120,4368576,1120,8,16,0,16,16,16,2,3,1864800,2240,1864800,2240,0,16,64,16,32,32,2,3,3602480,1120,3602480,1120,0,16,32,16,16,16,2,3,4386480,1120,4386480,1120,0,16,32,16,16,16,2,3,1864832,2240,1864832,2240,0,16,64,16,16,32,2,3,3602496,1120,3602496,1120,0,16,32,16,8,16,2,3,4386496,1120,4386496,1120,0,16,32,16,8,16,6,3,1864848,2240,1864848,2240,16,16,64,16,16,32,6,3,3602504,1120,3602504,1120,8,16,32,16,8,16,6,3,4386504,1120,4386504,1120,8,16,32,16,8,16,4,0,1793183,2240,1793184,2240,112,16,0,16,64,32,4,0,3584591,1120,3584592,1120,120,16,0,16,32,16,4,0,4368591,1120,4368592,1120,120,16,0,16,32,16,6,3,1864864,2240,1864864,2240,16,16,64,16,64,32,6,3,3602512,1120,3602512,1120,8,16,32,16,32,16,6,3,4386512,1120,4386512,1120,8,16,32,16,32,16,6,3,1793248,2240,1793248,2240,48,16,32,16,64,64,6,3,3584624,1120,3584624,1120,24,16,16,16,32,32,6,3,4368624,1120,4368624,1120,24,16,16,16,32,32,0,3,1793312,2240,1793312,2240,0,16,0,16,64,64,0,3,3584656,1120,3584656,1120,0,16,0,16,32,32,0,3,4368656,1120,4368656,1120,0,16,0,16,32,32,0,3,1793376,2240,1793376,2240,0,16,0,16,64,64,0,3,3584688,1120,3584688,1120,0,16,0,16,32,32,0,3,4368688,1120,4368688,1120,0,16,0,16,32,32,0,3,1793440,2240,1793440,2240,0,16,0,16,64,64,0,3,3584720,1120,3584720,1120,0,16,0,16,32,32,0,3,4368720,1120,4368720,1120,0,16,0,16,32,32,0,3,1793504,2240,1793504,2240,0,16,0,16,64,64,0,3,3584752,1120,3584752,1120,0,16,0,16,32,32,0,3,4368752,1120,4368752,1120,0,16,0,16,32,32,0,3,1793568,2240,1793568,2240,0,16,0,16,64,64,0,3,3584784,1120,3584784,1120,0,16,0,16,32,32,0,3,4368784,1120,4368784,1120,0,16,0,16,32,32,0,3,1793632,2240,1793632,2240,0,16,0,16,64,64,0,3,3584816,1120,3584816,1120,0,16,0,16,32,32,0,3,4368816,1120,4368816,1120,0,16,0,16,32,32,0,3,1793696,2240,1793696,2240,0,16,0,16,64,64,0,3,3584848,1120,3584848,1120,0,16,0,16,32,32,0,3,4368848,1120,4368848,1120,0,16,0,16,32,32,0,3,1793760,2240,1793760,2240,0,16,0,16,64,64,0,3,3584880,1120,3584880,1120,0,16,0,16,32,32,0,3,4368880,1120,4368880,1120,0,16,0,16,32,32,0,3,1793824,2240,1793824,2240,0,16,0,16,64,64,0,3,3584912,1120,3584912,1120,0,16,0,16,32,32,0,3,4368912,1120,4368912,1120,0,16,0,16,32,32,0,3,1793888,2240,1793888,2240,0,16,0,16,64,64,0,3,3584944,1120,3584944,1120,0,16,0,16,32,32,0,3,4368944,1120,4368944,1120,0,16,0,16,32,32,0,3,1793952,2240,1793952,2240,0,16,0,16,64,64,0,3,3584976,1120,3584976,1120,0,16,0,16,32,32,0,3,4368976,1120,4368976,1120,0,16,0,16,32,32,0,3,1794016,2240,1794016,2240,0,16,0,16,64,64,0,3,3585008,1120,3585008,1120,0,16,0,16,32,32,0,3,4369008,1120,4369008,1120,0,16,0,16,32,32,0,3,1935520,2240,1935520,2240,0,16,0,16,64,64,0,3,3619920,1120,3619920,1120,0,16,0,16,32,32,0,3,4403920,1120,4403920,1120,0,16,0,16,32,32,0,3,1935584,2240,1935584,2240,0,16,0,16,64,64,0,3,3619952,1120,3619952,1120,0,16,0,16,32,32,0,3,4403952,1120,4403952,1120,0,16,0,16,32,32,0,3,1935648,2240,1935648,2240,0,16,0,16,64,64,0,3,3619984,1120,3619984,1120,0,16,0,16,32,32,0,3,4403984,1120,4403984,1120,0,16,0,16,32,32,0,3,1935712,2240,1935712,2240,0,16,0,16,64,64,0,3,3620016,1120,3620016,1120,0,16,0,16,32,32,0,3,4404016,1120,4404016,1120,0,16,0,16,32,32,0,3,1935776,2240,1935776,2240,0,16,0,16,64,64,0,3,3620048,1120,3620048,1120,0,16,0,16,32,32,0,3,4404048,1120,4404048,1120,0,16,0,16,32,32,0,3,1935840,2240,1935840,2240,0,16,0,16,64,64,0,3,3620080,1120,3620080,1120,0,16,0,16,32,32,0,3,4404080,1120,4404080,1120,0,16,0,16,32,32,0,3,1935904,2240,1935904,2240,0,16,0,16,64,64,0,3,3620112,1120,3620112,1120,0,16,0,16,32,32,0,3,4404112,1120,4404112,1120,0,16,0,16,32,32,0,3,1935968,2240,1935968,2240,0,16,0,16,64,64,0,3,3620144,1120,3620144,1120,0,16,0,16,32,32,0,3,4404144,1120,4404144,1120,0,16,0,16,32,32,0,3,1936032,2240,1936032,2240,0,16,0,16,64,64,0,3,3620176,1120,3620176,1120,0,16,0,16,32,32,0,3,4404176,1120,4404176,1120,0,16,0,16,32,32,0,3,1936096,2240,1936096,2240,0,16,0,16,32,32,0,3,3620208,1120,3620208,1120,0,16,0,16,16,16,0,3,4404208,1120,4404208,1120,0,16,0,16,16,16,0,3,1936128,2240,1936128,2240,0,16,0,16,32,32,0,3,3620224,1120,3620224,1120,0,16,0,16,16,16,0,3,4404224,1120,4404224,1120,0,16,0,16,16,16,0,3,2007776,2240,2007776,2240,0,16,0,16,16,16,0,3,3638128,1120,3638128,1120,0,16,0,16,8,8,0,3,4422128,1120,4422128,1120,0,16,0,16,8,8,0,3,2007792,2240,2007792,2240,0,16,0,16,16,16,0,3,3638136,1120,3638136,1120,0,16,0,16,8,8,0,3,4422136,1120,4422136,1120,0,16,0,16,8,8,0,3,2043616,2240,2043616,2240,0,16,0,16,16,16,0,3,3647088,1120,3647088,1120,0,16,0,16,8,8,0,3,4431088,1120,4431088,1120,0,16,0,16,8,8,0,3,2043632,2240,2043632,2240,0,16,0,16,8,16,0,3,3647096,1120,3647096,1120,0,16,0,16,4,8,0,3,4431096,1120,4431096,1120,0,16,0,16,4,8,6,3,2043639,2240,2043640,2240,48,16,112,16,8,16,6,3,3647099,1120,3647100,1120,88,16,56,16,4,8,6,3,4431099,1120,4431100,1120,88,16,56,16,4,8,0,3,2007808,2240,2007808,2240,0,16,0,16,32,32,0,3,3638144,1120,3638144,1120,0,16,0,16,16,16,0,3,4422144,1120,4422144,1120,0,16,0,16,16,16,0,3,1936160,2240,1936160,2240,0,16,0,16,64,64,0,3,3620240,1120,3620240,1120,0,16,0,16,32,32,0,3,4404240,1120,4404240,1120,0,16,0,16,32,32,0,3,1936224,2240,1936224,2240,0,16,0,16,64,64,0,3,3620272,1120,3620272,1120,0,16,0,16,32,32,0,3,4404272,1120,4404272,1120,0,16,0,16,32,32,0,3,1936288,2240,1936288,2240,0,16,0,16,64,64,0,3,3620304,1120,3620304,1120,0,16,0,16,32,32,0,3,4404304,1120,4404304,1120,0,16,0,16,32,32,6,3,1936351,2240,1936352,2240,112,16,64,16,64,64,6,3,3620335,1120,3620336,1120,120,16,32,16,32,32,6,3,4404335,1120,4404336,1120,120,16,32,16,32,32,2,3,1936416,2240,1936416,2240,0,16,64,16,32,16,2,3,3620368,1120,3620368,1120,0,16,32,16,16,8,2,3,4404368,1120,4404368,1120,0,16,32,16,16,8,2,3,1972256,2240,1972256,2240,128,16,192,16,32,16,2,3,3629328,1120,3629328,1120,128,16,160,16,16,8,2,3,4413328,1120,4413328,1120,128,16,160,16,16,8,2,3,1936448,2240,1936448,2240,0,16,64,16,16,16,2,3,3620384,1120,3620384,1120,0,16,32,16,8,8,2,3,4404384,1120,4404384,1120,0,16,32,16,8,8,2,3,1936464,2240,1936464,2240,0,16,64,16,16,16,2,3,3620392,1120,3620392,1120,0,16,32,16,8,8,2,3,4404392,1120,4404392,1120,0,16,32,16,8,8,2,3,1972288,2240,1972288,2240,0,16,64,16,16,16,2,3,3629344,1120,3629344,1120,0,16,32,16,8,8,2,3,4413344,1120,4413344,1120,0,16,32,16,8,8,0,3,1972304,2240,1972304,2240,0,16,0,16,16,16,0,3,3629352,1120,3629352,1120,0,16,0,16,8,8,0,3,4413352,1120,4413352,1120,0,16,0,16,8,8,2,3,2008096,2240,2008096,2240,0,16,64,16,32,32,2,3,3638288,1120,3638288,1120,0,16,32,16,16,16,2,3,4422288,1120,4422288,1120,0,16,32,16,16,16,2,3,2008128,2240,2008128,2240,0,16,64,16,32,32,2,3,3638304,1120,3638304,1120,0,16,32,16,16,16,2,3,4422304,1120,4422304,1120,0,16,32,16,16,16,2,3,1936480,2240,1936480,2240,0,16,64,16,32,16,2,3,3620400,1120,3620400,1120,0,16,32,16,16,8,2,3,4404400,1120,4404400,1120,0,16,32,16,16,8,2,3,1972320,2240,1972320,2240,128,16,192,16,32,16,2,3,3629360,1120,3629360,1120,128,16,160,16,16,8,2,3,4413360,1120,4413360,1120,128,16,160,16,16,8,2,3,1936512,2240,1936512,2240,0,16,64,16,16,16,2,3,3620416,1120,3620416,1120,0,16,32,16,8,8,2,3,4404416,1120,4404416,1120,0,16,32,16,8,8,2,3,1936528,2240,1936528,2240,0,16,64,16,16,8,2,3,3620424,1120,3620424,1120,0,16,32,16,8,4,2,3,4404424,1120,4404424,1120,0,16,32,16,8,4,6,3,1954448,2240,1954448,2240,288,16,336,16,16,8,6,3,3624904,1120,3624904,1120,272,16,296,16,8,4,6,3,4408904,1120,4408904,1120,272,16,296,16,8,4,2,3,1972352,2240,1972352,2240,128,16,192,16,16,16,2,3,3629376,1120,3629376,1120,128,16,160,16,8,8,2,3,4413376,1120,4413376,1120,128,16,160,16,8,8,6,3,1972368,2240,1972368,2240,160,16,208,16,16,16,6,3,3629384,1120,3629384,1120,144,16,168,16,8,8,6,3,4413384,1120,4413384,1120,144,16,168,16,8,8,2,3,2008160,2240,2008160,2240,0,16,64,16,32,32,2,3,3638320,1120,3638320,1120,0,16,32,16,16,16,2,3,4422320,1120,4422320,1120,0,16,32,16,16,16,2,3,2008192,2240,2008192,2240,0,16,64,16,32,32,2,3,3638336,1120,3638336,1120,0,16,32,16,16,16,2,3,4422336,1120,4422336,1120,0,16,32,16,16,16,6,3,1936544,2240,1936544,2240,16,16,64,16,8,8,6,3,3620432,1120,3620432,1120,8,16,32,16,4,4,6,3,4404432,1120,4404432,1120,8,16,32,16,4,4,6,3,1936552,2240,1936552,2240,16,16,64,16,8,8,6,3,3620436,1120,3620436,1120,8,16,32,16,4,4,6,3,4404436,1120,4404436,1120,8,16,32,16,4,4,6,3,1954464,2240,1954464,2240,288,16,336,16,8,8,6,3,3624912,1120,3624912,1120,272,16,296,16,4,4,6,3,4408912,1120,4408912,1120,272,16,296,16,4,4,6,3,1954472,2240,1954472,2240,16,16,64,16,8,8,6,3,3624916,1120,3624916,1120,8,16,32,16,4,4,6,3,4408916,1120,4408916,1120,8,16,32,16,4,4,6,3,1936560,2240,1936560,2240,16,16,64,16,16,16,6,3,3620440,1120,3620440,1120,8,16,32,16,8,8,6,3,4404440,1120,4404440,1120,8,16,32,16,8,8,6,3,1972384,2240,1972384,2240,160,16,208,16,16,16,6,3,3629392,1120,3629392,1120,144,16,168,16,8,8,6,3,4413392,1120,4413392,1120,144,16,168,16,8,8,6,3,1972400,2240,1972400,2240,16,16,64,16,16,16,6,3,3629400,1120,3629400,1120,8,16,32,16,8,8,6,3,4413400,1120,4413400,1120,8,16,32,16,8,8,6,3,1936576,2240,1936576,2240,16,16,64,16,32,32,6,3,3620448,1120,3620448,1120,8,16,32,16,16,16,6,3,4404448,1120,4404448,1120,8,16,32,16,16,16,6,3,2008224,2240,2008224,2240,32,16,80,16,32,32,6,3,3638352,1120,3638352,1120,16,16,40,16,16,16,6,3,4422352,1120,4422352,1120,16,16,40,16,16,16,6,3,2008256,2240,2008256,2240,16,16,64,16,32,32,6,3,3638368,1120,3638368,1120,8,16,32,16,16,16,6,3,4422368,1120,4422368,1120,8,16,32,16,16,16,6,3,1936608,2240,1936608,2240,48,16,32,16,64,64,6,3,3620464,1120,3620464,1120,24,16,16,16,32,32,6,3,4404464,1120,4404464,1120,24,16,16,16,32,32,0,3,1936672,2240,1936672,2240,0,16,0,16,64,64,0,3,3620496,1120,3620496,1120,0,16,0,16,32,32,0,3,4404496,1120,4404496,1120,0,16,0,16,32,32,0,3,1936736,2240,1936736,2240,0,16,0,16,64,64,0,3,3620528,1120,3620528,1120,0,16,0,16,32,32,0,3,4404528,1120,4404528,1120,0,16,0,16,32,32,0,3,1936800,2240,1936800,2240,0,16,0,16,64,64,0,3,3620560,1120,3620560,1120,0,16,0,16,32,32,0,3,4404560,1120,4404560,1120,0,16,0,16,32,32,0,3,1936864,2240,1936864,2240,0,16,0,16,64,64,0,3,3620592,1120,3620592,1120,0,16,0,16,32,32,0,3,4404592,1120,4404592,1120,0,16,0,16,32,32,0,3,1936928,2240,1936928,2240,0,16,0,16,64,64,0,3,3620624,1120,3620624,1120,0,16,0,16,32,32,0,3,4404624,1120,4404624,1120,0,16,0,16,32,32,0,3,1936992,2240,1936992,2240,0,16,0,16,64,64,0,3,3620656,1120,3620656,1120,0,16,0,16,32,32,0,3,4404656,1120,4404656,1120,0,16,0,16,32,32,0,3,1937056,2240,1937056,2240,0,16,0,16,64,64,0,3,3620688,1120,3620688,1120,0,16,0,16,32,32,0,3,4404688,1120,4404688,1120,0,16,0,16,32,32,0,3,1937120,2240,1937120,2240,0,16,0,16,64,64,0,3,3620720,1120,3620720,1120,0,16,0,16,32,32,0,3,4404720,1120,4404720,1120,0,16,0,16,32,32,0,3,1937184,2240,1937184,2240,0,16,0,16,64,64,0,3,3620752,1120,3620752,1120,0,16,0,16,32,32,0,3,4404752,1120,4404752,1120,0,16,0,16,32,32,0,3,1937248,2240,1937248,2240,0,16,0,16,64,64,0,3,3620784,1120,3620784,1120,0,16,0,16,32,32,0,3,4404784,1120,4404784,1120,0,16,0,16,32,32,0,3,1937312,2240,1937312,2240,0,16,0,16,64,64,0,3,3620816,1120,3620816,1120,0,16,0,16,32,32,0,3,4404816,1120,4404816,1120,0,16,0,16,32,32,0,3,1937376,2240,1937376,2240,0,16,0,16,64,64,0,3,3620848,1120,3620848,1120,0,16,0,16,32,32,0,3,4404848,1120,4404848,1120,0,16,0,16,32,32,0,3,2078880,2240,2078880,2240,0,16,0,16,64,64,0,3,3655760,1120,3655760,1120,0,16,0,16,32,32,0,3,4439760,1120,4439760,1120,0,16,0,16,32,32,0,3,2078944,2240,2078944,2240,0,16,0,16,64,64,0,3,3655792,1120,3655792,1120,0,16,0,16,32,32,0,3,4439792,1120,4439792,1120,0,16,0,16,32,32,0,3,2079008,2240,2079008,2240,0,16,0,16,64,64,0,3,3655824,1120,3655824,1120,0,16,0,16,32,32,0,3,4439824,1120,4439824,1120,0,16,0,16,32,32,0,3,2079072,2240,2079072,2240,0,16,0,16,64,64,0,3,3655856,1120,3655856,1120,0,16,0,16,32,32,0,3,4439856,1120,4439856,1120,0,16,0,16,32,32,0,3,2079136,2240,2079136,2240,0,16,0,16,64,64,0,3,3655888,1120,3655888,1120,0,16,0,16,32,32,0,3,4439888,1120,4439888,1120,0,16,0,16,32,32,0,3,2079200,2240,2079200,2240,0,16,0,16,64,64,0,3,3655920,1120,3655920,1120,0,16,0,16,32,32,0,3,4439920,1120,4439920,1120,0,16,0,16,32,32,6,3,2079263,2240,2079264,2240,272,16,352,16,64,64,6,3,3655951,1120,3655952,1120,328,16,304,16,32,32,6,3,4439951,1120,4439952,1120,328,16,304,16,32,32,0,3,2079328,2240,2079328,2240,0,16,0,16,16,16,0,3,3655984,1120,3655984,1120,0,16,0,16,8,8,0,3,4439984,1120,4439984,1120,0,16,0,16,8,8,6,3,2079343,2240,2079344,2240,32,16,80,16,16,16,6,3,3655991,1120,3655992,1120,80,16,40,16,8,8,6,3,4439991,1120,4439992,1120,80,16,40,16,8,8,6,3,2115167,2240,2115168,2240,32,16,96,16,8,16,6,3,3664943,1120,3664944,1120,80,16,48,16,4,8,6,3,4448943,1120,4448944,1120,80,16,48,16,4,8,6,3,2115175,2240,2115176,2240,32,16,96,16,8,16,6,3,3664947,1120,3664948,1120,80,16,48,16,4,8,6,3,4448947,1120,4448948,1120,80,16,48,16,4,8,6,3,2115183,2240,2115184,2240,32,16,80,16,16,16,6,3,3664951,1120,3664952,1120,80,16,40,16,8,8,6,3,4448951,1120,4448952,1120,80,16,40,16,8,8,6,3,2079359,2240,2079360,2240,48,16,80,16,16,32,6,3,3655999,1120,3656000,1120,88,16,40,16,8,16,6,3,4439999,1120,4440000,1120,88,16,40,16,8,16,6,3,2079375,2240,2079376,2240,48,16,80,16,16,32,6,3,3656007,1120,3656008,1120,88,16,40,16,8,16,6,3,4440007,1120,4440008,1120,88,16,40,16,8,16,6,3,2151007,2240,2151008,2240,32,16,96,16,32,32,6,3,3673903,1120,3673904,1120,80,16,48,16,16,16,6,3,4457903,1120,4457904,1120,80,16,48,16,16,16,6,3,2151039,2240,2151040,2240,48,16,96,16,32,32,6,3,3673919,1120,3673920,1120,88,16,48,16,16,16,6,3,4457919,1120,4457920,1120,88,16,48,16,16,16,6,3,2079391,2240,2079392,2240,48,16,80,16,32,64,6,3,3656015,1120,3656016,1120,88,16,40,16,16,32,6,3,4440015,1120,4440016,1120,88,16,40,16,16,32,6,3,2079423,2240,2079424,2240,48,16,96,16,32,64,6,3,3656031,1120,3656032,1120,88,16,48,16,16,32,6,3,4440031,1120,4440032,1120,88,16,48,16,16,32,6,3,2079455,2240,2079456,2240,64,16,96,16,16,32,6,3,3656047,1120,3656048,1120,96,16,48,16,8,16,6,3,4440047,1120,4440048,1120,96,16,48,16,8,16,6,3,2079471,2240,2079472,2240,64,16,80,16,16,32,6,3,3656055,1120,3656056,1120,96,16,40,16,8,16,6,3,4440055,1120,4440056,1120,96,16,40,16,8,16,0,3,2079488,2240,2079488,2240,0,16,0,16,16,16,0,3,3656064,1120,3656064,1120,0,16,0,16,8,8,0,3,4440064,1120,4440064,1120,0,16,0,16,8,8,6,3,2079503,2240,2079504,2240,320,16,336,16,16,16,6,3,3656071,1120,3656072,1120,352,16,296,16,8,8,6,3,4440071,1120,4440072,1120,352,16,296,16,8,8,0,3,2115328,2240,2115328,2240,0,16,0,16,16,16,0,3,3665024,1120,3665024,1120,0,16,0,16,8,8,0,3,4449024,1120,4449024,1120,0,16,0,16,8,8,6,3,2151135,2240,2151136,2240,64,16,96,16,32,32,6,3,3673967,1120,3673968,1120,96,16,48,16,16,16,6,3,4457967,1120,4457968,1120,96,16,48,16,16,16,0,3,2151168,2240,2151168,2240,0,16,0,16,16,16,0,3,3673984,1120,3673984,1120,0,16,0,16,8,8,0,3,4457984,1120,4457984,1120,0,16,0,16,8,8,0,3,2187008,2240,2187008,2240,0,16,0,16,16,16,0,3,3682944,1120,3682944,1120,0,16,0,16,8,8,0,3,4466944,1120,4466944,1120,0,16,0,16,8,8,6,3,2187023,2240,2187024,2240,64,16,96,16,16,16,6,3,3682951,1120,3682952,1120,96,16,48,16,8,8,6,3,4466951,1120,4466952,1120,96,16,48,16,8,8,6,3,2079519,2240,2079520,2240,320,16,336,16,32,32,6,3,3656079,1120,3656080,1120,352,16,296,16,16,16,6,3,4440079,1120,4440080,1120,352,16,296,16,16,16,6,3,2079551,2240,2079552,2240,64,16,80,16,16,16,6,3,3656095,1120,3656096,1120,96,16,40,16,8,8,6,3,4440095,1120,4440096,1120,96,16,40,16,8,8,0,3,2079568,2240,2079568,2240,0,16,0,16,16,16,0,3,3656104,1120,3656104,1120,0,16,0,16,8,8,0,3,4440104,1120,4440104,1120,0,16,0,16,8,8,6,3,2115391,2240,2115392,2240,64,16,80,16,8,16,6,3,3665055,1120,3665056,1120,96,16,40,16,4,8,6,3,4449055,1120,4449056,1120,96,16,40,16,4,8,6,3,2115399,2240,2115400,2240,80,16,80,16,8,16,6,3,3665059,1120,3665060,1120,104,16,40,16,4,8,6,3,4449059,1120,4449060,1120,104,16,40,16,4,8,0,3,2115408,2240,2115408,2240,0,16,0,16,16,16,0,3,3665064,1120,3665064,1120,0,16,0,16,8,8,0,3,4449064,1120,4449064,1120,0,16,0,16,8,8,6,3,2151199,2240,2151200,2240,64,16,80,16,32,32,6,3,3673999,1120,3674000,1120,96,16,40,16,16,16,6,3,4457999,1120,4458000,1120,96,16,40,16,16,16,6,3,2151231,2240,2151232,2240,80,16,96,16,32,32,6,3,3674015,1120,3674016,1120,104,16,48,16,16,16,6,3,4458015,1120,4458016,1120,104,16,48,16,16,16,6,3,2079583,2240,2079584,2240,80,16,80,16,64,64,6,3,3656111,1120,3656112,1120,104,16,40,16,32,32,6,3,4440111,1120,4440112,1120,104,16,40,16,32,32,6,3,2079647,2240,2079648,2240,96,16,80,16,32,16,6,3,3656143,1120,3656144,1120,112,16,40,16,16,8,6,3,4440143,1120,4440144,1120,112,16,40,16,16,8,6,3,2115487,2240,2115488,2240,96,16,96,16,32,16,6,3,3665103,1120,3665104,1120,112,16,48,16,16,8,6,3,4449103,1120,4449104,1120,112,16,48,16,16,8,6,3,2079679,2240,2079680,2240,96,16,80,16,32,16,6,3,3656159,1120,3656160,1120,112,16,40,16,16,8,6,3,4440159,1120,4440160,1120,112,16,40,16,16,8,6,3,2115519,2240,2115520,2240,96,16,96,16,32,16,6,3,3665119,1120,3665120,1120,112,16,48,16,16,8,6,3,4449119,1120,4449120,1120,112,16,48,16,16,8,6,3,2151327,2240,2151328,2240,96,16,96,16,32,32,6,3,3674063,1120,3674064,1120,112,16,48,16,16,16,6,3,4458063,1120,4458064,1120,112,16,48,16,16,16,6,3,2151359,2240,2151360,2240,96,16,96,16,32,32,6,3,3674079,1120,3674080,1120,112,16,48,16,16,16,6,3,4458079,1120,4458080,1120,112,16,48,16,16,16,6,3,2079711,2240,2079712,2240,112,16,64,16,32,32,6,3,3656175,1120,3656176,1120,120,16,32,16,16,16,6,3,4440175,1120,4440176,1120,120,16,32,16,16,16,6,3,2079743,2240,2079744,2240,112,16,64,16,32,16,6,3,3656191,1120,3656192,1120,120,16,32,16,16,8,6,3,4440191,1120,4440192,1120,120,16,32,16,16,8,2,3,2115584,2240,2115584,2240,0,16,96,16,32,16,2,3,3665152,1120,3665152,1120,0,16,48,16,16,8,2,3,4449152,1120,4449152,1120,0,16,48,16,16,8,6,3,2151391,2240,2151392,2240,112,16,80,16,32,32,6,3,3674095,1120,3674096,1120,120,16,40,16,16,16,6,3,4458095,1120,4458096,1120,120,16,40,16,16,16,2,3,2151424,2240,2151424,2240,0,16,96,16,32,32,2,3,3674112,1120,3674112,1120,0,16,48,16,16,16,2,3,4458112,1120,4458112,1120,0,16,48,16,16,16,2,3,2079776,2240,2079776,2240,0,16,80,16,64,32,2,3,3656208,1120,3656208,1120,0,16,40,16,32,16,2,3,4440208,1120,4440208,1120,0,16,40,16,32,16,2,3,2151456,2240,2151456,2240,0,16,96,16,64,32,2,3,3674128,1120,3674128,1120,0,16,48,16,32,16,2,3,4458128,1120,4458128,1120,0,16,48,16,32,16,2,3,2079840,2240,2079840,2240,0,16,80,16,32,64,2,3,3656240,1120,3656240,1120,0,16,40,16,16,32,2,3,4440240,1120,4440240,1120,0,16,40,16,16,32,2,3,2079872,2240,2079872,2240,0,16,96,16,32,64,2,3,3656256,1120,3656256,1120,0,16,48,16,16,32,2,3,4440256,1120,4440256,1120,0,16,48,16,16,32,6,3,2079904,2240,2079904,2240,16,16,80,16,32,32,6,3,3656272,1120,3656272,1120,8,16,40,16,16,16,6,3,4440272,1120,4440272,1120,8,16,40,16,16,16,6,3,2079936,2240,2079936,2240,32,16,80,16,32,32,6,3,3656288,1120,3656288,1120,16,16,40,16,16,16,6,3,4440288,1120,4440288,1120,16,16,40,16,16,16,6,3,2151584,2240,2151584,2240,16,16,96,16,32,32,6,3,3674192,1120,3674192,1120,8,16,48,16,16,16,6,3,4458192,1120,4458192,1120,8,16,48,16,16,16,6,3,2151616,2240,2151616,2240,32,16,96,16,32,32,6,3,3674208,1120,3674208,1120,16,16,48,16,16,16,6,3,4458208,1120,4458208,1120,16,16,48,16,16,16,6,3,2079968,2240,2079968,2240,16,16,96,16,32,64,6,3,3656304,1120,3656304,1120,8,16,48,16,16,32,6,3,4440304,1120,4440304,1120,8,16,48,16,16,32,6,3,2080000,2240,2080000,2240,32,16,96,16,32,64,6,3,3656320,1120,3656320,1120,16,16,48,16,16,32,6,3,4440320,1120,4440320,1120,16,16,48,16,16,32,6,3,2080032,2240,2080032,2240,32,16,96,16,16,16,6,3,3656336,1120,3656336,1120,16,16,48,16,8,8,6,3,4440336,1120,4440336,1120,16,16,48,16,8,8,6,3,2080048,2240,2080048,2240,32,16,96,16,16,16,6,3,3656344,1120,3656344,1120,16,16,48,16,8,8,6,3,4440344,1120,4440344,1120,16,16,48,16,8,8,6,3,2115872,2240,2115872,2240,32,16,64,16,16,16,6,3,3665296,1120,3665296,1120,16,16,32,16,8,8,6,3,4449296,1120,4449296,1120,16,16,32,16,8,8,6,3,2115888,2240,2115888,2240,48,16,96,16,8,8,6,3,3665304,1120,3665304,1120,24,16,48,16,4,4,6,3,4449304,1120,4449304,1120,24,16,48,16,4,4,0,3,2115896,2240,2115896,2240,0,16,0,16,8,8,0,3,3665308,1120,3665308,1120,0,16,0,16,4,4,0,3,4449308,1120,4449308,1120,0,16,0,16,4,4,6,3,2133808,2240,2133808,2240,48,16,96,16,8,8,6,3,3669784,1120,3669784,1120,24,16,48,16,4,4,6,3,4453784,1120,4453784,1120,24,16,48,16,4,4,0,3,2133816,2240,2133816,2240,0,16,0,16,8,8,0,3,3669788,1120,3669788,1120,0,16,0,16,4,4,0,3,4453788,1120,4453788,1120,0,16,0,16,4,4,6,3,2080064,2240,2080064,2240,48,16,80,16,32,32,6,3,3656352,1120,3656352,1120,24,16,40,16,16,16,6,3,4440352,1120,4440352,1120,24,16,40,16,16,16,6,3,2151712,2240,2151712,2240,32,16,64,16,16,16,6,3,3674256,1120,3674256,1120,16,16,32,16,8,8,6,3,4458256,1120,4458256,1120,16,16,32,16,8,8,6,3,2151728,2240,2151728,2240,48,16,96,16,8,8,6,3,3674264,1120,3674264,1120,24,16,48,16,4,4,6,3,4458264,1120,4458264,1120,24,16,48,16,4,4,0,3,2151736,2240,2151736,2240,0,16,0,16,8,8,0,3,3674268,1120,3674268,1120,0,16,0,16,4,4,0,3,4458268,1120,4458268,1120,0,16,0,16,4,4,6,3,2169648,2240,2169648,2240,48,16,96,16,8,8,6,3,3678744,1120,3678744,1120,24,16,48,16,4,4,6,3,4462744,1120,4462744,1120,24,16,48,16,4,4,0,3,2169656,2240,2169656,2240,0,16,0,16,8,8,0,3,3678748,1120,3678748,1120,0,16,0,16,4,4,0,3,4462748,1120,4462748,1120,0,16,0,16,4,4,6,3,2187552,2240,2187552,2240,32,16,80,16,16,16,6,3,3683216,1120,3683216,1120,16,16,40,16,8,8,6,3,4467216,1120,4467216,1120,16,16,40,16,8,8,6,3,2187568,2240,2187568,2240,48,16,96,16,16,16,6,3,3683224,1120,3683224,1120,24,16,48,16,8,8,6,3,4467224,1120,4467224,1120,24,16,48,16,8,8,6,3,2151744,2240,2151744,2240,48,16,80,16,32,32,6,3,3674272,1120,3674272,1120,24,16,40,16,16,16,6,3,4458272,1120,4458272,1120,24,16,40,16,16,16,6,3,2080096,2240,2080096,2240,48,16,96,16,16,32,6,3,3656368,1120,3656368,1120,24,16,48,16,8,16,6,3,4440368,1120,4440368,1120,24,16,48,16,8,16,6,3,2080112,2240,2080112,2240,48,16,96,16,16,32,6,3,3656376,1120,3656376,1120,24,16,48,16,8,16,6,3,4440376,1120,4440376,1120,24,16,48,16,8,16,6,3,2080128,2240,2080128,2240,64,16,96,16,16,32,6,3,3656384,1120,3656384,1120,32,16,48,16,8,16,6,3,4440384,1120,4440384,1120,32,16,48,16,8,16,6,3,2080144,2240,2080144,2240,64,16,96,16,16,32,6,3,3656392,1120,3656392,1120,32,16,48,16,8,16,6,3,4440392,1120,4440392,1120,32,16,48,16,8,16,6,3,2151776,2240,2151776,2240,48,16,96,16,16,32,6,3,3674288,1120,3674288,1120,24,16,48,16,8,16,6,3,4458288,1120,4458288,1120,24,16,48,16,8,16,6,3,2151792,2240,2151792,2240,64,16,96,16,16,32,6,3,3674296,1120,3674296,1120,32,16,48,16,8,16,6,3,4458296,1120,4458296,1120,32,16,48,16,8,16,6,3,2151808,2240,2151808,2240,64,16,96,16,4,4,6,3,2151812,2240,2151812,2240,48,16,96,16,4,4,6,3,2160768,2240,2160768,2240,64,16,96,16,4,4,6,3,2160772,2240,2160772,2240,48,16,96,16,4,4,6,3,3674304,1120,3674304,1120,32,16,48,16,4,4,6,3,4458304,1120,4458304,1120,32,16,48,16,4,4,6,3,2151816,2240,2151816,2240,64,16,96,16,8,8,6,3,3674308,1120,3674308,1120,32,16,48,16,4,4,6,3,4458308,1120,4458308,1120,32,16,48,16,4,4,6,3,2169728,2240,2169728,2240,48,16,96,16,8,8,6,3,3678784,1120,3678784,1120,24,16,48,16,4,4,6,3,4462784,1120,4462784,1120,24,16,48,16,4,4,6,3,2169736,2240,2169736,2240,64,16,96,16,8,8,6,3,3678788,1120,3678788,1120,32,16,48,16,4,4,6,3,4462788,1120,4462788,1120,32,16,48,16,4,4,6,3,2151824,2240,2151824,2240,64,16,96,16,16,16,6,3,3674312,1120,3674312,1120,32,16,48,16,8,8,6,3,4458312,1120,4458312,1120,32,16,48,16,8,8,6,3,2187648,2240,2187648,2240,64,16,96,16,16,16,6,3,3683264,1120,3683264,1120,32,16,48,16,8,8,6,3,4467264,1120,4467264,1120,32,16,48,16,8,8,6,3,2187664,2240,2187664,2240,64,16,96,16,16,16,6,3,3683272,1120,3683272,1120,32,16,48,16,8,8,6,3,4467272,1120,4467272,1120,32,16,48,16,8,8,6,3,2080160,2240,2080160,2240,64,16,96,16,32,32,6,3,3656400,1120,3656400,1120,32,16,48,16,16,16,6,3,4440400,1120,4440400,1120,32,16,48,16,16,16,6,3,2080192,2240,2080192,2240,80,16,80,16,32,16,6,3,3656416,1120,3656416,1120,40,16,40,16,16,8,6,3,4440416,1120,4440416,1120,40,16,40,16,16,8,6,3,2116032,2240,2116032,2240,80,16,96,16,32,16,6,3,3665376,1120,3665376,1120,40,16,48,16,16,8,6,3,4449376,1120,4449376,1120,40,16,48,16,16,8,6,3,2151840,2240,2151840,2240,64,16,96,16,32,32,6,3,3674320,1120,3674320,1120,32,16,48,16,16,16,6,3,4458320,1120,4458320,1120,32,16,48,16,16,16,6,3,2151872,2240,2151872,2240,80,16,96,16,16,8,6,3,3674336,1120,3674336,1120,40,16,48,16,8,4,6,3,4458336,1120,4458336,1120,40,16,48,16,8,4,6,0,2167551,2240,2169792,2240,48,16,32,16,16,8,6,0,3677695,1120,3678816,1120,88,16,80,16,8,4,6,0,4461695,1120,4462816,1120,88,16,80,16,8,4,6,3,2151888,2240,2151888,2240,80,16,96,16,16,16,6,3,3674344,1120,3674344,1120,40,16,48,16,8,8,6,3,4458344,1120,4458344,1120,40,16,48,16,8,8,6,3,2187712,2240,2187712,2240,64,16,96,16,16,16,6,3,3683296,1120,3683296,1120,32,16,48,16,8,8,6,3,4467296,1120,4467296,1120,32,16,48,16,8,8,6,3,2187728,2240,2187728,2240,80,16,96,16,16,16,6,3,3683304,1120,3683304,1120,40,16,48,16,8,8,6,3,4467304,1120,4467304,1120,40,16,48,16,8,8,6,3,2080224,2240,2080224,2240,336,16,336,16,32,64,6,3,3656432,1120,3656432,1120,296,16,296,16,16,32,6,3,4440432,1120,4440432,1120,296,16,296,16,16,32,6,3,2080256,2240,2080256,2240,96,16,80,16,32,64,6,3,3656448,1120,3656448,1120,48,16,40,16,16,32,6,3,4440448,1120,4440448,1120,48,16,40,16,16,32,6,3,2080288,2240,2080288,2240,96,16,80,16,64,64,6,3,3656464,1120,3656464,1120,48,16,40,16,32,32,6,3,4440464,1120,4440464,1120,48,16,40,16,32,32,6,3,2080352,2240,2080352,2240,96,16,80,16,64,32,6,3,3656496,1120,3656496,1120,48,16,40,16,32,16,6,3,4440496,1120,4440496,1120,48,16,40,16,32,16,6,3,2152032,2240,2152032,2240,96,16,96,16,64,32,6,3,3674416,1120,3674416,1120,48,16,48,16,32,16,6,3,4458416,1120,4458416,1120,48,16,48,16,32,16,0,3,2080416,2240,2080416,2240,0,16,0,16,64,64,0,3,3656528,1120,3656528,1120,0,16,0,16,32,32,0,3,4440528,1120,4440528,1120,0,16,0,16,32,32,0,3,2080480,2240,2080480,2240,0,16,0,16,64,64,0,3,3656560,1120,3656560,1120,0,16,0,16,32,32,0,3,4440560,1120,4440560,1120,0,16,0,16,32,32,0,3,2080544,2240,2080544,2240,0,16,0,16,64,64,0,3,3656592,1120,3656592,1120,0,16,0,16,32,32,0,3,4440592,1120,4440592,1120,0,16,0,16,32,32,0,3,2080608,2240,2080608,2240,0,16,0,16,64,64,0,3,3656624,1120,3656624,1120,0,16,0,16,32,32,0,3,4440624,1120,4440624,1120,0,16,0,16,32,32,0,3,2080672,2240,2080672,2240,0,16,0,16,64,64,0,3,3656656,1120,3656656,1120,0,16,0,16,32,32,0,3,4440656,1120,4440656,1120,0,16,0,16,32,32,0,3,2080736,2240,2080736,2240,0,16,0,16,64,64,0,3,3656688,1120,3656688,1120,0,16,0,16,32,32,0,3,4440688,1120,4440688,1120,0,16,0,16,32,32,0,3,2222240,2240,2222240,2240,0,16,0,16,64,64,0,3,3691600,1120,3691600,1120,0,16,0,16,32,32,0,3,4475600,1120,4475600,1120,0,16,0,16,32,32,0,3,2222304,2240,2222304,2240,0,16,0,16,64,64,0,3,3691632,1120,3691632,1120,0,16,0,16,32,32,0,3,4475632,1120,4475632,1120,0,16,0,16,32,32,0,3,2222368,2240,2222368,2240,0,16,0,16,64,64,0,3,3691664,1120,3691664,1120,0,16,0,16,32,32,0,3,4475664,1120,4475664,1120,0,16,0,16,32,32,0,3,2222432,2240,2222432,2240,0,16,0,16,64,64,0,3,3691696,1120,3691696,1120,0,16,0,16,32,32,0,3,4475696,1120,4475696,1120,0,16,0,16,32,32,0,3,2222496,2240,2222496,2240,0,16,0,16,64,64,0,3,3691728,1120,3691728,1120,0,16,0,16,32,32,0,3,4475728,1120,4475728,1120,0,16,0,16,32,32,0,3,2222560,2240,2222560,2240,0,16,0,16,64,64,0,3,3691760,1120,3691760,1120,0,16,0,16,32,32,0,3,4475760,1120,4475760,1120,0,16,0,16,32,32,6,3,2222623,2240,2222624,2240,16,16,96,16,64,64,6,3,3691791,1120,3691792,1120,72,16,48,16,32,32,6,3,4475791,1120,4475792,1120,72,16,48,16,32,32,6,3,2222687,2240,2222688,2240,32,16,96,16,64,64,6,3,3691823,1120,3691824,1120,80,16,48,16,32,32,6,3,4475823,1120,4475824,1120,80,16,48,16,32,32,6,3,2222751,2240,2222752,2240,48,16,80,16,64,64,6,3,3691855,1120,3691856,1120,88,16,40,16,32,32,6,3,4475855,1120,4475856,1120,88,16,40,16,32,32,6,3,2222815,2240,2222816,2240,64,16,96,16,64,64,6,3,3691887,1120,3691888,1120,96,16,48,16,32,32,6,3,4475887,1120,4475888,1120,96,16,48,16,32,32,6,3,2222879,2240,2222880,2240,64,16,80,16,64,64,6,3,3691919,1120,3691920,1120,96,16,40,16,32,32,6,3,4475919,1120,4475920,1120,96,16,40,16,32,32,6,3,2222943,2240,2222944,2240,80,16,80,16,64,64,6,3,3691951,1120,3691952,1120,104,16,40,16,32,32,6,3,4475951,1120,4475952,1120,104,16,40,16,32,32,6,3,2223007,2240,2223008,2240,112,16,96,16,64,64,6,3,3691983,1120,3691984,1120,120,16,48,16,32,32,6,3,4475983,1120,4475984,1120,120,16,48,16,32,32,6,3,2223071,2240,2223072,2240,112,16,112,16,32,64,6,3,3692015,1120,3692016,1120,120,16,56,16,16,32,6,3,4476015,1120,4476016,1120,120,16,56,16,16,32,6,3,2223103,2240,2223104,2240,112,16,96,16,32,64,6,3,3692031,1120,3692032,1120,120,16,48,16,16,32,6,3,4476031,1120,4476032,1120,120,16,48,16,16,32,2,3,2223136,2240,2223136,2240,0,16,96,16,64,64,2,3,3692048,1120,3692048,1120,0,16,48,16,32,32,2,3,4476048,1120,4476048,1120,0,16,48,16,32,32,2,3,2223200,2240,2223200,2240,0,16,96,16,64,64,2,3,3692080,1120,3692080,1120,0,16,48,16,32,32,2,3,4476080,1120,4476080,1120,0,16,48,16,32,32,6,3,2223264,2240,2223264,2240,32,16,112,16,64,64,6,3,3692112,1120,3692112,1120,16,16,56,16,32,32,6,3,4476112,1120,4476112,1120,16,16,56,16,32,32,6,3,2223328,2240,2223328,2240,32,16,112,16,32,64,6,3,3692144,1120,3692144,1120,16,16,56,16,16,32,6,3,4476144,1120,4476144,1120,16,16,56,16,16,32,6,3,2223360,2240,2223360,2240,32,16,96,16,32,64,6,3,3692160,1120,3692160,1120,16,16,48,16,16,32,6,3,4476160,1120,4476160,1120,16,16,48,16,16,32,6,3,2223392,2240,2223392,2240,48,16,96,16,64,64,6,3,3692176,1120,3692176,1120,24,16,48,16,32,32,6,3,4476176,1120,4476176,1120,24,16,48,16,32,32,6,3,2223456,2240,2223456,2240,64,16,96,16,64,64,6,3,3692208,1120,3692208,1120,32,16,48,16,32,32,6,3,4476208,1120,4476208,1120,32,16,48,16,32,32,6,3,2223520,2240,2223520,2240,64,16,96,16,64,64,6,3,3692240,1120,3692240,1120,32,16,48,16,32,32,6,3,4476240,1120,4476240,1120,32,16,48,16,32,32,6,3,2223584,2240,2223584,2240,80,16,80,16,64,64,6,3,3692272,1120,3692272,1120,40,16,40,16,32,32,6,3,4476272,1120,4476272,1120,40,16,40,16,32,32,6,3,2223648,2240,2223648,2240,96,16,80,16,64,64,6,3,3692304,1120,3692304,1120,48,16,40,16,32,32,6,3,4476304,1120,4476304,1120,48,16,40,16,32,32,6,3,2223712,2240,2223712,2240,96,16,96,16,64,64,6,3,3692336,1120,3692336,1120,48,16,48,16,32,32,6,3,4476336,1120,4476336,1120,48,16,48,16,32,32,0,3,2223776,2240,2223776,2240,0,16,0,16,64,64,0,3,3692368,1120,3692368,1120,0,16,0,16,32,32,0,3,4476368,1120,4476368,1120,0,16,0,16,32,32,0,3,2223840,2240,2223840,2240,0,16,0,16,64,64,0,3,3692400,1120,3692400,1120,0,16,0,16,32,32,0,3,4476400,1120,4476400,1120,0,16,0,16,32,32,0,3,2223904,2240,2223904,2240,0,16,0,16,64,64,0,3,3692432,1120,3692432,1120,0,16,0,16,32,32,0,3,4476432,1120,4476432,1120,0,16,0,16,32,32,0,3,2223968,2240,2223968,2240,0,16,0,16,64,64,0,3,3692464,1120,3692464,1120,0,16,0,16,32,32,0,3,4476464,1120,4476464,1120,0,16,0,16,32,32,0,3,2224032,2240,2224032,2240,0,16,0,16,64,64,0,3,3692496,1120,3692496,1120,0,16,0,16,32,32,0,3,4476496,1120,4476496,1120,0,16,0,16,32,32,0,3,2224096,2240,2224096,2240,0,16,0,16,64,64,0,3,3692528,1120,3692528,1120,0,16,0,16,32,32,0,3,4476528,1120,4476528,1120,0,16,0,16,32,32,0,3,2365600,2240,2365600,2240,0,16,0,16,64,64,0,3,3727440,1120,3727440,1120,0,16,0,16,32,32,0,3,4511440,1120,4511440,1120,0,16,0,16,32,32,0,3,2365664,2240,2365664,2240,0,16,0,16,64,64,0,3,3727472,1120,3727472,1120,0,16,0,16,32,32,0,3,4511472,1120,4511472,1120,0,16,0,16,32,32,0,3,2365728,2240,2365728,2240,0,16,0,16,64,64,0,3,3727504,1120,3727504,1120,0,16,0,16,32,32,0,3,4511504,1120,4511504,1120,0,16,0,16,32,32,0,3,2365792,2240,2365792,2240,0,16,0,16,64,64,0,3,3727536,1120,3727536,1120,0,16,0,16,32,32,0,3,4511536,1120,4511536,1120,0,16,0,16,32,32,0,3,2365856,2240,2365856,2240,0,16,0,16,64,64,0,3,3727568,1120,3727568,1120,0,16,0,16,32,32,0,3,4511568,1120,4511568,1120,0,16,0,16,32,32,0,3,2365920,2240,2365920,2240,0,16,0,16,64,64,0,3,3727600,1120,3727600,1120,0,16,0,16,32,32,0,3,4511600,1120,4511600,1120,0,16,0,16,32,32,6,3,2365983,2240,2365984,2240,16,16,96,16,64,64,6,3,3727631,1120,3727632,1120,72,16,48,16,32,32,6,3,4511631,1120,4511632,1120,72,16,48,16,32,32,6,3,2366047,2240,2366048,2240,32,16,96,16,64,64,6,3,3727663,1120,3727664,1120,80,16,48,16,32,32,6,3,4511663,1120,4511664,1120,80,16,48,16,32,32,6,3,2366111,2240,2366112,2240,48,16,80,16,64,64,6,3,3727695,1120,3727696,1120,88,16,40,16,32,32,6,3,4511695,1120,4511696,1120,88,16,40,16,32,32,6,3,2366175,2240,2366176,2240,64,16,96,16,64,64,6,3,3727727,1120,3727728,1120,96,16,48,16,32,32,6,3,4511727,1120,4511728,1120,96,16,48,16,32,32,6,3,2366239,2240,2366240,2240,64,16,80,16,64,64,6,3,3727759,1120,3727760,1120,96,16,40,16,32,32,6,3,4511759,1120,4511760,1120,96,16,40,16,32,32,6,3,2366303,2240,2366304,2240,80,16,80,16,64,64,6,3,3727791,1120,3727792,1120,104,16,40,16,32,32,6,3,4511791,1120,4511792,1120,104,16,40,16,32,32,6,3,2366367,2240,2366368,2240,96,16,112,16,64,64,6,3,3727823,1120,3727824,1120,112,16,56,16,32,32,6,3,4511823,1120,4511824,1120,112,16,56,16,32,32,6,3,2366431,2240,2366432,2240,112,16,112,16,64,64,6,3,3727855,1120,3727856,1120,120,16,56,16,32,32,6,3,4511855,1120,4511856,1120,120,16,56,16,32,32,6,3,2366495,2240,2366496,2240,112,16,112,16,8,16,6,3,3727887,1120,3727888,1120,120,16,56,16,4,8,6,3,4511887,1120,4511888,1120,120,16,56,16,4,8,2,3,2366504,2240,2366504,2240,0,16,96,16,8,16,2,3,3727892,1120,3727892,1120,0,16,48,16,4,8,2,3,4511892,1120,4511892,1120,0,16,48,16,4,8,2,3,2366512,2240,2366512,2240,0,16,112,16,16,16,2,3,3727896,1120,3727896,1120,0,16,56,16,8,8,2,3,4511896,1120,4511896,1120,0,16,56,16,8,8,6,3,2402335,2240,2402336,2240,112,16,96,16,16,16,6,3,3736847,1120,3736848,1120,120,16,48,16,8,8,6,3,4520847,1120,4520848,1120,120,16,48,16,8,8,2,3,2402352,2240,2402352,2240,0,16,112,16,16,16,2,3,3736856,1120,3736856,1120,0,16,56,16,8,8,2,3,4520856,1120,4520856,1120,0,16,56,16,8,8,2,3,2366528,2240,2366528,2240,0,16,112,16,32,32,2,3,3727904,1120,3727904,1120,0,16,56,16,16,16,2,3,4511904,1120,4511904,1120,0,16,56,16,16,16,6,3,2438175,2240,2438176,2240,112,16,96,16,32,32,6,3,3745807,1120,3745808,1120,120,16,48,16,16,16,6,3,4529807,1120,4529808,1120,120,16,48,16,16,16,2,3,2438208,2240,2438208,2240,0,16,112,16,32,32,2,3,3745824,1120,3745824,1120,0,16,56,16,16,16,2,3,4529824,1120,4529824,1120,0,16,56,16,16,16,2,3,2366560,2240,2366560,2240,0,16,112,16,32,32,2,3,3727920,1120,3727920,1120,0,16,56,16,16,16,2,3,4511920,1120,4511920,1120,0,16,56,16,16,16,2,3,2366592,2240,2366592,2240,0,16,112,16,16,16,2,3,3727936,1120,3727936,1120,0,16,56,16,8,8,2,3,4511936,1120,4511936,1120,0,16,56,16,8,8,6,3,2366608,2240,2366608,2240,16,16,112,16,16,8,6,3,3727944,1120,3727944,1120,8,16,56,16,8,4,6,3,4511944,1120,4511944,1120,8,16,56,16,8,4,0,3,2386768,2240,2384528,2240,0,16,0,16,16,8,2,3,3732424,1120,3732424,1120,0,16,64,16,8,4,2,3,4516424,1120,4516424,1120,0,16,64,16,8,4,2,3,2402432,2240,2402432,2240,0,16,112,16,16,16,2,3,3736896,1120,3736896,1120,0,16,56,16,8,8,2,3,4520896,1120,4520896,1120,0,16,56,16,8,8,2,3,2402448,2240,2402448,2240,0,16,112,16,16,16,2,3,3736904,1120,3736904,1120,0,16,56,16,8,8,2,3,4520904,1120,4520904,1120,0,16,56,16,8,8,2,3,2438240,2240,2438240,2240,0,16,112,16,32,32,2,3,3745840,1120,3745840,1120,0,16,56,16,16,16,2,3,4529840,1120,4529840,1120,0,16,56,16,16,16,2,3,2438272,2240,2438272,2240,0,16,112,16,32,32,2,3,3745856,1120,3745856,1120,0,16,56,16,16,16,2,3,4529856,1120,4529856,1120,0,16,56,16,16,16,6,3,2366624,2240,2366624,2240,16,16,112,16,32,64,6,3,3727952,1120,3727952,1120,8,16,56,16,16,32,6,3,4511952,1120,4511952,1120,8,16,56,16,16,32,6,3,2366656,2240,2366656,2240,16,16,96,16,32,64,6,3,3727968,1120,3727968,1120,8,16,48,16,16,32,6,3,4511968,1120,4511968,1120,8,16,48,16,16,32,6,3,2366688,2240,2366688,2240,32,16,112,16,64,64,6,3,3727984,1120,3727984,1120,16,16,56,16,32,32,6,3,4511984,1120,4511984,1120,16,16,56,16,32,32,6,3,2366752,2240,2366752,2240,48,16,96,16,64,64,6,3,3728016,1120,3728016,1120,24,16,48,16,32,32,6,3,4512016,1120,4512016,1120,24,16,48,16,32,32,6,3,2366816,2240,2366816,2240,64,16,96,16,64,64,6,3,3728048,1120,3728048,1120,32,16,48,16,32,32,6,3,4512048,1120,4512048,1120,32,16,48,16,32,32,6,3,2366880,2240,2366880,2240,64,16,96,16,64,64,6,3,3728080,1120,3728080,1120,32,16,48,16,32,32,6,3,4512080,1120,4512080,1120,32,16,48,16,32,32,6,3,2366944,2240,2366944,2240,80,16,80,16,64,64,6,3,3728112,1120,3728112,1120,40,16,40,16,32,32,6,3,4512112,1120,4512112,1120,40,16,40,16,32,32,6,3,2367008,2240,2367008,2240,96,16,80,16,64,64,6,3,3728144,1120,3728144,1120,48,16,40,16,32,32,6,3,4512144,1120,4512144,1120,48,16,40,16,32,32,6,3,2367072,2240,2367072,2240,96,16,96,16,64,64,6,3,3728176,1120,3728176,1120,48,16,48,16,32,32,6,3,4512176,1120,4512176,1120,48,16,48,16,32,32,0,3,2367136,2240,2367136,2240,0,16,0,16,64,64,0,3,3728208,1120,3728208,1120,0,16,0,16,32,32,0,3,4512208,1120,4512208,1120,0,16,0,16,32,32,0,3,2367200,2240,2367200,2240,0,16,0,16,64,64,0,3,3728240,1120,3728240,1120,0,16,0,16,32,32,0,3,4512240,1120,4512240,1120,0,16,0,16,32,32,0,3,2367264,2240,2367264,2240,0,16,0,16,64,64,0,3,3728272,1120,3728272,1120,0,16,0,16,32,32,0,3,4512272,1120,4512272,1120,0,16,0,16,32,32,0,3,2367328,2240,2367328,2240,0,16,0,16,64,64,0,3,3728304,1120,3728304,1120,0,16,0,16,32,32,0,3,4512304,1120,4512304,1120,0,16,0,16,32,32,0,3,2367392,2240,2367392,2240,0,16,0,16,64,64,0,3,3728336,1120,3728336,1120,0,16,0,16,32,32,0,3,4512336,1120,4512336,1120,0,16,0,16,32,32,0,3,2367456,2240,2367456,2240,0,16,0,16,64,64,0,3,3728368,1120,3728368,1120,0,16,0,16,32,32,0,3,4512368,1120,4512368,1120,0,16,0,16,32,32,0,3,2508960,2240,2508960,2240,0,16,0,16,64,64,0,3,3763280,1120,3763280,1120,0,16,0,16,32,32,0,3,4547280,1120,4547280,1120,0,16,0,16,32,32,0,3,2509024,2240,2509024,2240,0,16,0,16,64,64,0,3,3763312,1120,3763312,1120,0,16,0,16,32,32,0,3,4547312,1120,4547312,1120,0,16,0,16,32,32,0,3,2509088,2240,2509088,2240,0,16,0,16,64,64,0,3,3763344,1120,3763344,1120,0,16,0,16,32,32,0,3,4547344,1120,4547344,1120,0,16,0,16,32,32,0,3,2509152,2240,2509152,2240,0,16,0,16,64,64,0,3,3763376,1120,3763376,1120,0,16,0,16,32,32,0,3,4547376,1120,4547376,1120,0,16,0,16,32,32,0,3,2509216,2240,2509216,2240,0,16,0,16,64,64,0,3,3763408,1120,3763408,1120,0,16,0,16,32,32,0,3,4547408,1120,4547408,1120,0,16,0,16,32,32,0,3,2509280,2240,2509280,2240,0,16,0,16,64,64,0,3,3763440,1120,3763440,1120,0,16,0,16,32,32,0,3,4547440,1120,4547440,1120,0,16,0,16,32,32,6,3,2509343,2240,2509344,2240,16,16,96,16,64,64,6,3,3763471,1120,3763472,1120,72,16,48,16,32,32,6,3,4547471,1120,4547472,1120,72,16,48,16,32,32,6,3,2509407,2240,2509408,2240,32,16,96,16,64,64,6,3,3763503,1120,3763504,1120,80,16,48,16,32,32,6,3,4547503,1120,4547504,1120,80,16,48,16,32,32,6,3,2509471,2240,2509472,2240,48,16,80,16,64,64,6,3,3763535,1120,3763536,1120,88,16,40,16,32,32,6,3,4547535,1120,4547536,1120,88,16,40,16,32,32,6,3,2509535,2240,2509536,2240,64,16,96,16,64,64,6,3,3763567,1120,3763568,1120,96,16,48,16,32,32,6,3,4547567,1120,4547568,1120,96,16,48,16,32,32,6,3,2509599,2240,2509600,2240,64,16,80,16,64,64,6,3,3763599,1120,3763600,1120,96,16,40,16,32,32,6,3,4547599,1120,4547600,1120,96,16,40,16,32,32,6,3,2509663,2240,2509664,2240,80,16,80,16,64,64,6,3,3763631,1120,3763632,1120,104,16,40,16,32,32,6,3,4547631,1120,4547632,1120,104,16,40,16,32,32,6,3,2509727,2240,2509728,2240,96,16,112,16,64,64,6,3,3763663,1120,3763664,1120,112,16,56,16,32,32,6,3,4547663,1120,4547664,1120,112,16,56,16,32,32,6,3,2509791,2240,2509792,2240,112,16,112,16,64,64,6,3,3763695,1120,3763696,1120,120,16,56,16,32,32,6,3,4547695,1120,4547696,1120,120,16,56,16,32,32,6,3,2509855,2240,2509856,2240,112,16,96,16,64,64,6,3,3763727,1120,3763728,1120,120,16,48,16,32,32,6,3,4547727,1120,4547728,1120,120,16,48,16,32,32,2,3,2509920,2240,2509920,2240,0,16,112,16,64,64,2,3,3763760,1120,3763760,1120,0,16,56,16,32,32,2,3,4547760,1120,4547760,1120,0,16,56,16,32,32,6,3,2509984,2240,2509984,2240,16,16,112,16,64,64,6,3,3763792,1120,3763792,1120,8,16,56,16,32,32,6,3,4547792,1120,4547792,1120,8,16,56,16,32,32,6,3,2510048,2240,2510048,2240,32,16,112,16,64,64,6,3,3763824,1120,3763824,1120,16,16,56,16,32,32,6,3,4547824,1120,4547824,1120,16,16,56,16,32,32,6,3,2510112,2240,2510112,2240,48,16,96,16,64,64,6,3,3763856,1120,3763856,1120,24,16,48,16,32,32,6,3,4547856,1120,4547856,1120,24,16,48,16,32,32,6,3,2510176,2240,2510176,2240,64,16,96,16,64,64,6,3,3763888,1120,3763888,1120,32,16,48,16,32,32,6,3,4547888,1120,4547888,1120,32,16,48,16,32,32,6,3,2510240,2240,2510240,2240,64,16,96,16,64,64,6,3,3763920,1120,3763920,1120,32,16,48,16,32,32,6,3,4547920,1120,4547920,1120,32,16,48,16,32,32,6,3,2510304,2240,2510304,2240,80,16,80,16,64,64,6,3,3763952,1120,3763952,1120,40,16,40,16,32,32,6,3,4547952,1120,4547952,1120,40,16,40,16,32,32,6,3,2510368,2240,2510368,2240,96,16,80,16,64,64,6,3,3763984,1120,3763984,1120,48,16,40,16,32,32,6,3,4547984,1120,4547984,1120,48,16,40,16,32,32,6,3,2510432,2240,2510432,2240,96,16,96,16,64,64,6,3,3764016,1120,3764016,1120,48,16,48,16,32,32,6,3,4548016,1120,4548016,1120,48,16,48,16,32,32,0,3,2510496,2240,2510496,2240,0,16,0,16,64,64,0,3,3764048,1120,3764048,1120,0,16,0,16,32,32,0,3,4548048,1120,4548048,1120,0,16,0,16,32,32,0,3,2510560,2240,2510560,2240,0,16,0,16,64,64,0,3,3764080,1120,3764080,1120,0,16,0,16,32,32,0,3,4548080,1120,4548080,1120,0,16,0,16,32,32,0,3,2510624,2240,2510624,2240,0,16,0,16,64,64,0,3,3764112,1120,3764112,1120,0,16,0,16,32,32,0,3,4548112,1120,4548112,1120,0,16,0,16,32,32,0,3,2510688,2240,2510688,2240,0,16,0,16,64,64,0,3,3764144,1120,3764144,1120,0,16,0,16,32,32,0,3,4548144,1120,4548144,1120,0,16,0,16,32,32,0,3,2510752,2240,2510752,2240,0,16,0,16,64,64,0,3,3764176,1120,3764176,1120,0,16,0,16,32,32,0,3,4548176,1120,4548176,1120,0,16,0,16,32,32,0,3,2510816,2240,2510816,2240,0,16,0,16,64,64,0,3,3764208,1120,3764208,1120,0,16,0,16,32,32,0,3,4548208,1120,4548208,1120,0,16,0,16,32,32,0,3,2652320,2240,2652320,2240,0,16,0,16,64,64,4,0,825488,2240,825488,2240,64,16,0,16,8,8,4,0,3342664,1120,3342664,1120,32,16,0,16,4,4,4,0,4126664,1120,4126664,1120,32,16,0,16,4,4,4,0,2077727,2240,2079968,2240,64,16,0,16,32,64,6,0,3655183,1120,3656304,1120,96,16,64,16,16,32,6,0,4439183,1120,4440304,1120,96,16,64,16,16,32,6,0,2077903,2240,2080144,2240,16,16,32,16,16,32,6,0,3655271,1120,3656392,1120,72,16,80,16,8,16,6,0,4439271,1120,4440392,1120,72,16,80,16,8,16,6,0,2149583,2240,2151824,2240,16,16,32,16,16,16,6,0,3673191,1120,3674312,1120,72,16,80,16,8,8,6,0,4457191,1120,4458312,1120,72,16,80,16,8,8,6,0,2185423,2240,2187664,2240,16,16,32,16,16,16,6,0,3682151,1120,3683272,1120,72,16,80,16,8,8,6,0,4466151,1120,4467272,1120,72,16,80,16,8,8,6,0,2077983,2240,2080224,2240,304,16,304,16,32,64,6,0,3655311,1120,3656432,1120,344,16,344,16,16,32,6,0,4439311,1120,4440432,1120,344,16,344,16,16,32,
diff --git a/tests/res/values/attrs.xml b/tests/res/values/attrs.xml
index 404427d..4c3d9db 100644
--- a/tests/res/values/attrs.xml
+++ b/tests/res/values/attrs.xml
@@ -129,4 +129,17 @@
<attr name="themeType" format="integer"/>
<!-- Theme reference used to override parent theme. -->
<attr name="themeOverrideAttr" format="reference"/>
+
+ <!-- Drawable theming attributes -->
+ <attr name="themeBoolean" />
+ <attr name="themeColor" />
+ <attr name="themeFloat" />
+ <attr name="themeInteger" />
+ <attr name="themeDimension" />
+ <attr name="themeDrawable" />
+ <attr name="themeBitmap" />
+ <attr name="themeNinePatch" />
+ <attr name="themeGravity" />
+ <attr name="themeTileMode" />
+ <attr name="themeAngle" />
</resources>
diff --git a/tests/res/values/styles.xml b/tests/res/values/styles.xml
index daaed48..47238a3 100644
--- a/tests/res/values/styles.xml
+++ b/tests/res/values/styles.xml
@@ -150,5 +150,19 @@
<style name="Theme_OverrideAttr">
<item name="themeType">3</item>
</style>
+
+ <style name="Theme_ThemedDrawableTest">
+ <item name="themeBoolean">true</item>
+ <item name="themeColor">@android:color/black</item>
+ <item name="themeFloat">1.0</item>
+ <item name="themeAngle">45.0</item>
+ <item name="themeInteger">1</item>
+ <item name="themeDimension">1px</item>
+ <item name="themeDrawable">@drawable/icon_black</item>
+ <item name="themeBitmap">@drawable/icon_black</item>
+ <item name="themeNinePatch">@drawable/ninepatch_0</item>
+ <item name="themeGravity">48</item>
+ <item name="themeTileMode">2</item>
+ </style>
</resources>
diff --git a/tests/src/android/webkit/cts/WebViewOnUiThread.java b/tests/src/android/webkit/cts/WebViewOnUiThread.java
index 6e485c3..c1032a6 100644
--- a/tests/src/android/webkit/cts/WebViewOnUiThread.java
+++ b/tests/src/android/webkit/cts/WebViewOnUiThread.java
@@ -241,6 +241,15 @@
});
}
+ public boolean canZoomIn() {
+ return getValue(new ValueGetter<Boolean>() {
+ @Override
+ public Boolean capture() {
+ return mWebView.canZoomIn();
+ }
+ });
+ }
+
public boolean zoomIn() {
return getValue(new ValueGetter<Boolean>() {
@Override
diff --git a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
index 7e05e32..81db5be 100644
--- a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
+++ b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
@@ -46,10 +46,8 @@
/**
* Creates a new instance for testing {@link AccessibilityEndToEndActivity}.
- *
- * @throws Exception If any error occurs.
*/
- public AccessibilityEndToEndTest() throws Exception {
+ public AccessibilityEndToEndTest() {
super(AccessibilityEndToEndActivity.class);
}
diff --git a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
index 0ea9a7f..6a7ac62 100644
--- a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
+++ b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
@@ -31,6 +31,7 @@
import android.test.suitebuilder.annotation.MediumTest;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityWindowInfo;
import com.android.cts.accessibilityservice.R;
@@ -47,6 +48,8 @@
public class AccessibilityWindowQueryTest
extends AccessibilityActivityTestCase<AccessibilityWindowQueryActivity> {
+ private static final long TIMEOUT_WINDOW_STATE_IDLE = 500;
+
public AccessibilityWindowQueryTest() {
super(AccessibilityWindowQueryActivity.class);
}
@@ -70,55 +73,239 @@
@MediumTest
public void testTraverseWindow() throws Exception {
- try {
- AccessibilityServiceInfo info = getInstrumentation().getUiAutomation().getServiceInfo();
- info.flags |= AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
- getInstrumentation().getUiAutomation().setServiceInfo(info);
+ verifyNodesInAppWindow(getInstrumentation().getUiAutomation().getRootInActiveWindow());
+ }
- // make list of expected nodes
- List<String> classNameAndTextList = new ArrayList<String>();
- classNameAndTextList.add("android.widget.FrameLayout");
- classNameAndTextList.add("android.widget.LinearLayout");
- classNameAndTextList.add("android.widget.FrameLayout");
- classNameAndTextList.add("android.widget.LinearLayout");
- classNameAndTextList.add("android.widget.LinearLayout");
- classNameAndTextList.add("android.widget.LinearLayout");
- classNameAndTextList.add("android.widget.LinearLayout");
- classNameAndTextList.add("android.widget.ButtonButton1");
- classNameAndTextList.add("android.widget.ButtonButton2");
- classNameAndTextList.add("android.widget.ButtonButton3");
- classNameAndTextList.add("android.widget.ButtonButton4");
- classNameAndTextList.add("android.widget.ButtonButton5");
- classNameAndTextList.add("android.widget.ButtonButton6");
- classNameAndTextList.add("android.widget.ButtonButton7");
- classNameAndTextList.add("android.widget.ButtonButton8");
- classNameAndTextList.add("android.widget.ButtonButton9");
+ @MediumTest
+ public void testNoWindowsAccessIfFlagNotSet() throws Exception {
+ // Make sure the windows cannot be accessed.
+ UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+ assertTrue(uiAutomation.getWindows().isEmpty());
- Queue<AccessibilityNodeInfo> fringe = new LinkedList<AccessibilityNodeInfo>();
- fringe.add(getInstrumentation().getUiAutomation().getRootInActiveWindow());
+ // Find a button to click on.
+ final AccessibilityNodeInfo button1 = uiAutomation.getRootInActiveWindow()
+ .findAccessibilityNodeInfosByViewId(
+ "com.android.cts.accessibilityservice:id/button1").get(0);
- // do a BFS traversal and check nodes
- while (!fringe.isEmpty()) {
- AccessibilityNodeInfo current = fringe.poll();
+ // Argh...
+ final List<AccessibilityEvent> events = new ArrayList<AccessibilityEvent>();
- CharSequence text = current.getText();
- String receivedClassNameAndText = current.getClassName().toString()
- + ((text != null) ? text.toString() : "");
- String expectedClassNameAndText = classNameAndTextList.remove(0);
-
- assertEquals("Did not get the expected node info",
- expectedClassNameAndText, receivedClassNameAndText);
-
- final int childCount = current.getChildCount();
- for (int i = 0; i < childCount; i++) {
- AccessibilityNodeInfo child = current.getChild(i);
- fringe.add(child);
- }
+ // Click the button.
+ uiAutomation.executeAndWaitForEvent(new Runnable() {
+ @Override
+ public void run() {
+ button1.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
+ },
+ new UiAutomation.AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent event) {
+ if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_CLICKED) {
+ events.add(event);
+ return true;
+ }
+ return false;
+ }
+ },
+ TIMEOUT_ASYNC_PROCESSING);
+
+ // Make sure the source window cannot be accessed.
+ AccessibilityEvent event = events.get(0);
+ assertNull(event.getSource().getWindow());
+ }
+
+ @MediumTest
+ public void testTraverseAllWindows() throws Exception {
+ setAccessInteractiveWindowsFlag();
+ try {
+ final UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+
+ getInstrumentation().getUiAutomation().waitForIdle(
+ TIMEOUT_WINDOW_STATE_IDLE,
+ TIMEOUT_ASYNC_PROCESSING);
+
+ List<AccessibilityWindowInfo> windows = uiAutomation.getWindows();
+
+ Rect boundsInScreen = new Rect();
+
+ // Verify the navigation bar window.
+ AccessibilityWindowInfo navBarWindow = windows.get(0);
+ navBarWindow.getBoundsInScreen(boundsInScreen);
+ assertFalse(boundsInScreen.isEmpty()); // Varies on screen size, just emptiness check.
+ assertSame(navBarWindow.getType(), AccessibilityWindowInfo.TYPE_SYSTEM);
+ assertFalse(navBarWindow.isFocused());
+ assertFalse(navBarWindow.isActive());
+ assertNull(navBarWindow.getParent());
+ assertSame(0, navBarWindow.getChildCount());
+ assertNotNull(navBarWindow.getRoot());
+
+ // Verify the status bar window.
+ AccessibilityWindowInfo statusBarWindow = windows.get(1);
+ statusBarWindow.getBoundsInScreen(boundsInScreen);
+ assertFalse(boundsInScreen.isEmpty()); // Varies on screen size, just emptiness check.
+ assertSame(statusBarWindow.getType(), AccessibilityWindowInfo.TYPE_SYSTEM);
+ assertFalse(statusBarWindow.isFocused());
+ assertFalse(statusBarWindow.isActive());
+ assertNull(statusBarWindow.getParent());
+ assertSame(0, statusBarWindow.getChildCount());
+ assertNotNull(statusBarWindow.getRoot());
+
+ // Verify the application window.
+ AccessibilityWindowInfo appWindow = windows.get(2);
+ appWindow.getBoundsInScreen(boundsInScreen);
+ assertFalse(boundsInScreen.isEmpty()); // Varies on screen size, just emptiness check.
+ assertSame(appWindow.getType(), AccessibilityWindowInfo.TYPE_APPLICATION);
+ assertTrue(appWindow.isFocused());
+ assertTrue(appWindow.isActive());
+ assertNull(appWindow.getParent());
+ assertSame(0, appWindow.getChildCount());
+ assertNotNull(appWindow.getRoot());
+
+ verifyNodesInAppWindow(appWindow.getRoot());
} finally {
- AccessibilityServiceInfo info = getInstrumentation().getUiAutomation().getServiceInfo();
- info.flags &= ~AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
- getInstrumentation().getUiAutomation().setServiceInfo(info);
+ clearAccessInteractiveWindowsFlag();
+ }
+ }
+
+ @MediumTest
+ public void testTraverseWindowFromEvent() throws Exception {
+ setAccessInteractiveWindowsFlag();
+ try {
+ UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+
+ // Find a button to click on.
+ final AccessibilityNodeInfo button1 = uiAutomation.getRootInActiveWindow()
+ .findAccessibilityNodeInfosByViewId(
+ "com.android.cts.accessibilityservice:id/button1").get(0);
+
+ // Argh...
+ final List<AccessibilityEvent> events = new ArrayList<AccessibilityEvent>();
+
+ // Click the button.
+ uiAutomation.executeAndWaitForEvent(new Runnable() {
+ @Override
+ public void run() {
+ button1.performAction(AccessibilityNodeInfo.ACTION_CLICK);
+ }
+ },
+ new UiAutomation.AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent event) {
+ if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_CLICKED) {
+ events.add(event);
+ return true;
+ }
+ return false;
+ }
+ },
+ TIMEOUT_ASYNC_PROCESSING);
+
+ // Get the source window.
+ AccessibilityEvent event = events.get(0);
+ AccessibilityWindowInfo window = event.getSource().getWindow();
+
+ // Verify the application window.
+ Rect boundsInScreen = new Rect();
+ window.getBoundsInScreen(boundsInScreen);
+ assertFalse(boundsInScreen.isEmpty()); // Varies on screen size, so just emptiness check.
+ assertSame(window.getType(), AccessibilityWindowInfo.TYPE_APPLICATION);
+ assertTrue(window.isFocused());
+ assertTrue(window.isActive());
+ assertNull(window.getParent());
+ assertSame(0, window.getChildCount());
+ assertNotNull(window.getRoot());
+
+ // Verify the window content.
+ verifyNodesInAppWindow(window.getRoot());
+ } finally {
+ clearAccessInteractiveWindowsFlag();
+ }
+ }
+
+ @MediumTest
+ public void testInteractWithAppWindow() throws Exception {
+ setAccessInteractiveWindowsFlag();
+ try {
+ UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+
+ // Find a button to click on.
+ final AccessibilityNodeInfo button1 = uiAutomation.getRootInActiveWindow()
+ .findAccessibilityNodeInfosByViewId(
+ "com.android.cts.accessibilityservice:id/button1").get(0);
+
+ // Argh...
+ final List<AccessibilityEvent> events = new ArrayList<AccessibilityEvent>();
+
+ // Click the button.
+ uiAutomation.executeAndWaitForEvent(new Runnable() {
+ @Override
+ public void run() {
+ button1.performAction(AccessibilityNodeInfo.ACTION_CLICK);
+ }
+ },
+ new UiAutomation.AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent event) {
+ if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_CLICKED) {
+ events.add(event);
+ return true;
+ }
+ return false;
+ }
+ },
+ TIMEOUT_ASYNC_PROCESSING);
+
+ // Get the source window.
+ AccessibilityEvent event = events.get(0);
+ AccessibilityWindowInfo window = event.getSource().getWindow();
+
+ // Find a another button from the event's window.
+ final AccessibilityNodeInfo button2 = window.getRoot()
+ .findAccessibilityNodeInfosByViewId(
+ "com.android.cts.accessibilityservice:id/button2").get(0);
+
+ // Click the second button.
+ uiAutomation.executeAndWaitForEvent(new Runnable() {
+ @Override
+ public void run() {
+ button2.performAction(AccessibilityNodeInfo.ACTION_CLICK);
+ }
+ },
+ new UiAutomation.AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent event) {
+ return event.getEventType() == AccessibilityEvent.TYPE_VIEW_CLICKED;
+ }
+ },
+ TIMEOUT_ASYNC_PROCESSING);
+ } finally {
+ clearAccessInteractiveWindowsFlag();
+ }
+ }
+
+ @MediumTest
+ public void testInteractWithNavBarWindow() throws Exception {
+ setAccessInteractiveWindowsFlag();
+ try {
+ UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+ AccessibilityWindowInfo window = uiAutomation.getWindows().get(0);
+ assertTrue(window.getRoot().performAction(
+ AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS));
+ } finally {
+ clearAccessInteractiveWindowsFlag();
+ }
+ }
+
+ @MediumTest
+ public void testInteractWithStatusBarWindow() throws Exception {
+ setAccessInteractiveWindowsFlag();
+ try {
+ UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+ AccessibilityWindowInfo window = uiAutomation.getWindows().get(1);
+ assertTrue(window.getRoot().performAction(
+ AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS));
+ } finally {
+ clearAccessInteractiveWindowsFlag();
}
}
@@ -420,6 +607,75 @@
}
}
+ private void setAccessInteractiveWindowsFlag () {
+ UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+ AccessibilityServiceInfo info = uiAutomation.getServiceInfo();
+ info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
+ uiAutomation.setServiceInfo(info);
+ }
+
+ private void clearAccessInteractiveWindowsFlag () {
+ UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+ AccessibilityServiceInfo info = uiAutomation.getServiceInfo();
+ info.flags &= ~AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
+ uiAutomation.setServiceInfo(info);
+ }
+
+ private void verifyNodesInAppWindow(AccessibilityNodeInfo root) throws Exception {
+ try {
+ AccessibilityServiceInfo info = getInstrumentation().getUiAutomation().getServiceInfo();
+ info.flags |= AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
+ getInstrumentation().getUiAutomation().setServiceInfo(info);
+
+ root.refresh();
+
+ // make list of expected nodes
+ List<String> classNameAndTextList = new ArrayList<String>();
+ classNameAndTextList.add("android.widget.FrameLayout");
+ classNameAndTextList.add("android.widget.LinearLayout");
+ classNameAndTextList.add("android.widget.FrameLayout");
+ classNameAndTextList.add("android.widget.LinearLayout");
+ classNameAndTextList.add("android.widget.LinearLayout");
+ classNameAndTextList.add("android.widget.LinearLayout");
+ classNameAndTextList.add("android.widget.LinearLayout");
+ classNameAndTextList.add("android.widget.ButtonButton1");
+ classNameAndTextList.add("android.widget.ButtonButton2");
+ classNameAndTextList.add("android.widget.ButtonButton3");
+ classNameAndTextList.add("android.widget.ButtonButton4");
+ classNameAndTextList.add("android.widget.ButtonButton5");
+ classNameAndTextList.add("android.widget.ButtonButton6");
+ classNameAndTextList.add("android.widget.ButtonButton7");
+ classNameAndTextList.add("android.widget.ButtonButton8");
+ classNameAndTextList.add("android.widget.ButtonButton9");
+
+ Queue<AccessibilityNodeInfo> fringe = new LinkedList<AccessibilityNodeInfo>();
+ fringe.add(root);
+
+ // do a BFS traversal and check nodes
+ while (!fringe.isEmpty()) {
+ AccessibilityNodeInfo current = fringe.poll();
+
+ CharSequence text = current.getText();
+ String receivedClassNameAndText = current.getClassName().toString()
+ + ((text != null) ? text.toString() : "");
+ String expectedClassNameAndText = classNameAndTextList.remove(0);
+
+ assertEquals("Did not get the expected node info",
+ expectedClassNameAndText, receivedClassNameAndText);
+
+ final int childCount = current.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ AccessibilityNodeInfo child = current.getChild(i);
+ fringe.add(child);
+ }
+ }
+ } finally {
+ AccessibilityServiceInfo info = getInstrumentation().getUiAutomation().getServiceInfo();
+ info.flags &= ~AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
+ getInstrumentation().getUiAutomation().setServiceInfo(info);
+ }
+ }
+
@Override
protected void scrubClass(Class<?> testCaseClass) {
/* intentionally do not scrub */
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/GradientDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/GradientDrawableTest.java
index 0a19cb7..75639c2 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/GradientDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/GradientDrawableTest.java
@@ -18,11 +18,11 @@
import com.android.cts.stub.R;
-
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import android.content.res.Resources;
+import android.content.res.Resources.Theme;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.PixelFormat;
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/ThemedDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/ThemedDrawableTest.java
new file mode 100644
index 0000000..c8a5e24
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/ThemedDrawableTest.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.drawable.cts;
+
+import android.annotation.TargetApi;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.Shader.TileMode;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.LayerDrawable;
+import android.graphics.drawable.NinePatchDrawable;
+import android.graphics.drawable.TouchFeedbackDrawable;
+import android.test.AndroidTestCase;
+import android.util.SparseIntArray;
+import android.view.ContextThemeWrapper;
+import android.view.Gravity;
+
+import com.android.cts.stub.R;
+
+@TargetApi(19)
+public class ThemedDrawableTest extends AndroidTestCase {
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mContext.setTheme(R.style.Theme_ThemedDrawableTest);
+ }
+
+ public void testBitmapDrawable() {
+ BitmapDrawable d = (BitmapDrawable) mContext.getDrawable(R.drawable.bitmapdrawable_theme);
+
+ internalTestBitmapDrawable(d);
+ }
+
+ private void internalTestBitmapDrawable(BitmapDrawable d) {
+ assertEquals(true, d.hasAntiAlias());
+ assertEquals(true, d.isAutoMirrored());
+ // assertEquals(true, d.hasDither());
+ // assertEquals(true, d.hasFilter());
+ assertEquals(Gravity.TOP, d.getGravity());
+ assertEquals(true, d.hasMipMap());
+ assertNotNull(d.getBitmap());
+ assertEquals(TileMode.MIRROR, d.getTileModeX());
+ assertEquals(TileMode.MIRROR, d.getTileModeY());
+ }
+
+ public void testColorDrawable() {
+ ColorDrawable d = (ColorDrawable) mContext.getDrawable(R.drawable.colordrawable_theme);
+
+ assertEquals(Color.BLACK, d.getColor());
+ }
+
+ public void testGradientDrawable() {
+ GradientDrawable d = (GradientDrawable) mContext.getDrawable(
+ R.drawable.gradientdrawable_theme);
+
+ // Corners
+ // assertEquals(1, d.getCornerRadius(0));
+ // assertEquals(1, d.getCornerRadius(1));
+ // assertEquals(1, d.getCornerRadius(2));
+ // assertEquals(1, d.getCornerRadius(3));
+
+ // Gradient
+ // int[] colors = d.getColors(null);
+ // for (int i = 0; i < color.length; i++) {
+ // assertEquals(Color.BLACK, colors[i]);
+ // }
+ // assertEquals(1.0f, d.getGradientAngle());
+ // assertEquals(1.0, d.getGradientCenterX());
+ // assertEquals(1.0, d.getGradientCenterY());
+ // assertEquals(1.0, d.getGradientRadius());
+ // assertEquals(false, d.getUseLevel());
+
+ // Padding
+ Rect padding = new Rect();
+ assertTrue(d.getPadding(padding));
+ assertEquals(1, padding.left);
+ assertEquals(1, padding.top);
+ assertEquals(1, padding.bottom);
+ assertEquals(1, padding.right);
+
+ // Size
+ assertEquals(1, d.getIntrinsicHeight());
+ assertEquals(1, d.getIntrinsicWidth());
+
+ // Solid
+ // assertEquals(true, d.hasSolidColor());
+ // assertEquals(Color.BLACK, d.getColor());
+
+ // Stroke
+ // assertEquals(1.0, d.getStrokeWidth());
+ // assertEquals(Color.BLACK, d.getStrokeColor());
+ // assertEquals(1.0, d.getStrokeDashWidth());
+ // assertEquals(1.0, d.getStrokeDashGap());
+ }
+
+ public void testNinePatchDrawable() {
+ NinePatchDrawable d = (NinePatchDrawable) mContext.getDrawable(
+ R.drawable.ninepatchdrawable_theme);
+
+ internalTestNinePatchDrawable(d);
+ }
+
+ private void internalTestNinePatchDrawable(NinePatchDrawable d) {
+ assertEquals(true, d.isAutoMirrored());
+ // assertEquals(true, d.hasDither());
+ // assertNotNull(d.getNinePatch());
+ }
+
+ public void testTouchFeedbackDrawable() {
+ TouchFeedbackDrawable d = (TouchFeedbackDrawable) mContext.getDrawable(
+ R.drawable.touchfeedbackdrawable_theme);
+
+ // assertEquals(Color.BLACK, d.getPressColor());
+ }
+
+ public void testLayerDrawable() {
+ LayerDrawable d = (LayerDrawable) mContext.getDrawable(R.drawable.layerdrawable_theme);
+
+ // Layer autoMirror values are set to the parent's autoMirror value, so
+ // make sure the container is using the expected value.
+ assertEquals(true, d.isAutoMirrored());
+
+ BitmapDrawable bitmapDrawable = (BitmapDrawable) d.getDrawable(0);
+ internalTestBitmapDrawable(bitmapDrawable);
+
+ NinePatchDrawable ninePatchDrawable = (NinePatchDrawable) d.getDrawable(1);
+ internalTestNinePatchDrawable(ninePatchDrawable);
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/AllocationTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/AllocationTest.java
index 2d21e6f..b27e0d6 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/AllocationTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/AllocationTest.java
@@ -16,28 +16,36 @@
package android.hardware.camera2.cts;
+import static android.graphics.ImageFormat.YUV_420_888;
+import static android.hardware.camera2.cts.helpers.Preconditions.*;
+import static android.hardware.camera2.cts.helpers.AssertHelpers.*;
import static android.hardware.camera2.cts.CameraTestUtils.*;
import static com.android.ex.camera2.blocking.BlockingStateListener.*;
import android.content.Context;
import android.graphics.ImageFormat;
+import android.graphics.RectF;
import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
-import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
-import android.hardware.camera2.Size;
import android.hardware.camera2.Rational;
+import android.hardware.camera2.Size;
+import android.hardware.camera2.cts.helpers.MaybeNull;
import android.hardware.camera2.cts.helpers.StaticMetadata;
+import android.hardware.camera2.cts.rs.RenderScriptSingleton;
+import android.hardware.camera2.cts.rs.ScriptGraph;
+import android.hardware.camera2.cts.rs.ScriptYuvCrop;
+import android.hardware.camera2.cts.rs.ScriptYuvMeans1d;
+import android.hardware.camera2.cts.rs.ScriptYuvMeans2dTo1d;
+import android.hardware.camera2.cts.rs.ScriptYuvToRgb;
import android.os.Handler;
import android.os.HandlerThread;
import android.renderscript.Allocation;
-import android.renderscript.Element;
-import android.renderscript.RenderScript;
import android.renderscript.Script.LaunchOptions;
-import android.renderscript.ScriptIntrinsicYuvToRGB;
-import android.renderscript.Type;
import android.test.AndroidTestCase;
import android.util.Log;
import android.view.Surface;
@@ -50,20 +58,16 @@
import java.util.List;
/**
- * Basic test for camera2 -> RenderScript APIs.
+ * Suite of tests for camera2 -> RenderScript APIs.
*
* <p>It uses CameraDevice as producer, camera sends the data to the surface provided by
- * Allocation. Below image formats are tested:</p>
+ * Allocation. Only the below format is tested:</p>
*
* <p>YUV_420_888: flexible YUV420, it is a mandatory format for camera.</p>
*/
public class AllocationTest extends AndroidTestCase {
private static final String TAG = "AllocationTest";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
- private static final boolean DEBUG = false;
- // number of frame (for streaming requests) to be verified.
- // TODO: Need extend it to bigger number
- private static final int NUM_FRAME_VERIFIED = 1;
private CameraManager mCameraManager;
private CameraDevice mCamera;
@@ -71,71 +75,45 @@
private String[] mCameraIds;
private Handler mHandler;
- private OnBufferAvailableListener mListener;
- private HandlerThread mLooperThread;
+ private HandlerThread mHandlerThread;
- private Allocation mAllocation; // Flexible YUV (USAGE_IO_INPUT from camera2)
- private Allocation mAllocationOut; // RGB (regular usage)
- private RenderScript mRS;
-
- private ScriptIntrinsicYuvToRGB mRgbConverter;
- private ScriptC_crop_yuvf_420_to_yuvx_444 mScript_crop;
- private ScriptC_means_yuvx_444_2d_to_1d mScript_means_2d; // 2d -> 1d means
- private ScriptC_means_yuvx_444_1d_to_single mScript_means_1d; // 1d -> single means
- /**
- * yuvf_420, same as ImageFormat.YUV_420_888
- */
- private Element ELEMENT_YUVF_420; //
- /**
- * yuvx_444, each pixel is [y, u, v, x] where x is some garbage byte. single plane.
- */
- private Element ELEMENT_YUVX_444;
+ private CameraIterable mCameraIterable;
+ private SizeIterable mSizeIterable;
+ private ResultIterable mResultIterable;
@Override
- public void setContext(Context context) {
+ public synchronized void setContext(Context context) {
super.setContext(context);
mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
assertNotNull("Can't connect to camera manager!", mCameraManager);
- mRS = RenderScript.create(context);
- assertNotNull("Can't create a RenderScript context", mRS);
- mScript_crop = new ScriptC_crop_yuvf_420_to_yuvx_444(mRS);
- mScript_means_2d = new ScriptC_means_yuvx_444_2d_to_1d(mRS);
- mScript_means_1d = new ScriptC_means_yuvx_444_1d_to_single(mRS);
-
- ELEMENT_YUVF_420 = Element.YUV(mRS);
- ELEMENT_YUVX_444 = Element.U8_3(mRS);
+ RenderScriptSingleton.setContext(context);
+ // TODO: call clearContext
}
@Override
protected void setUp() throws Exception {
super.setUp();
mCameraIds = mCameraManager.getCameraIdList();
- mLooperThread = new HandlerThread("AllocationTest");
- mLooperThread.start();
- mHandler = new Handler(mLooperThread.getLooper());
+ mHandlerThread = new HandlerThread("AllocationTest");
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
mCameraListener = new BlockingStateListener();
+
+ mCameraIterable = new CameraIterable();
+ mSizeIterable = new SizeIterable();
+ mResultIterable = new ResultIterable();
}
@Override
protected void tearDown() throws Exception {
- if (mCamera != null) {
- mCamera.close();
- mCamera = null;
- }
- if (mAllocation != null) {
- mAllocation.destroy();
- mAllocation = null;
- }
- if (mAllocationOut != null) {
- mAllocationOut.destroy();
- mAllocationOut = null;
- }
- if (mRS != null) {
- mRS.destroy();
- mRS = null;
- }
- mLooperThread.quitSafely();
+ MaybeNull.close(mCamera);
+
+ // TODO: Clean up RenderScript context in a static test run finished method.
+ // Or alternatively count the # of test methods that are in this test,
+ // once we reach that count, it's time to call the last tear down
+
+ mHandlerThread.quitSafely();
mHandler = null;
super.tearDown();
}
@@ -147,7 +125,7 @@
* @param sensitivity ISO gain units (e.g. 100)
* @param expTimeNs Exposure time in nanoseconds
*/
- private void setManualCaptureRequest(CaptureRequest.Builder request, int sensitivity,
+ private static void setManualCaptureRequest(CaptureRequest.Builder request, int sensitivity,
long expTimeNs) {
final Rational ONE = new Rational(1, 1);
final Rational ZERO = new Rational(0, 1);
@@ -181,37 +159,36 @@
request.set(CaptureRequest.TONEMAP_MODE, CaptureRequest.TONEMAP_MODE_FAST);
}
- private static void assertInRange(float value, float low, float high) {
- assertTrue(value >= low && value <= high);
- }
-
/**
- * Calculate the crop window from an Allocation, and configure {@link LaunchOptions} for it.
+ * Calculate the absolute crop window from a {@link Size},
+ * and configure {@link LaunchOptions} for it.
*/
- public class Patch {
-
+ // TODO: split patch crop window and the application against a particular size into 2 classes
+ public static class Patch {
/**
- * Extract a subset of the Y plane from sourceYuv.
+ * Create a new {@link Patch} from relative crop coordinates.
*
- * <p>All float values are normalized coordinates between [0, 1].</p>
+ * <p>All float values must be normalized coordinates between [0, 1].</p>
*
- * @param sourceYuv An allocation of the {@link Element#YUV} format (flexible YUV).
+ * @param size Size of the original rectangle that is being cropped.
* @param xNorm The X coordinate defining the left side of the rectangle (in [0, 1]).
* @param yNorm The Y coordinate defining the top side of the rectangle (in [0, 1]).
* @param wNorm The width of the crop rectangle (normalized between [0, 1]).
* @param hNorm The height of the crop rectangle (normalized between [0, 1]).
+ *
+ * @throws NullPointerException if size was {@code null}.
+ * @throws AssertionError if any of the normalized coordinates were out of range
*/
- public Patch(Allocation sourceYuv, float xNorm, float yNorm, float wNorm, float hNorm) {
- assertNotNull(sourceYuv);
- assertTrue(sourceYuv.getElement().isCompatible(ELEMENT_YUVF_420)); // flexible YUV
+ public Patch(Size size, float xNorm, float yNorm, float wNorm, float hNorm) {
+ checkNotNull("size", size);
assertInRange(xNorm, 0.0f, 1.0f);
assertInRange(yNorm, 0.0f, 1.0f);
assertInRange(wNorm, 0.0f, 1.0f);
assertInRange(hNorm, 0.0f, 1.0f);
- wFull = sourceYuv.getType().getX();
- hFull = sourceYuv.getType().getY();
+ wFull = size.getWidth();
+ hFull = size.getWidth();
xTile = (int)Math.ceil(xNorm * wFull);
yTile = (int)Math.ceil(yNorm * hFull);
@@ -219,28 +196,72 @@
wTile = (int)Math.ceil(wNorm * wFull);
hTile = (int)Math.ceil(hNorm * hFull);
- mSourceAllocation = sourceYuv;
+ mSourceSize = size;
}
- public Allocation getAllocation() {
- return mSourceAllocation;
+ /**
+ * Get the original size used to create this {@link Patch}.
+ *
+ * @return source size
+ */
+ public Size getSourceSize() {
+ return mSourceSize;
}
+ /**
+ * Get the cropped size after applying the normalized crop window.
+ *
+ * @return cropped size
+ */
+ public Size getSize() {
+ return new Size(wFull, hFull);
+ }
+
+ /**
+ * Get the {@link LaunchOptions} that can be used with a {@link android.renderscript.Script}
+ * to apply a kernel over a subset of an {@link Allocation}.
+ *
+ * @return launch options
+ */
public LaunchOptions getLaunchOptions() {
return (new LaunchOptions())
.setX(xTile, xTile + wTile)
.setY(yTile, yTile + hTile);
}
+ /**
+ * Get the cropped width after applying the normalized crop window.
+ *
+ * @return cropped width
+ */
public int getWidth() {
return wTile;
}
+ /**
+ * Get the cropped height after applying the normalized crop window.
+ *
+ * @return cropped height
+ */
public int getHeight() {
return hTile;
}
- private final Allocation mSourceAllocation;
+ /**
+ * Convert to a {@link RectF} where each corner is represented by a
+ * normalized coordinate in between [0.0, 1.0] inclusive.
+ *
+ * @return a new rectangle
+ */
+ public RectF toRectF() {
+ return new RectF(
+ xTile * 1.0f / wFull,
+ yTile * 1.0f / hFull,
+ (xTile + wTile) * 1.0f / wFull,
+ (yTile + hTile) * 1.0f / hFull);
+ }
+
+ private final Size mSourceSize;
private final int wFull;
private final int hFull;
private final int xTile;
@@ -250,269 +271,71 @@
}
/**
- * Compute a 3-channel RGB float array of the average YUV values in the allocation specified by
- * patch.
+ * Convert a single YUV pixel (3 byte elements) to an RGB pixel.
*
- * @param patch A cropped allocation rectangle.
- * @return Float array, size 3.
+ * <p>The color channels must be in the following order:
+ * <ul><li>Y - 0th channel
+ * <li>U - 1st channel
+ * <li>V - 2nd channel
+ * </ul></p>
+ *
+ * <p>Each channel has data in the range 0-255.</p>
+ *
+ * <p>Output data is a 3-element pixel with each channel in the range of [0,1].
+ * Each channel is saturated to avoid over/underflow.</p>
+ *
+ * <p>The conversion is done using JFIF File Interchange Format's "Conversion to and from RGB":
+ * <ul>
+ * <li>R = Y + 1.042 (Cr - 128)
+ * <li>G = Y - 0.34414 (Cb - 128) - 0.71414 (Cr - 128)
+ * <li>B = Y + 1.772 (Cb - 128)
+ * </ul>
+ *
+ * Where Cr and Cb are aliases of V and U respectively.
+ * </p>
+ *
+ * @param yuvData An array of a YUV pixel (at least 3 bytes large)
+ *
+ * @return an RGB888 pixel with each channel in the range of [0,1]
*/
- private float[] computeImageMeans(Patch patch) {
-
- Allocation tile = patch.getAllocation();
-
- assertNotNull(tile);
- assertTrue(tile.getElement().isCompatible(ELEMENT_YUVF_420));
-
- int width = patch.getWidth();
- int height = patch.getHeight();
-
- Log.v(TAG, "Computing image means (WxH)" + width + " " + height);
-
- /*
- * Phase 1: 2d -> 1d means
- * - Input: yuvf_420
- * - Output: yuvx_444
- *
- * Average a WxH 2d array into a Hx1 1d array (processed row-wise).
- */
- Allocation yuv1dAllocation;
- {
- // U8 x 3 = YUV (1:1 sample), fully interleaved
- Type.Builder summedYuvBuilder = new Type.Builder(mRS, ELEMENT_YUVX_444);
- summedYuvBuilder.setX(height);
-
- yuv1dAllocation = Allocation.createTyped(mRS, summedYuvBuilder.create(),
- Allocation.USAGE_SCRIPT);
- }
-
- {
- // TODO: move to script init. Don't set globals directly.
- mScript_means_2d.set_mInput(tile);
- mScript_means_2d.set_width(width);
- mScript_means_2d.set_inv_width(1.0f / width);
- mScript_means_2d.set_src_x(patch.getLaunchOptions().getXStart());
- mScript_means_2d.set_src_y(patch.getLaunchOptions().getYStart());
-
- // Execute the script over a cropped region
- mScript_means_2d.forEach_means_yuvf_420(yuv1dAllocation);
- }
-
- if (DEBUG) {
- byte[] byteMeans = new byte[yuv1dAllocation.getBytesSize()];
- yuv1dAllocation.copyTo(byteMeans);
- assertArrayNotAllZeroes("1d Means should not be all zeroes", byteMeans);
-
- float[] averageValues = new float[] { 0f, 0f, 0f };
- for (int i = 0; i < byteMeans.length / 3; i += 3) {
- averageValues[0] += byteMeans[i] & 0xFF;
- averageValues[1] += byteMeans[i+1] & 0xFF;
- averageValues[2] += byteMeans[i+2] & 0xFF;
- }
-
- averageValues[0] /= byteMeans.length / 3;
- averageValues[1] /= byteMeans.length / 3;
- averageValues[2] /= byteMeans.length / 3;
-
- Log.v(TAG, String.format("(After 2D -> 1D) Average pixel values: (%f, %f, %f)",
- averageValues[0], averageValues[1], averageValues[2]));
- }
-
- /*
- * Phase 2: 1d -> single means
- *
- * Average a Hx1 1d array into a 1x1 single YUV pixel.
- */
- Allocation yuvSingleAllocation;
- {
- // U8 x 3 = YUV (1:1 sample), fully interleaved
- Type.Builder summedYuvBuilder = new Type.Builder(mRS, ELEMENT_YUVX_444);
- summedYuvBuilder.setX(1);
-
- // TODO: allocations all together, to avoid flushing
- yuvSingleAllocation = Allocation.createTyped(mRS, summedYuvBuilder.create(),
- Allocation.USAGE_SCRIPT);
- }
-
- {
- // TODO: move to script init. Don't set globals directly.
- mScript_means_1d.set_mInput(yuv1dAllocation);
- mScript_means_1d.set_width(yuv1dAllocation.getType().getX());
- mScript_means_1d.set_inv_width(1.0f / yuv1dAllocation.getType().getX());
- mScript_means_1d.forEach_means_yuvx_444(yuvSingleAllocation);
- }
-
- byte[] byteMeans = new byte[yuvSingleAllocation.getBytesSize()];
- yuvSingleAllocation.copyTo(byteMeans);
- assertArrayNotAllZeroes("Single Means should not be all zeroes", byteMeans);
-
- assertTrue("Wrong means length " + byteMeans.length,
- byteMeans.length == 3 || byteMeans.length == 4);
-
- if (VERBOSE) {
- Log.v(TAG,
- String.format("RS means calculated (y,u,v) = (%d, %d, %d)",
- byteMeans[0] & 0xFF,
- byteMeans[1] & 0xFF,
- byteMeans[2] & 0xFF));
- }
-
+ private static float[] convertPixelYuvToRgb(byte[] yuvData) {
final int CHANNELS = 3; // yuv
final float COLOR_RANGE = 256f;
- float[] means = new float[CHANNELS];
+ assertTrue("YUV pixel must be at least 3 bytes large", CHANNELS <= yuvData.length);
- float y = byteMeans[0] & 0xFF;
- float u = byteMeans[1] & 0xFF;
- float v = byteMeans[2] & 0xFF;
+ float[] rgb = new float[CHANNELS];
- // convert YUV -> RGB
- // TODO: Which transform is this?
- float r = y + 1.402f * (u - 128);
- float g = y - 0.34414f * (v - 128) - 0.71414f * (u - 128);
- float b = y + 1.772f * (v - 128);
+ float y = yuvData[0] & 0xFF; // Y channel
+ float cb = yuvData[1] & 0xFF; // U channel
+ float cr = yuvData[2] & 0xFF; // V channel
- // [0,255] -> [0,1]
- means[0] = r / COLOR_RANGE;
- means[1] = g / COLOR_RANGE;
- means[2] = b / COLOR_RANGE;
+ // convert YUV -> RGB (from JFIF's "Conversion to and from RGB" section)
+ float r = y + 1.402f * (cr - 128);
+ float g = y - 0.34414f * (cb - 128) - 0.71414f * (cr - 128);
+ float b = y + 1.772f * (cb - 128);
+
+ // normalize [0,255] -> [0,1]
+ rgb[0] = r / COLOR_RANGE;
+ rgb[1] = g / COLOR_RANGE;
+ rgb[2] = b / COLOR_RANGE;
+
+ // Clamp to range [0,1]
+ for (int i = 0; i < CHANNELS; ++i) {
+ rgb[i] = Math.max(0.0f, Math.min(1.0f, rgb[i]));
+ }
if (VERBOSE) {
- Log.v(TAG, String.format("Means calculated (r,g,b) = (%f, %f, %f)", means[0], means[1],
- means[2]));
+ Log.v(TAG, String.format("RGB calculated (r,g,b) = (%f, %f, %f)", rgb[0], rgb[1],
+ rgb[2]));
}
- return means;
- }
-
- private void checkUpperBound(float[] means, float upperBound) {
- for (int i = 0; i < means.length; ++i) {
- assertTrue(String.format("%s should be less than than %s (color channel %d)", means[i],
- upperBound, i),
- means[i] < upperBound);
- }
- }
-
- private void checkLowerBound(float[] means, float lowerBound) {
- for (int i = 0; i < means.length; ++i) {
- assertTrue(String.format("%s should be greater than %s (color channel %d)", means[i],
- lowerBound, i),
- means[i] > lowerBound);
- }
- }
-
- private void bufferFormatTestByCamera(int format, String cameraId) throws Exception {
- CameraCharacteristics properties = mCameraManager.getCameraCharacteristics(cameraId);
- assertNotNull("Can't get camera properties!", properties);
-
- int[] availableFormats = properties.get(CameraCharacteristics.SCALER_AVAILABLE_FORMATS);
- assertArrayNotEmpty(availableFormats,
- "availableFormats should not be empty");
- Arrays.sort(availableFormats);
- assertTrue("Can't find the format " + format + " in supported formats " +
- Arrays.toString(availableFormats),
- Arrays.binarySearch(availableFormats, format) >= 0);
-
- Size[] availableSizes = getSupportedSizeForFormat(format, mCamera.getId(), mCameraManager);
- assertArrayNotEmpty(availableSizes, "availableSizes should not be empty");
-
- // for each resolution, test allocations:
- for (Size sz : availableSizes) {
- if (VERBOSE) Log.v(TAG, "Testing size " + sz.toString() + " for camera " + cameraId);
-
- prepareAllocation(sz, format);
- // TODO: if there a size upper limit (like 4K) for an Allocation?
- // if so, some camera sensor could have width > 4K.
-
- CaptureRequest request = prepareCaptureRequest(format);
-
- captureAndValidateImage(request, sz, format);
-
- stopCapture();
- }
- }
-
- private class OnBufferAvailableListener implements Allocation.OnBufferAvailableListener {
- private int mPendingBuffers = 0;
- private final Object mBufferSyncObject = new Object();
-
- public boolean isBufferPending() {
- synchronized (mBufferSyncObject) {
- return (mPendingBuffers > 0);
- }
- }
-
- /**
- * Waits for a buffer. Caller must call ioReceive exactly once after calling this.
- */
- public void waitForBuffer() {
- final int TIMEOUT_MS = 5000;
- synchronized (mBufferSyncObject) {
- while (mPendingBuffers == 0) {
- try {
- if (VERBOSE)
- Log.d(TAG, "waiting for next buffer");
- mBufferSyncObject.wait(TIMEOUT_MS);
- if (mPendingBuffers == 0) {
- fail("wait for buffer image timed out");
- }
- } catch (InterruptedException ie) {
- throw new AssertionError(ie);
- }
- }
- mPendingBuffers--;
- }
- }
-
- @Override
- public void onBufferAvailable(Allocation a) {
- if (VERBOSE) Log.v(TAG, "new buffer in allocation available");
- synchronized (mBufferSyncObject) {
- mPendingBuffers++;
- mBufferSyncObject.notifyAll();
- }
- }
- }
-
- private void prepareAllocation(Size sz, int format) throws Exception {
- int width = sz.getWidth();
- int height = sz.getHeight();
-
- {
- // XX: Can I replace this with Element.YUV(mRS) ?
- Element elementYuv = Element.createPixel(mRS, Element.DataType.UNSIGNED_8,
- Element.DataKind.PIXEL_YUV);
-
- Type.Builder yuvBuilder = new Type.Builder(mRS, elementYuv);
- yuvBuilder.setYuvFormat(ImageFormat.YUV_420_888);
- yuvBuilder.setX(width);
- yuvBuilder.setY(height);
-
- mListener = new OnBufferAvailableListener();
-
- // Note: RenderScript Allocation consumer can acquire up to 1 buffer at a time only.
- mAllocation = Allocation.createTyped(mRS, yuvBuilder.create(),
- Allocation.USAGE_SCRIPT | Allocation.USAGE_IO_INPUT);
- mAllocation.setOnBufferAvailableListener(mListener);
-
- mRgbConverter = ScriptIntrinsicYuvToRGB.create(mRS, elementYuv);
- mRgbConverter.setInput(mAllocation);
- }
-
- {
- // YUV to RGB intrinsic only works with U8_4
- Element elementRgb = Element.U8_4(mRS);
- Type.Builder rgbBuilder = (new Type.Builder(mRS, elementRgb))
- .setX(width)
- .setY(height);
- mAllocationOut = Allocation.createTyped(mRS, rgbBuilder.create(),
- Allocation.USAGE_SCRIPT);
- }
-
- if (VERBOSE) Log.v(TAG, "Preparing ImageReader size " + sz.toString());
+ return rgb;
}
/**
- * Create a capture request builder with {@link #mAllocation} as the sole surface target.
+ * Configure the camera with the target surface;
+ * create a capture request builder with {@code cameraTarget} as the sole surface target.
*
* <p>Outputs are configured with the new surface targets, and this function blocks until
* the camera has finished configuring.</p>
@@ -521,11 +344,11 @@
* No other keys are set.
* </p>
*/
- private CaptureRequest.Builder prepareCaptureRequestBuilder(int format) throws Exception {
+ private CaptureRequest.Builder configureAndCreateRequestForSurface(Surface cameraTarget)
+ throws CameraAccessException {
List<Surface> outputSurfaces = new ArrayList<Surface>(/*capacity*/1);
- Surface surface = mAllocation.getSurface();
- assertNotNull("Fail to get surface from ImageReader", surface);
- outputSurfaces.add(surface);
+ assertNotNull("Failed to get Surface", cameraTarget);
+ outputSurfaces.add(cameraTarget);
mCamera.configureOutputs(outputSurfaces);
mCameraListener.waitForState(STATE_BUSY, CAMERA_BUSY_TIMEOUT_MS);
@@ -534,24 +357,23 @@
CaptureRequest.Builder captureBuilder =
mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
assertNotNull("Fail to create captureRequest", captureBuilder);
- captureBuilder.addTarget(surface);
+ captureBuilder.addTarget(cameraTarget);
- if (VERBOSE) Log.v(TAG, "Prepared capture request builder");
+ if (VERBOSE) Log.v(TAG, "configureAndCreateRequestForSurface - done");
return captureBuilder;
}
- private CaptureRequest prepareCaptureRequest(int format) throws Exception {
- return prepareCaptureRequestBuilder(format).build();
- }
-
/**
* Submit a single request to the camera, block until the buffer is available.
*
- * <p>Upon return from this function, the latest buffer is available in {@link #mAllocation}.
+ * <p>Upon return from this function, script has been executed against the latest buffer.
* </p>
*/
- private void captureSingleShot(CaptureRequest request) throws Exception {
+ private void captureSingleShotAndExecute(CaptureRequest request, ScriptGraph graph)
+ throws CameraAccessException {
+ checkNotNull("request", request);
+ checkNotNull("graph", graph);
mCamera.capture(request, new CameraDevice.CaptureListener() {
@Override
@@ -561,225 +383,462 @@
}
}, mHandler);
- assertNotNull("Buffer listener is null", mListener);
-
if (VERBOSE) Log.v(TAG, "Waiting for single shot buffer");
- mListener.waitForBuffer();
- mAllocation.ioReceive();
-
+ graph.advanceInputWaiting();
if (VERBOSE) Log.v(TAG, "Got the buffer");
- }
-
- private void captureAndValidateImage(CaptureRequest request,
- Size sz, int format) throws Exception {
- // TODO: Add more format here, and wrap each one as a function.
- int captureCount = NUM_FRAME_VERIFIED;
-
- // Only verify single image for still capture
- if (format == ImageFormat.JPEG) {
- captureCount = 1;
- mCamera.capture(request, null, null);
- } else {
- mCamera.setRepeatingRequest(request, null, null);
- }
-
- for (int i = 0; i < captureCount; i++) {
- assertNotNull("Image listener is null", mListener);
- if (VERBOSE) Log.v(TAG, "Waiting for a Buffer");
- mListener.waitForBuffer();
- mAllocation.ioReceive();
- if (VERBOSE) Log.v(TAG, "Got next image");
- validateAllocation(mAllocation, sz.getWidth(), sz.getHeight(), format);
-
- // Return the pending images to producer in case the validation is slower
- // than the image producing rate. Otherwise, it could cause the producer
- // starvation.
- while (mListener.isBufferPending()) {
- mListener.waitForBuffer();
- mAllocation.ioReceive();
- }
- }
+ graph.execute();
}
private void stopCapture() throws CameraAccessException {
if (VERBOSE) Log.v(TAG, "Stopping capture and waiting for idle");
// Stop repeat, wait for captures to complete, and disconnect from surfaces
- mCamera.configureOutputs(/*outputs*/ null);
+ mCamera.configureOutputs(/*outputs*/null);
mCameraListener.waitForState(STATE_BUSY, CAMERA_BUSY_TIMEOUT_MS);
mCameraListener.waitForState(STATE_UNCONFIGURED, CAMERA_IDLE_TIMEOUT_MS);
-
- // Camera has disconnected, clear out the allocations
-
- // TODO: don't destroy allocations every time, reuse instead
-
- if (mAllocation != null) {
- mAllocation.destroy();
- mAllocation = null;
- }
-
- if (mAllocationOut != null) {
- mAllocationOut.destroy();
- mAllocationOut = null;
- }
-
- mListener = null;
- }
-
- private void openDevice(String cameraId) {
- if (mCamera != null) {
- throw new IllegalStateException("Already have open camera device");
- }
- try {
- mCamera = openCamera(
- mCameraManager, cameraId, mCameraListener, mHandler);
- } catch (CameraAccessException e) {
- mCamera = null;
- fail("Fail to open camera, " + Log.getStackTraceString(e));
- } catch (BlockingOpenException e) {
- mCamera = null;
- fail("Fail to open camera, " + Log.getStackTraceString(e));
- }
- mCameraListener.waitForState(STATE_UNCONFIGURED, CAMERA_OPEN_TIMEOUT_MS);
- }
-
- private void closeDevice(String cameraId) {
- mCamera.close();
- mCameraListener.waitForState(STATE_CLOSED, CAMERA_CLOSE_TIMEOUT_MS);
- mCamera = null;
- }
-
- /**
- * Assert that at least one of the elements in data is non-zero.
- *
- * <p>An empty or a null array always fails.</p>
- */
- private void assertArrayNotAllZeroes(String message, byte[] data) {
- int size = data.length;
-
- int i = 0;
- for (i = 0; i < size; ++i) {
- if (data[i] != 0) {
- break;
- }
- }
-
- assertTrue(message, i < size);
- }
-
- /**
- * Checks that at least one of the pixels is a non-zero value
- * (across any of the color channels).
- */
- private void checkAllocationByConvertingToRgba(ScriptIntrinsicYuvToRGB rgbConverter,
- Allocation allocationOut, int width, int height) {
-
- final int RGBA_CHANNELS = 4;
-
- rgbConverter.forEach(allocationOut);
-
- int actualSize = allocationOut.getBytesSize();
- int packedSize = width * height * RGBA_CHANNELS;
-
- byte[] data = new byte[actualSize];
- allocationOut.copyTo(data);
-
- assertArrayNotAllZeroes("RGBA data was not updated", data);
-
- assertTrue(
- String.format(
- "Packed size (%d) should be at least as large as the actual size (%d)",
- packedSize, actualSize), packedSize <= actualSize);
}
/**
* Extremely dumb validator. Makes sure there is at least one non-zero RGB pixel value.
*/
- private void validateAllocation(Allocation allocation, int width, int height, int format) {
- checkAllocationByConvertingToRgba(mRgbConverter, mAllocationOut, width, height);
+ private void validateInputOutputNotZeroes(ScriptGraph scriptGraph, Size size) {
+ final int BPP = 8; // bits per pixel
+
+ int width = size.getWidth();
+ int height = size.getHeight();
+ /**
+ * Check the input allocation is sane.
+ * - Byte size matches what we expect.
+ * - The input is not all zeroes.
+ */
+
+ // Check that input data was updated first. If it wasn't, the rest of the test will fail.
+ byte[] data = scriptGraph.getInputData();
+ assertArrayNotAllZeroes("Input allocation data was not updated", data);
// Minimal required size to represent YUV 4:2:0 image
- int packedSize = width * height * ImageFormat.getBitsPerPixel(format) / 8;
- if(VERBOSE) Log.v(TAG, "Expected image size = " + packedSize);
-
+ int packedSize =
+ width * height * ImageFormat.getBitsPerPixel(YUV_420_888) / BPP;
+ if (VERBOSE) Log.v(TAG, "Expected image size = " + packedSize);
+ int actualSize = data.length;
// Actual size may be larger due to strides or planes being non-contiguous
- int actualSize = allocation.getBytesSize();
+ assertTrue(
+ String.format(
+ "YUV 420 packed size (%d) should be at least as large as the actual size " +
+ "(%d)", packedSize, actualSize), packedSize <= actualSize);
+ /**
+ * Check the output allocation by converting to RGBA.
+ * - Byte size matches what we expect
+ * - The output is not all zeroes
+ */
+ final int RGBA_CHANNELS = 4;
- // FIXME: b/12134914 this currently only counts the Y plane
- if (true) {
- assertTrue(
- String.format(
- "Packed size (%d) should be at least as large as the actual size (%d)",
- packedSize, actualSize), packedSize <= actualSize);
- } else {
- assertTrue("Non-positive packed size encountered", packedSize > 0);
- assertTrue("Non-positive actual size encountered", actualSize > 0);
+ int actualSizeOut = scriptGraph.getOutputAllocation().getBytesSize();
+ int packedSizeOut = width * height * RGBA_CHANNELS;
+
+ byte[] dataOut = scriptGraph.getOutputData();
+ assertEquals("RGB mismatched byte[] and expected size",
+ packedSizeOut, dataOut.length);
+
+ if (VERBOSE) {
+ Log.v(TAG, "checkAllocationByConvertingToRgba - RGB data size " + dataOut.length);
}
- if(VERBOSE) Log.v(TAG, "validating Buffer , size = " + actualSize);
- byte[] data = new byte[actualSize];
- allocation.copyTo(data);
+ assertArrayNotAllZeroes("RGBA data was not updated", dataOut);
+ // RGBA8888 stride should be equal to the width
+ assertEquals("RGBA 8888 mismatched byte[] and expected size", packedSizeOut, actualSizeOut);
- assertArrayNotAllZeroes("Allocation data was not updated", data);
+ if (VERBOSE) Log.v(TAG, "validating Buffer , size = " + actualSize);
}
public void testAllocationFromCameraFlexibleYuv() throws Exception {
- for (int i = 0; i < mCameraIds.length; i++) {
- Log.i(TAG, "Testing Camera " + mCameraIds[i]);
- openDevice(mCameraIds[i]);
- bufferFormatTestByCamera(ImageFormat.YUV_420_888, mCameraIds[i]);
- closeDevice(mCameraIds[i]);
+
+ /** number of frame (for streaming requests) to be verified. */
+ final int NUM_FRAME_VERIFIED = 1;
+
+ mCameraIterable.forEachCamera(new CameraBlock() {
+ @Override
+ public void run(CameraDevice camera) throws CameraAccessException {
+
+ // Iterate over each size in the camera
+ mSizeIterable.forEachSize(YUV_420_888, new SizeBlock() {
+ @Override
+ public void run(final Size size) throws CameraAccessException {
+ // Create a script graph that converts YUV to RGB
+ final ScriptGraph scriptGraph = ScriptGraph.create()
+ .configureInputWithSurface(size, YUV_420_888)
+ .chainScript(ScriptYuvToRgb.class)
+ .buildGraph();
+
+ if (VERBOSE) Log.v(TAG, "Prepared ScriptYuvToRgb for size " + size);
+
+ // Run the graph against camera input and validate we get some input
+ try {
+ CaptureRequest request =
+ configureAndCreateRequestForSurface(scriptGraph.getInputSurface()).build();
+
+ // Block until we get 1 result, then iterate over the result
+ mResultIterable.forEachResultRepeating(
+ request, NUM_FRAME_VERIFIED, new ResultBlock() {
+ @Override
+ public void run(CaptureResult result) throws CameraAccessException {
+ scriptGraph.advanceInputWaiting();
+ scriptGraph.execute();
+ validateInputOutputNotZeroes(scriptGraph, size);
+ scriptGraph.advanceInputAndDrop();
+ }
+ });
+
+ stopCapture();
+ } finally {
+ scriptGraph.close();
+ }
+ }
+ });
+ }
+ });
+ }
+
+ /**
+ * Take two shots and ensure per-frame-control with exposure/gain is working correctly.
+ *
+ * <p>Takes a shot with very low ISO and exposure time. Expect it to be black.</p>
+ *
+ * <p>Take a shot with very high ISO and exposure time. Expect it to be white.</p>
+ *
+ * @throws Exception
+ */
+ public void testBlackWhite() throws CameraAccessException {
+
+ /** low iso + low exposure (first shot) */
+ final float THRESHOLD_LOW = 0.025f;
+ /** high iso + high exposure (second shot) */
+ final float THRESHOLD_HIGH = 0.975f;
+
+ mCameraIterable.forEachCamera(/*fullHwLevel*/true, new CameraBlock() {
+ @Override
+ public void run(CameraDevice camera) throws CameraAccessException {
+
+ final Size maxSize = getMaxSize(
+ getSupportedSizeForFormat(YUV_420_888, camera.getId(), mCameraManager));
+ final StaticMetadata staticInfo =
+ new StaticMetadata(mCameraManager.getCameraCharacteristics(camera.getId()));
+
+ ScriptGraph scriptGraph = createGraphForYuvCroppedMeans(maxSize);
+
+ CaptureRequest.Builder req =
+ configureAndCreateRequestForSurface(scriptGraph.getInputSurface());
+
+ // Take a shot with very low ISO and exposure time. Expect it to be black.
+ int minimumSensitivity = staticInfo.getSensitivityMinimumOrDefault();
+ long minimumExposure = staticInfo.getExposureMinimumOrDefault();
+ setManualCaptureRequest(req, minimumSensitivity, minimumExposure);
+
+ CaptureRequest lowIsoExposureShot = req.build();
+ captureSingleShotAndExecute(lowIsoExposureShot, scriptGraph);
+
+ float[] blackMeans = convertPixelYuvToRgb(scriptGraph.getOutputData());
+
+ // Take a shot with very high ISO and exposure time. Expect it to be white.
+ int maximumSensitivity = staticInfo.getSensitivityMaximumOrDefault();
+ long maximumExposure = staticInfo.getExposureMaximumOrDefault();
+ setManualCaptureRequest(req, maximumSensitivity, maximumExposure);
+
+ CaptureRequest highIsoExposureShot = req.build();
+ captureSingleShotAndExecute(highIsoExposureShot, scriptGraph);
+
+ float[] whiteMeans = convertPixelYuvToRgb(scriptGraph.getOutputData());
+
+ // low iso + low exposure (first shot)
+ assertArrayWithinUpperBound("Black means too high", blackMeans, THRESHOLD_LOW);
+
+ // high iso + high exposure (second shot)
+ assertArrayWithinLowerBound("White means too low", whiteMeans, THRESHOLD_HIGH);
+ }
+ });
+ }
+
+ /**
+ * Test that the android.sensitivity.parameter is applied.
+ */
+ public void testParamSensitivity() throws CameraAccessException {
+ final float THRESHOLD_MAX_MIN_DIFF = 0.3f;
+ final float THRESHOLD_MAX_MIN_RATIO = 2.0f;
+ final int NUM_STEPS = 5;
+ final long EXPOSURE_TIME_NS = 2000000; // 2 seconds
+ final int RGB_CHANNELS = 3;
+
+ final List<float[]> rgbMeans = new ArrayList<float[]>();
+
+ mCameraIterable.forEachCamera(/*fullHwLevel*/true, new CameraBlock() {
+ @Override
+ public void run(CameraDevice camera) throws CameraAccessException {
+
+ final Size maxSize = getMaxSize(
+ getSupportedSizeForFormat(YUV_420_888, camera.getId(), mCameraManager));
+ final StaticMetadata staticInfo =
+ new StaticMetadata(mCameraManager.getCameraCharacteristics(camera.getId()));
+
+ final int sensitivityMin = staticInfo.getSensitivityMinimumOrDefault();
+ final int sensitivityMax = staticInfo.getSensitivityMaximumOrDefault();
+
+ // List each sensitivity from min-max in NUM_STEPS increments
+ int[] sensitivities = new int[NUM_STEPS];
+ for (int i = 0; i < NUM_STEPS; ++i) {
+ int delta = (sensitivityMax - sensitivityMin) / (NUM_STEPS - 1);
+ sensitivities[i] = sensitivityMin + delta * i;
+ }
+
+ ScriptGraph scriptGraph = createGraphForYuvCroppedMeans(maxSize);
+
+ CaptureRequest.Builder req =
+ configureAndCreateRequestForSurface(scriptGraph.getInputSurface());
+
+ // Take burst shots with increasing sensitivity one after other.
+ for (int i = 0; i < NUM_STEPS; ++i) {
+ setManualCaptureRequest(req, sensitivities[i], EXPOSURE_TIME_NS);
+ captureSingleShotAndExecute(req.build(), scriptGraph);
+ float[] means = convertPixelYuvToRgb(scriptGraph.getOutputData());
+ rgbMeans.add(means);
+
+ if (VERBOSE) {
+ Log.v(TAG, "testParamSensitivity - captured image " + i +
+ " with RGB means: " + Arrays.toString(means));
+ }
+ }
+
+ // Test that every consecutive image gets brighter.
+ for (int i = 0; i < rgbMeans.size() - 1; ++i) {
+ float[] curMeans = rgbMeans.get(i);
+ float[] nextMeans = rgbMeans.get(i+1);
+
+ assertArrayNotGreater(
+ String.format("Shot with sensitivity %d should not have higher " +
+ "average means than shot with sensitivity %d",
+ sensitivities[i], sensitivities[i+1]),
+ curMeans, nextMeans);
+ }
+
+ // Test the min-max diff and ratios are within expected thresholds
+ float[] lastMeans = rgbMeans.get(NUM_STEPS - 1);
+ float[] firstMeans = rgbMeans.get(/*location*/0);
+ for (int i = 0; i < RGB_CHANNELS; ++i) {
+ assertTrue(
+ String.format("Sensitivity max-min diff too small (max=%f, min=%f)",
+ lastMeans[i], firstMeans[i]),
+ lastMeans[i] - firstMeans[i] > THRESHOLD_MAX_MIN_DIFF);
+ assertTrue(
+ String.format("Sensitivity max-min ratio too small (max=%f, min=%f)",
+ lastMeans[i], firstMeans[i]),
+ lastMeans[i] / firstMeans[i] > THRESHOLD_MAX_MIN_RATIO);
+ }
+ }
+ });
+
+ }
+
+ /**
+ * Common script graph for manual-capture based tests that determine the average pixel
+ * values of a cropped sub-region.
+ *
+ * <p>Processing chain:
+ *
+ * <pre>
+ * input: YUV_420_888 surface
+ * output: mean YUV value of a central section of the image,
+ * YUV 4:4:4 encoded as U8_3
+ * steps:
+ * 1) crop [0.45,0.45] - [0.55, 0.55]
+ * 2) average columns
+ * 3) average rows
+ * </pre>
+ * </p>
+ */
+ private static ScriptGraph createGraphForYuvCroppedMeans(final Size size) {
+ ScriptGraph scriptGraph = ScriptGraph.create()
+ .configureInputWithSurface(size, YUV_420_888)
+ .configureScript(ScriptYuvCrop.class)
+ .set(ScriptYuvCrop.CROP_WINDOW,
+ new Patch(size, /*x*/0.45f, /*y*/0.45f, /*w*/0.1f, /*h*/0.1f).toRectF())
+ .buildScript()
+ .chainScript(ScriptYuvMeans2dTo1d.class)
+ .chainScript(ScriptYuvMeans1d.class)
+ // TODO: Make a script for YUV 444 -> RGB 888 conversion
+ .buildGraph();
+ return scriptGraph;
+ }
+
+ /*
+ * TODO: Refactor below code into separate classes and to not depend on AllocationTest
+ * inner variables.
+ *
+ * TODO: add javadocs to below methods
+ *
+ * TODO: Figure out if there's some elegant way to compose these forEaches together, so that
+ * the callers don't have to do a ton of nesting
+ */
+
+ interface CameraBlock {
+ void run(CameraDevice camera) throws CameraAccessException;
+ }
+
+ class CameraIterable {
+ public void forEachCamera(CameraBlock runnable)
+ throws CameraAccessException {
+ forEachCamera(/*fullHwLevel*/false, runnable);
+ }
+
+ public void forEachCamera(boolean fullHwLevel, CameraBlock runnable)
+ throws CameraAccessException {
+ assertNotNull("No camera manager", mCameraManager);
+ assertNotNull("No camera IDs", mCameraIds);
+
+ for (int i = 0; i < mCameraIds.length; i++) {
+ // Don't execute the runnable against non-FULL cameras if FULL is required
+ CameraCharacteristics properties =
+ mCameraManager.getCameraCharacteristics(mCameraIds[i]);
+ StaticMetadata staticInfo = new StaticMetadata(properties);
+ if (fullHwLevel && !staticInfo.isHardwareLevelFull()) {
+ Log.i(TAG, String.format(
+ "Skipping this test for camera %d, needs FULL hw level",
+ mCameraIds[i]));
+ continue;
+ }
+
+ // FIXME: hammerhead FFC thinks its FULL but doesnt have per-frame-control
+ if (fullHwLevel &&
+ staticInfo.getCharacteristics().get(CameraCharacteristics.LENS_FACING)
+ != CameraMetadata.LENS_FACING_BACK
+ && "hammerhead".equals(android.os.Build.PRODUCT)) {
+ Log.w(TAG,
+ "FIXME: Front facing camera claims to support per-frame-control " +
+ "but doesn't for product " + android.os.Build.PRODUCT);
+ continue;
+ }
+
+ // Open camera and execute test
+ Log.i(TAG, "Testing Camera " + mCameraIds[i]);
+ try {
+ openDevice(mCameraIds[i]);
+
+ runnable.run(mCamera);
+ } finally {
+ closeDevice(mCameraIds[i]);
+ }
+ }
+ }
+
+ private void openDevice(String cameraId) {
+ if (mCamera != null) {
+ throw new IllegalStateException("Already have open camera device");
+ }
+ try {
+ mCamera = openCamera(
+ mCameraManager, cameraId, mCameraListener, mHandler);
+ } catch (CameraAccessException e) {
+ fail("Fail to open camera synchronously, " + Log.getStackTraceString(e));
+ } catch (BlockingOpenException e) {
+ fail("Fail to open camera asynchronously, " + Log.getStackTraceString(e));
+ }
+ mCameraListener.waitForState(STATE_UNCONFIGURED, CAMERA_OPEN_TIMEOUT_MS);
+ }
+
+ private void closeDevice(String cameraId) {
+ if (mCamera != null) {
+ mCamera.close();
+ mCameraListener.waitForState(STATE_CLOSED, CAMERA_CLOSE_TIMEOUT_MS);
+ mCamera = null;
+ }
}
}
- public void testBlackWhite() throws Exception {
- String[] devices = mCameraManager.getCameraIdList();
- if (devices == null || devices.length == 0) {
- return;
+ interface SizeBlock {
+ void run(Size size) throws CameraAccessException;
+ }
+
+ class SizeIterable {
+ public void forEachSize(int format, SizeBlock runnable) throws CameraAccessException {
+ assertNotNull("No camera opened", mCamera);
+ assertNotNull("No camera manager", mCameraManager);
+
+ CameraCharacteristics properties =
+ mCameraManager.getCameraCharacteristics(mCamera.getId());
+
+ assertNotNull("Can't get camera properties!", properties);
+
+ int[] availableFormats = properties.get(CameraCharacteristics.SCALER_AVAILABLE_FORMATS);
+ assertArrayNotEmpty(availableFormats,
+ "availableFormats should not be empty");
+ Arrays.sort(availableFormats);
+ assertTrue("Can't find the format " + format + " in supported formats " +
+ Arrays.toString(availableFormats),
+ Arrays.binarySearch(availableFormats, format) >= 0);
+
+ Size[] availableSizes = getSupportedSizeForFormat(format, mCamera.getId(),
+ mCameraManager);
+ assertArrayNotEmpty(availableSizes, "availableSizes should not be empty");
+
+ for (Size size : availableSizes) {
+
+ if (VERBOSE) {
+ Log.v(TAG, "Testing size " + size.toString() +
+ " for camera " + mCamera.getId());
+ }
+ runnable.run(size);
+ }
+ }
+ }
+
+ interface ResultBlock {
+ void run(CaptureResult result) throws CameraAccessException;
+ }
+
+ class ResultIterable {
+ public void forEachResultOnce(CaptureRequest request, ResultBlock block)
+ throws CameraAccessException {
+ forEachResult(request, /*count*/1, /*repeating*/false, block);
}
- final String CAMERA_ID = devices[0]; // TODO: don't hardcode to the first camera ID
- final int FORMAT = ImageFormat.YUV_420_888;
-
- final Size maxSize = getMaxSize(
- getSupportedSizeForFormat(FORMAT, CAMERA_ID, mCameraManager));
- final StaticMetadata staticInfo =
- new StaticMetadata(mCameraManager.getCameraCharacteristics(CAMERA_ID));
-
- // TODO: check on a more granular level if what we're trying to do is possible
- // (e.g. manual sensor control, manual processing control)
- if (staticInfo.isHardwareLevelLimited()) {
- return;
+ public void forEachResultRepeating(CaptureRequest request, int count, ResultBlock block)
+ throws CameraAccessException {
+ forEachResult(request, count, /*repeating*/true, block);
}
- openDevice(CAMERA_ID);
- prepareAllocation(maxSize, FORMAT);
- CaptureRequest.Builder req = prepareCaptureRequestBuilder(FORMAT);
+ public void forEachResult(CaptureRequest request, int count, boolean repeating,
+ ResultBlock block) throws CameraAccessException {
- // Take a shot with very low ISO and exposure time. Expect it to be
- // black.
- int minimumSensitivity = staticInfo.getSensitivityMinimumOrDefault(100); // 100 ISO
- long minimumExposure = staticInfo.getExposureMinimumOrDefault(100000); // 0.1ms
- setManualCaptureRequest(req, minimumSensitivity, minimumExposure);
+ // TODO: start capture, i.e. configureOutputs
- CaptureRequest lowIsoExposureShot = req.build();
- captureSingleShot(lowIsoExposureShot);
+ SimpleCaptureListener listener = new SimpleCaptureListener();
- Patch tile = new Patch(mAllocation, 0.45f, 0.45f, 0.1f, 0.1f);
- float[] blackMeans = computeImageMeans(tile);
+ if (!repeating) {
+ for (int i = 0; i < count; ++i) {
+ mCamera.capture(request, listener, mHandler);
+ }
+ } else {
+ mCamera.setRepeatingRequest(request, listener, mHandler);
+ }
- // Take a shot with very high ISO and exposure time. Expect it to be
- // white.
- int maximumSensitivity = staticInfo.getSensitivityMaximumOrDefault(10000); // 10,000 ISO
- long maximumExposure = staticInfo.getExposureMaximumOrDefault(1000000000); // 1000ms
- setManualCaptureRequest(req, maximumSensitivity, maximumExposure);
+ // Assume that the device is already IDLE.
+ mCameraListener.waitForState(BlockingStateListener.STATE_ACTIVE,
+ CAMERA_ACTIVE_TIMEOUT_MS);
- CaptureRequest highIsoExposureShot = req.build();
- captureSingleShot(highIsoExposureShot);
+ for (int i = 0; i < count; ++i) {
+ if (VERBOSE) {
+ Log.v(TAG, String.format("Testing with result %d of %d for camera %s",
+ i, count, mCamera.getId()));
+ }
- tile = new Patch(mAllocation, 0.45f, 0.45f, 0.1f, 0.1f);
- float[] whiteMeans = computeImageMeans(tile);
+ CaptureResult result = listener.getCaptureResult(CAPTURE_RESULT_TIMEOUT_MS);
+ block.run(result);
+ }
- checkUpperBound(blackMeans, 0.025f); // low iso + low exposure (first shot)
- checkLowerBound(whiteMeans, 0.975f); // high iso + high exposure (second shot)
+ if (repeating) {
+ mCamera.stopRepeating();
+ mCameraListener.waitForState(BlockingStateListener.STATE_IDLE,
+ CAMERA_IDLE_TIMEOUT_MS);
+ }
+
+ // TODO: Make a Configure decorator or some such for configureOutputs
+ }
}
}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraCharacteristicsTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraCharacteristicsTest.java
index 32a1ff7..527b356 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraCharacteristicsTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraCharacteristicsTest.java
@@ -310,6 +310,29 @@
}
}
+ public void testCameraCharacteristicsAndroidEdgeAvailableEdgeModes() throws Exception {
+ String[] ids = mCameraManager.getCameraIdList();
+ for (int i = 0; i < ids.length; i++) {
+ CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
+ assertNotNull(String.format("Can't get camera characteristics from: ID %s", ids[i]),
+ props);
+
+ {
+
+ assertNotNull("Invalid property: android.edge.availableEdgeModes",
+ props.get(CameraCharacteristics.EDGE_AVAILABLE_EDGE_MODES));
+
+ List<Key<?>> allKeys = props.getKeys();
+ assertNotNull(String.format("Can't get camera characteristics keys from: ID %s",
+ ids[i], props));
+ assertTrue("Key not in keys list: android.edge.availableEdgeModes", allKeys.contains(
+ CameraCharacteristics.EDGE_AVAILABLE_EDGE_MODES));
+
+ }
+
+ }
+ }
+
public void testCameraCharacteristicsAndroidFlashInfoAvailable() throws Exception {
String[] ids = mCameraManager.getCameraIdList();
for (int i = 0; i < ids.length; i++) {
@@ -590,6 +613,29 @@
}
}
+ public void testCameraCharacteristicsAndroidNoiseReductionAvailableNoiseReductionModes() throws Exception {
+ String[] ids = mCameraManager.getCameraIdList();
+ for (int i = 0; i < ids.length; i++) {
+ CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
+ assertNotNull(String.format("Can't get camera characteristics from: ID %s", ids[i]),
+ props);
+
+ {
+
+ assertNotNull("Invalid property: android.noiseReduction.availableNoiseReductionModes",
+ props.get(CameraCharacteristics.NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES));
+
+ List<Key<?>> allKeys = props.getKeys();
+ assertNotNull(String.format("Can't get camera characteristics keys from: ID %s",
+ ids[i], props));
+ assertTrue("Key not in keys list: android.noiseReduction.availableNoiseReductionModes", allKeys.contains(
+ CameraCharacteristics.NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES));
+
+ }
+
+ }
+ }
+
public void testCameraCharacteristicsAndroidRequestMaxNumOutputStreams() throws Exception {
String[] ids = mCameraManager.getCameraIdList();
for (int i = 0; i < ids.length; i++) {
@@ -1346,6 +1392,29 @@
}
}
+ public void testCameraCharacteristicsAndroidTonemapAvailableToneMapModes() throws Exception {
+ String[] ids = mCameraManager.getCameraIdList();
+ for (int i = 0; i < ids.length; i++) {
+ CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
+ assertNotNull(String.format("Can't get camera characteristics from: ID %s", ids[i]),
+ props);
+
+ {
+
+ assertNotNull("Invalid property: android.tonemap.availableToneMapModes",
+ props.get(CameraCharacteristics.TONEMAP_AVAILABLE_TONE_MAP_MODES));
+
+ List<Key<?>> allKeys = props.getKeys();
+ assertNotNull(String.format("Can't get camera characteristics keys from: ID %s",
+ ids[i], props));
+ assertTrue("Key not in keys list: android.tonemap.availableToneMapModes", allKeys.contains(
+ CameraCharacteristics.TONEMAP_AVAILABLE_TONE_MAP_MODES));
+
+ }
+
+ }
+ }
+
public void testCameraCharacteristicsAndroidInfoSupportedHardwareLevel() throws Exception {
String[] ids = mCameraManager.getCameraIdList();
for (int i = 0; i < ids.length; i++) {
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraDeviceTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraDeviceTest.java
index e61e373..1c1fb19 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraDeviceTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraDeviceTest.java
@@ -26,24 +26,17 @@
import android.graphics.ImageFormat;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
-import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.CaptureFailure;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
-import android.hardware.camera2.cts.helpers.CameraErrorCollector;
-import android.media.Image;
-import android.media.ImageReader;
-import android.os.Handler;
-import android.os.HandlerThread;
+import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
import android.os.SystemClock;
-import android.test.AndroidTestCase;
import android.util.Log;
import android.view.Surface;
import com.android.ex.camera2.blocking.BlockingStateListener;
-import org.hamcrest.CoreMatchers;
import org.mockito.ArgumentMatcher;
import java.util.ArrayList;
@@ -53,33 +46,20 @@
/**
* <p>Basic test for CameraDevice APIs.</p>
*/
-public class CameraDeviceTest extends AndroidTestCase {
+public class CameraDeviceTest extends Camera2AndroidTestCase {
private static final String TAG = "CameraDeviceTest";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
- private static final int CAMERA_CONFIGURE_TIMEOUT_MS = 2000;
- private static final int CAPTURE_WAIT_TIMEOUT_MS = 2000;
private static final int ERROR_LISTENER_WAIT_TIMEOUT_MS = 1000;
private static final int REPEATING_CAPTURE_EXPECTED_RESULT_COUNT = 5;
- // VGA size capture is required by CDD.
- private static final int DEFAULT_CAPTURE_WIDTH = 640;
- private static final int DEFAULT_CAPTURE_HEIGHT = 480;
private static final int MAX_NUM_IMAGES = 5;
private static final int MIN_FPS_REQUIRED_FOR_STREAMING = 20;
private static final int AE_REGION_INDEX = 0;
private static final int AWB_REGION_INDEX = 1;
private static final int AF_REGION_INDEX = 2;
- private CameraManager mCameraManager;
- private BlockingStateListener mCameraListener;
- private HandlerThread mHandlerThread;
- private Handler mHandler;
+ private BlockingStateListener mCameraMockListener;
private int mLatestState = STATE_UNINITIALIZED;
- private ImageReader mReader;
- private Surface mSurface;
- private String[] mCameraIds;
- private CameraErrorCollector mCollector;
-
private static int[] mTemplates = new int[] {
CameraDevice.TEMPLATE_PREVIEW,
CameraDevice.TEMPLATE_RECORD,
@@ -91,18 +71,11 @@
public void setContext(Context context) {
super.setContext(context);
/**
- * Workaround for mockito and JB-MR2 incompatibility
- *
- * Avoid java.lang.IllegalArgumentException: dexcache == null
- * https://code.google.com/p/dexmaker/issues/detail?id=2
- */
- System.setProperty("dexmaker.dexcache", mContext.getCacheDir().toString());
- /**
- * Create errorlistener in context scope, to catch asynchronous device error.
+ * Create error listener in context scope, to catch asynchronous device error.
* Use spy object here since we want to use the SimpleDeviceListener callback
* implementation (spy doesn't stub the functions unless we ask it to do so).
*/
- mCameraListener = spy(new BlockingStateListener());
+ mCameraMockListener = spy(new BlockingStateListener());
}
@Override
@@ -114,37 +87,21 @@
* fail the rest of the tests. This is especially needed when error
* callback is fired too late.
*/
- verify(mCameraListener, never())
+ verify(mCameraMockListener, never())
.onError(
any(CameraDevice.class),
anyInt());
- verify(mCameraListener, never())
+ verify(mCameraMockListener, never())
.onDisconnected(
any(CameraDevice.class));
- mCameraManager = (CameraManager)mContext.getSystemService(Context.CAMERA_SERVICE);
- assertNotNull("Can't connect to camera manager", mCameraManager);
- mCameraIds = mCameraManager.getCameraIdList();
- mCollector = new CameraErrorCollector();
- assertNotNull("Camera ids shouldn't be null", mCameraIds);
- mHandlerThread = new HandlerThread(TAG);
- mHandlerThread.start();
- mHandler = new Handler(mHandlerThread.getLooper());
- createDefaultSurface();
+ mCameraListener = mCameraMockListener;
+ createImageReader(DEFAULT_CAPTURE_SIZE, ImageFormat.YUV_420_888, MAX_NUM_IMAGES,
+ new ImageDropperListener());
}
@Override
protected void tearDown() throws Exception {
- mHandlerThread.quitSafely();
- mReader.close();
-
- try {
- mCollector.verify();
- } catch (Throwable e) {
- // When new Exception(e) is used, exception info will be printed twice.
- throw new Exception(e.getMessage());
- }
-
super.tearDown();
}
@@ -273,20 +230,15 @@
}
public void testCameraDeviceCreateCaptureBuilder() throws Exception {
- String[] ids = mCameraManager.getCameraIdList();
- for (int i = 0; i < ids.length; i++) {
- CameraDevice camera = null;
+ for (int i = 0; i < mCameraIds.length; i++) {
try {
- camera = CameraTestUtils.openCamera(mCameraManager, ids[i], mHandler);
- assertNotNull(
- String.format("Failed to open camera device ID: %s", ids[i]), camera);
-
+ openDevice(mCameraIds[i], mCameraMockListener);
/**
* Test: that each template type is supported, and that its required fields are
* present.
*/
for (int j = 0; j < mTemplates.length; j++) {
- CaptureRequest.Builder capReq = camera.createCaptureRequest(mTemplates[j]);
+ CaptureRequest.Builder capReq = mCamera.createCaptureRequest(mTemplates[j]);
assertNotNull("Failed to create capture request", capReq);
assertNotNull("Missing field: SENSOR_EXPOSURE_TIME",
capReq.get(CaptureRequest.SENSOR_EXPOSURE_TIME));
@@ -295,55 +247,196 @@
}
}
finally {
- if (camera != null) {
- camera.close();
- }
+ closeDevice(mCameraIds[i], mCameraMockListener);
}
}
}
public void testCameraDeviceSetErrorListener() throws Exception {
- String[] ids = mCameraManager.getCameraIdList();
- for (int i = 0; i < ids.length; i++) {
- CameraDevice camera = null;
+ for (int i = 0; i < mCameraIds.length; i++) {
try {
- camera = CameraTestUtils.openCamera(mCameraManager, ids[i],
- mCameraListener, mHandler);
- assertNotNull(
- String.format("Failed to open camera device %s", ids[i]), camera);
-
+ openDevice(mCameraIds[i], mCameraMockListener);
/**
* Test: that the error listener can be set without problems.
* Also, wait some time to check if device doesn't run into error.
*/
SystemClock.sleep(ERROR_LISTENER_WAIT_TIMEOUT_MS);
- verify(mCameraListener, never())
+ verify(mCameraMockListener, never())
.onError(
any(CameraDevice.class),
anyInt());
}
finally {
- if (camera != null) {
- camera.close();
- }
+ closeDevice(mCameraIds[i], mCameraMockListener);
}
}
}
public void testCameraDeviceCapture() throws Exception {
- runCaptureTest(false, false);
+ runCaptureTest(/*burst*/false, /*repeating*/false, /*flush*/false);
}
public void testCameraDeviceCaptureBurst() throws Exception {
- runCaptureTest(true, false);
+ runCaptureTest(/*burst*/true, /*repeating*/false, /*flush*/false);
}
public void testCameraDeviceRepeatingRequest() throws Exception {
- runCaptureTest(false, true);
+ runCaptureTest(/*burst*/false, /*repeating*/true, /*flush*/false);
}
public void testCameraDeviceRepeatingBurst() throws Exception {
- runCaptureTest(true, true);
+ runCaptureTest(/*burst*/true, /*repeating*/true, /*flush*/false);
+ }
+
+ /**
+ * Test {@link CameraDevice#flush} API.
+ *
+ * <p>
+ * Flush is the fastest way to idle the camera device for reconfiguration
+ * with {@link #configureOutputs}, at the cost of discarding in-progress
+ * work. Once the flush is complete, the idle callback will be called.
+ * </p>
+ */
+ public void testCameraDeviceFlush() throws Exception {
+ runCaptureTest(/*burst*/false, /*repeating*/true, /*flush*/true);
+ runCaptureTest(/*burst*/true, /*repeating*/true, /*flush*/true);
+ /**
+ * TODO: this is only basic test of flush. we probably should also test below cases:
+ *
+ * 1. Performance. Make sure flush is faster than stopRepeating, we can test each one
+ * a couple of times, then compare the average. Also, for flush() alone, we should make
+ * sure it doesn't take too long time (e.g. <100ms for full devices, <500ms for limited
+ * devices), after the flush, we should be able to get all results back very quickly.
+ * This can be done in performance test.
+ *
+ * 2. Make sure all in-flight request comes back after flush, e.g. submit a couple of
+ * long exposure single captures, then flush, then check if we can get the pending
+ * request back quickly.
+ *
+ * 3. Also need check onCaptureSequenceCompleted for repeating burst after flush().
+ */
+ }
+
+ /**
+ * Test invalid capture (e.g. null or empty capture request).
+ */
+ public void testInvalidCapture() throws Exception {
+ for (int i = 0; i < mCameraIds.length; i++) {
+ try {
+ openDevice(mCameraIds[i], mCameraMockListener);
+ waitForState(STATE_UNCONFIGURED, CAMERA_OPEN_TIMEOUT_MS);
+
+ prepareCapture();
+
+ invalidRequestCaptureTestByCamera();
+ }
+ finally {
+ closeDevice(mCameraIds[i], mCameraMockListener);
+ }
+ }
+ }
+
+ private void invalidRequestCaptureTestByCamera() throws Exception {
+ List<CaptureRequest> emptyRequests = new ArrayList<CaptureRequest>();
+ CaptureRequest.Builder requestBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ CaptureRequest unConfiguredRequest = requestBuilder.build();
+ List<CaptureRequest> unConfiguredRequests = new ArrayList<CaptureRequest>();
+ unConfiguredRequests.add(unConfiguredRequest);
+
+ try {
+ // Test: CameraDevice capture should throw IAE for null request.
+ mCamera.capture(/*request*/null, /*listener*/null, mHandler);
+ mCollector.addMessage(
+ "CameraDevice capture should throw IllegalArgumentException for null request");
+ } catch (IllegalArgumentException e) {
+ // Pass.
+ }
+
+ try {
+ // Test: CameraDevice capture should throw IAE for request
+ // without surface configured.
+ mCamera.capture(unConfiguredRequest, /*listener*/null, mHandler);
+ mCollector.addMessage("CameraDevice capture should throw " +
+ "IllegalArgumentException for request without surface configured");
+ } catch (IllegalArgumentException e) {
+ // Pass.
+ }
+
+ try {
+ // Test: CameraDevice setRepeatingRequest should throw IAE for null request.
+ mCamera.setRepeatingRequest(/*request*/null, /*listener*/null, mHandler);
+ mCollector.addMessage("CameraDevice setRepeatingRequest should throw" +
+ "IllegalArgumentException for null request");
+ } catch (IllegalArgumentException e) {
+ // Pass.
+ }
+
+ try {
+ // Test: CameraDevice setRepeatingRequest should throw IAE for for request
+ // without surface configured.
+ mCamera.setRepeatingRequest(unConfiguredRequest, /*listener*/null, mHandler);
+ mCollector.addMessage("Capture zero burst should throw IllegalArgumentException" +
+ "for request without surface configured");
+ } catch (IllegalArgumentException e) {
+ // Pass.
+ }
+
+ try {
+ // Test: CameraDevice captureBurst should throw IAE for null request list.
+ mCamera.captureBurst(/*requests*/null, /*listener*/null, mHandler);
+ mCollector.addMessage("CameraDevice captureBurst should throw" +
+ "IllegalArgumentException for null request list");
+ } catch (IllegalArgumentException e) {
+ // Pass.
+ }
+
+ try {
+ // Test: CameraDevice captureBurst should throw IAE for empty request list.
+ mCamera.captureBurst(emptyRequests, /*listener*/null, mHandler);
+ mCollector.addMessage("CameraDevice captureBurst should throw" +
+ " IllegalArgumentException for empty request list");
+ } catch (IllegalArgumentException e) {
+ // Pass.
+ }
+
+ try {
+ // Test: CameraDevice captureBurst should throw IAE for request
+ // without surface configured.
+ mCamera.captureBurst(unConfiguredRequests, /*listener*/null, mHandler);
+ fail("CameraDevice captureBurst should throw IllegalArgumentException" +
+ "for null request list");
+ } catch (IllegalArgumentException e) {
+ // Pass.
+ }
+
+ try {
+ // Test: CameraDevice setRepeatingBurst should throw IAE for null request list.
+ mCamera.setRepeatingBurst(/*requests*/null, /*listener*/null, mHandler);
+ mCollector.addMessage("CameraDevice setRepeatingBurst should throw" +
+ "IllegalArgumentException for null request list");
+ } catch (IllegalArgumentException e) {
+ // Pass.
+ }
+
+ try {
+ // Test: CameraDevice setRepeatingBurst should throw IAE for empty request list.
+ mCamera.setRepeatingBurst(emptyRequests, /*listener*/null, mHandler);
+ mCollector.addMessage("CameraDevice setRepeatingBurst should throw" +
+ "IllegalArgumentException for empty request list");
+ } catch (IllegalArgumentException e) {
+ // Pass.
+ }
+
+ try {
+ // Test: CameraDevice setRepeatingBurst should throw IAE for request
+ // without surface configured.
+ mCamera.setRepeatingBurst(unConfiguredRequests, /*listener*/null, mHandler);
+ mCollector.addMessage("CameraDevice setRepeatingBurst should throw" +
+ "IllegalArgumentException for request without surface configured");
+ } catch (IllegalArgumentException e) {
+ // Pass.
+ }
}
private class IsCameraMetadataNotEmpty<T extends CameraMetadata>
@@ -363,31 +456,33 @@
}
}
- private void runCaptureTest(boolean burst, boolean repeating) throws Exception {
- String[] ids = mCameraManager.getCameraIdList();
- for (int i = 0; i < ids.length; i++) {
- CameraDevice camera = null;
+ /**
+ * Run capture test with different test configurations.
+ *
+ * @param burst If the test uses {@link CameraDevice#captureBurst} or
+ * {@link CameraDevice#setRepeatingBurst} to capture the burst.
+ * @param repeating If the test uses {@link CameraDevice#setRepeatingBurst} or
+ * {@link CameraDevice#setRepeatingRequest} for repeating capture.
+ * @param flush If the test uses {@link CameraDevice#flush} to stop the repeating capture.
+ * It has no effect if repeating is false.
+ */
+ private void runCaptureTest(boolean burst, boolean repeating, boolean flush) throws Exception {
+ for (int i = 0; i < mCameraIds.length; i++) {
try {
- camera = CameraTestUtils.openCamera(mCameraManager, ids[i],
- mCameraListener, mHandler);
- assertNotNull(
- String.format("Failed to open camera device %s", ids[i]), camera);
+ openDevice(mCameraIds[i], mCameraMockListener);
waitForState(STATE_UNCONFIGURED, CAMERA_OPEN_TIMEOUT_MS);
- prepareCapture(camera);
+ prepareCapture();
if (!burst) {
// Test: that a single capture of each template type succeeds.
for (int j = 0; j < mTemplates.length; j++) {
- captureSingleShot(camera, ids[i], mTemplates[j], repeating);
+ captureSingleShot(mCameraIds[i], mTemplates[j], repeating, flush);
}
}
else {
- // Test: burst of zero shots
- captureBurstShot(camera, ids[i], mTemplates, 0, repeating);
-
// Test: burst of one shot
- captureBurstShot(camera, ids[i], mTemplates, 1, repeating);
+ captureBurstShot(mCameraIds[i], mTemplates, 1, repeating, flush);
int[] templates = new int[] {
CameraDevice.TEMPLATE_STILL_CAPTURE,
@@ -398,36 +493,34 @@
};
// Test: burst of 5 shots of the same template type
- captureBurstShot(camera, ids[i], templates, templates.length, repeating);
+ captureBurstShot(mCameraIds[i], templates, templates.length, repeating, flush);
// Test: burst of 5 shots of different template types
- captureBurstShot(camera, ids[i], mTemplates, mTemplates.length, repeating);
+ captureBurstShot(
+ mCameraIds[i], mTemplates, mTemplates.length, repeating, flush);
}
- verify(mCameraListener, never())
+ verify(mCameraMockListener, never())
.onError(
any(CameraDevice.class),
anyInt());
}
finally {
- if (camera != null) {
- camera.close();
- }
+ closeDevice(mCameraIds[i], mCameraMockListener);
}
}
}
private void captureSingleShot(
- CameraDevice camera,
String id,
int template,
- boolean repeating) throws Exception {
+ boolean repeating, boolean flush) throws Exception {
assertEquals("Bad initial state for preparing to capture",
mLatestState, STATE_IDLE);
- CaptureRequest.Builder requestBuilder = camera.createCaptureRequest(template);
+ CaptureRequest.Builder requestBuilder = mCamera.createCaptureRequest(template);
assertNotNull("Failed to create capture request", requestBuilder);
- requestBuilder.addTarget(mSurface);
+ requestBuilder.addTarget(mReaderSurface);
CameraDevice.CaptureListener mockCaptureListener =
mock(CameraDevice.CaptureListener.class);
@@ -435,30 +528,29 @@
Log.v(TAG, String.format("Capturing shot for device %s, template %d",
id, template));
}
- if (!repeating) {
- camera.capture(requestBuilder.build(), mockCaptureListener, mHandler);
- }
- else {
- camera.setRepeatingRequest(requestBuilder.build(), mockCaptureListener,
- mHandler);
- }
+
+ startCapture(requestBuilder.build(), repeating, mockCaptureListener, mHandler);
waitForState(STATE_ACTIVE, CAMERA_CONFIGURE_TIMEOUT_MS);
int expectedCaptureResultCount = repeating ? REPEATING_CAPTURE_EXPECTED_RESULT_COUNT : 1;
- verifyCaptureResults(camera, mockCaptureListener, expectedCaptureResultCount);
+ verifyCaptureResults(mockCaptureListener, expectedCaptureResultCount);
if (repeating) {
- camera.stopRepeating();
+ if (flush) {
+ mCamera.flush();
+ } else {
+ mCamera.stopRepeating();
+ }
}
waitForState(STATE_IDLE, CAMERA_CONFIGURE_TIMEOUT_MS);
}
private void captureBurstShot(
- CameraDevice camera,
String id,
int[] templates,
int len,
- boolean repeating) throws Exception {
+ boolean repeating,
+ boolean flush) throws Exception {
assertEquals("Bad initial state for preparing to capture",
mLatestState, STATE_IDLE);
@@ -466,9 +558,9 @@
assertTrue("Invalid args to capture function", len <= templates.length);
List<CaptureRequest> requests = new ArrayList<CaptureRequest>();
for (int i = 0; i < len; i++) {
- CaptureRequest.Builder requestBuilder = camera.createCaptureRequest(templates[i]);
+ CaptureRequest.Builder requestBuilder = mCamera.createCaptureRequest(templates[i]);
assertNotNull("Failed to create capture request", requestBuilder);
- requestBuilder.addTarget(mSurface);
+ requestBuilder.addTarget(mReaderSurface);
requests.add(requestBuilder.build());
}
CameraDevice.CaptureListener mockCaptureListener =
@@ -479,10 +571,10 @@
}
if (!repeating) {
- camera.captureBurst(requests, mockCaptureListener, mHandler);
+ mCamera.captureBurst(requests, mockCaptureListener, mHandler);
}
else {
- camera.setRepeatingBurst(requests, mockCaptureListener, mHandler);
+ mCamera.setRepeatingBurst(requests, mockCaptureListener, mHandler);
}
waitForState(STATE_ACTIVE, CAMERA_CONFIGURE_TIMEOUT_MS);
@@ -491,140 +583,66 @@
expectedResultCount *= REPEATING_CAPTURE_EXPECTED_RESULT_COUNT;
}
- verifyCaptureResults(camera, mockCaptureListener, expectedResultCount);
+ verifyCaptureResults(mockCaptureListener, expectedResultCount);
if (repeating) {
- camera.stopRepeating();
+ if (flush) {
+ mCamera.flush();
+ } else {
+ mCamera.stopRepeating();
+ }
}
waitForState(STATE_IDLE, CAMERA_CONFIGURE_TIMEOUT_MS);
}
// Precondition: Device must be in known IDLE/UNCONFIGURED state (has been waited for)
- private void prepareCapture(CameraDevice camera) throws Exception {
+ private void prepareCapture() throws Exception {
assertTrue("Bad initial state for preparing to capture",
mLatestState == STATE_IDLE || mLatestState == STATE_UNCONFIGURED);
List<Surface> outputSurfaces = new ArrayList<Surface>(1);
- outputSurfaces.add(mSurface);
- camera.configureOutputs(outputSurfaces);
+ outputSurfaces.add(mReaderSurface);
+ mCamera.configureOutputs(outputSurfaces);
waitForState(STATE_BUSY, CAMERA_BUSY_TIMEOUT_MS);
waitForState(STATE_IDLE, CAMERA_IDLE_TIMEOUT_MS);
- }
-
- /**
- * Dummy listener that release the image immediately once it is available.
- * It can be used for the case where we don't care the image data at all.
- * TODO: move it to the CameraTestUtil class.
- */
- private class ImageDropperListener implements ImageReader.OnImageAvailableListener {
- @Override
- public void onImageAvailable(ImageReader reader) {
- Image image = null;
- try {
- image = reader.acquireNextImage();
- } finally {
- if (image != null) {
- image.close();
- }
- }
- }
- }
-
- private void createDefaultSurface() throws Exception {
- mReader =
- ImageReader.newInstance(DEFAULT_CAPTURE_WIDTH,
- DEFAULT_CAPTURE_HEIGHT,
- ImageFormat.YUV_420_888,
- MAX_NUM_IMAGES);
- mSurface = mReader.getSurface();
- // Create dummy image listener since we don't care the image data in this test.
- ImageReader.OnImageAvailableListener listener = new ImageDropperListener();
- mReader.setOnImageAvailableListener(listener, mHandler);
- }
+}
private void waitForState(int state, long timeout) {
- mCameraListener.waitForState(state, timeout);
+ mCameraMockListener.waitForState(state, timeout);
mLatestState = state;
}
private void verifyCaptureResults(
- CameraDevice camera,
CameraDevice.CaptureListener mockListener,
int expectResultCount) {
// Should receive expected number of capture results.
verify(mockListener,
timeout(CAPTURE_WAIT_TIMEOUT_MS).atLeast(expectResultCount))
.onCaptureCompleted(
- eq(camera),
+ eq(mCamera),
isA(CaptureRequest.class),
argThat(new IsCameraMetadataNotEmpty<CaptureResult>()));
// Should not receive any capture failed callbacks.
verify(mockListener, never())
.onCaptureFailed(
- eq(camera),
+ eq(mCamera),
argThat(new IsCameraMetadataNotEmpty<CaptureRequest>()),
isA(CaptureFailure.class));
// Should receive expected number of capture shutter calls
verify(mockListener,
atLeast(expectResultCount))
.onCaptureStarted(
- eq(camera),
+ eq(mCamera),
isA(CaptureRequest.class),
anyLong());
}
- /**
- * Check if the key is non-null and the value is equal to target.
- * Only check non-null if the target is null.
- */
- private <T> void expectKeyEquals(CaptureRequest.Builder request,
- CameraMetadata.Key<T> key, T target) {
- assertTrue("request, key and target shouldn't be null",
- request != null && key != null && target != null);
-
- if (!expectKeyNotNull(request, key)) {
- return;
- }
-
- T value = request.get(key);
- String reason = "Key " + key.getName() + " value " + value.toString()
- + " doesn't match the expected value " + target.toString();
- mCollector.checkThat(reason, value, CoreMatchers.equalTo(target));
- }
-
- /**
- * Check if the key is non-null and the value is not equal to target.
- */
- private <T> void expectKeyValueNotEquals(CaptureRequest.Builder request,
- CameraMetadata.Key<T> key, T target) {
- assertTrue("request, key and target shouldn't be null",
- request != null && key != null && target != null);
-
- if (!expectKeyNotNull(request, key)) {
- return;
- }
-
- T value = request.get(key);
- String reason = "Key " + key.getName() + " shouldn't have value " + value.toString();
- mCollector.checkThat(reason, value, CoreMatchers.not(target));
- }
-
- private <T> boolean expectKeyNotNull(CaptureRequest.Builder request,
- CameraMetadata.Key<T> key) {
-
- T value = request.get(key);
- if (value == null) {
- mCollector.addMessage("Key " + key.getName() + " shouldn't be null");
- return false;
- }
-
- return true;
- }
-
private void checkFpsRange(CaptureRequest.Builder request, int template,
CameraCharacteristics props) {
- if (!expectKeyNotNull(request, CONTROL_AE_TARGET_FPS_RANGE)) {
+ Key<int[]> fpsRangeKey = CONTROL_AE_TARGET_FPS_RANGE;
+ int[] fpsRange;
+ if ((fpsRange = mCollector.expectKeyValueNotNull(request, fpsRangeKey)) == null) {
return;
}
@@ -633,12 +651,8 @@
final int CONTROL_AE_TARGET_FPS_RANGE_MIN = 0;
final int CONTROL_AE_TARGET_FPS_RANGE_MAX = 1;
- Key<int[]> key = CONTROL_AE_TARGET_FPS_RANGE;
- int[] fpsRange = request.get(key);
- if (fpsRange.length != CONTROL_AE_TARGET_FPS_RANGE_SIZE) {
- mCollector.addMessage("Expected array length of " + key.getName()
- + " is " + CONTROL_AE_TARGET_FPS_RANGE_SIZE
- + ", actual length is " + fpsRange.length);
+ String cause = "Failed with fps range size check";
+ if (!mCollector.expectEquals(cause, CONTROL_AE_TARGET_FPS_RANGE_SIZE, fpsRange.length)) {
return;
}
@@ -711,8 +725,8 @@
targetAfMode = CONTROL_AF_MODE_OFF;
}
- expectKeyEquals(request, CONTROL_AF_MODE, targetAfMode);
- expectKeyNotNull(request, LENS_FOCUS_DISTANCE);
+ mCollector.expectKeyValueEquals(request, CONTROL_AF_MODE, targetAfMode);
+ mCollector.expectKeyValueNotNull(request, LENS_FOCUS_DISTANCE);
}
/**
@@ -729,7 +743,7 @@
CameraCharacteristics props) {
// 3A settings--control.mode.
if (template != CameraDevice.TEMPLATE_MANUAL) {
- expectKeyEquals(request, CONTROL_MODE, CONTROL_MODE_AUTO);
+ mCollector.expectKeyValueEquals(request, CONTROL_MODE, CONTROL_MODE_AUTO);
}
// 3A settings--AE/AWB/AF.
@@ -737,114 +751,113 @@
checkAfMode(request, template, props);
checkFpsRange(request, template, props);
if (template == CameraDevice.TEMPLATE_MANUAL) {
- expectKeyEquals(request, CONTROL_MODE, CONTROL_MODE_OFF);
- expectKeyEquals(request, CONTROL_AE_MODE, CONTROL_AE_MODE_OFF);
- expectKeyEquals(request, CONTROL_AWB_MODE, CONTROL_AWB_MODE_OFF);
+ mCollector.expectKeyValueEquals(request, CONTROL_MODE, CONTROL_MODE_OFF);
+ mCollector.expectKeyValueEquals(request, CONTROL_AE_MODE, CONTROL_AE_MODE_OFF);
+ mCollector.expectKeyValueEquals(request, CONTROL_AWB_MODE, CONTROL_AWB_MODE_OFF);
} else {
- expectKeyEquals(request, CONTROL_AE_MODE, CONTROL_AE_MODE_ON);
- expectKeyValueNotEquals(request, CONTROL_AE_ANTIBANDING_MODE,
+ mCollector.expectKeyValueEquals(request, CONTROL_AE_MODE, CONTROL_AE_MODE_ON);
+ mCollector.expectKeyValueNotEquals(request, CONTROL_AE_ANTIBANDING_MODE,
CONTROL_AE_ANTIBANDING_MODE_OFF);
- expectKeyEquals(request, CONTROL_AE_EXPOSURE_COMPENSATION, 0);
- expectKeyEquals(request, CONTROL_AE_LOCK, false);
- expectKeyEquals(request, CONTROL_AE_PRECAPTURE_TRIGGER,
+ mCollector.expectKeyValueEquals(request, CONTROL_AE_EXPOSURE_COMPENSATION, 0);
+ mCollector.expectKeyValueEquals(request, CONTROL_AE_LOCK, false);
+ mCollector.expectKeyValueEquals(request, CONTROL_AE_PRECAPTURE_TRIGGER,
CONTROL_AE_PRECAPTURE_TRIGGER_IDLE);
- expectKeyEquals(request, CONTROL_AF_TRIGGER, CONTROL_AF_TRIGGER_IDLE);
+ mCollector.expectKeyValueEquals(request, CONTROL_AF_TRIGGER, CONTROL_AF_TRIGGER_IDLE);
- expectKeyEquals(request, CONTROL_AWB_MODE, CONTROL_AWB_MODE_AUTO);
- expectKeyEquals(request, CONTROL_AWB_LOCK, false);
+ mCollector.expectKeyValueEquals(request, CONTROL_AWB_MODE, CONTROL_AWB_MODE_AUTO);
+ mCollector.expectKeyValueEquals(request, CONTROL_AWB_LOCK, false);
// Check 3A regions.
if (VERBOSE) {
Log.v(TAG, "maxRegions is: " + Arrays.toString(maxRegions));
}
if (maxRegions[AE_REGION_INDEX] > 0) {
- expectKeyNotNull(request, CONTROL_AE_REGIONS);
+ mCollector.expectKeyValueNotNull(request, CONTROL_AE_REGIONS);
}
if (maxRegions[AWB_REGION_INDEX] > 0) {
- expectKeyNotNull(request, CONTROL_AWB_REGIONS);
+ mCollector.expectKeyValueNotNull(request, CONTROL_AWB_REGIONS);
}
if (maxRegions[AF_REGION_INDEX] > 0) {
- expectKeyNotNull(request, CONTROL_AF_REGIONS);
+ mCollector.expectKeyValueNotNull(request, CONTROL_AF_REGIONS);
}
}
// Sensor settings.
- float[] availableApertures = props.get(CameraCharacteristics.LENS_INFO_AVAILABLE_APERTURES);
+ float[] availableApertures =
+ props.get(CameraCharacteristics.LENS_INFO_AVAILABLE_APERTURES);
if (availableApertures.length > 1) {
- expectKeyNotNull(request, LENS_APERTURE);
+ mCollector.expectKeyValueNotNull(request, LENS_APERTURE);
}
float[] availableFilters =
props.get(CameraCharacteristics.LENS_INFO_AVAILABLE_FILTER_DENSITIES);
if (availableFilters.length > 1) {
- expectKeyNotNull(request, LENS_FILTER_DENSITY);
+ mCollector.expectKeyValueNotNull(request, LENS_FILTER_DENSITY);
}
float[] availableFocalLen =
props.get(CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS);
if (availableFocalLen.length > 1) {
- expectKeyNotNull(request, LENS_FOCAL_LENGTH);
+ mCollector.expectKeyValueNotNull(request, LENS_FOCAL_LENGTH);
}
byte[] availableOIS =
props.get(CameraCharacteristics.LENS_INFO_AVAILABLE_OPTICAL_STABILIZATION);
if (availableOIS.length > 1) {
- expectKeyNotNull(request, LENS_OPTICAL_STABILIZATION_MODE);
+ mCollector.expectKeyValueNotNull(request, LENS_OPTICAL_STABILIZATION_MODE);
}
- expectKeyEquals(request, BLACK_LEVEL_LOCK, false);
- expectKeyNotNull(request, SENSOR_FRAME_DURATION);
- expectKeyNotNull(request, SENSOR_EXPOSURE_TIME);
- expectKeyNotNull(request, SENSOR_SENSITIVITY);
+ mCollector.expectKeyValueEquals(request, BLACK_LEVEL_LOCK, false);
+ mCollector.expectKeyValueNotNull(request, SENSOR_FRAME_DURATION);
+ mCollector.expectKeyValueNotNull(request, SENSOR_EXPOSURE_TIME);
+ mCollector.expectKeyValueNotNull(request, SENSOR_SENSITIVITY);
// ISP-processing settings.
- expectKeyEquals(request, STATISTICS_FACE_DETECT_MODE, STATISTICS_FACE_DETECT_MODE_OFF);
- expectKeyEquals(request, FLASH_MODE, FLASH_MODE_OFF);
- expectKeyEquals(
+ mCollector.expectKeyValueEquals(
+ request, STATISTICS_FACE_DETECT_MODE, STATISTICS_FACE_DETECT_MODE_OFF);
+ mCollector.expectKeyValueEquals(request, FLASH_MODE, FLASH_MODE_OFF);
+ mCollector.expectKeyValueEquals(
request, STATISTICS_LENS_SHADING_MAP_MODE, STATISTICS_LENS_SHADING_MAP_MODE_OFF);
if (template == CameraDevice.TEMPLATE_STILL_CAPTURE) {
// TODO: Update these to check for availability (e.g. availableColorCorrectionModes)
- expectKeyEquals(
+ mCollector.expectKeyValueEquals(
request, COLOR_CORRECTION_MODE, COLOR_CORRECTION_MODE_HIGH_QUALITY);
- expectKeyEquals(request, EDGE_MODE, EDGE_MODE_HIGH_QUALITY);
- expectKeyEquals(
+ mCollector.expectKeyValueEquals(request, EDGE_MODE, EDGE_MODE_HIGH_QUALITY);
+ mCollector.expectKeyValueEquals(
request, NOISE_REDUCTION_MODE, NOISE_REDUCTION_MODE_HIGH_QUALITY);
- expectKeyEquals(request, TONEMAP_MODE, TONEMAP_MODE_HIGH_QUALITY);
+ mCollector.expectKeyValueEquals(request, TONEMAP_MODE, TONEMAP_MODE_HIGH_QUALITY);
} else {
- expectKeyNotNull(request, EDGE_MODE);
- expectKeyNotNull(request, NOISE_REDUCTION_MODE);
- expectKeyValueNotEquals(request, TONEMAP_MODE, TONEMAP_MODE_CONTRAST_CURVE);
+ mCollector.expectKeyValueNotNull(request, EDGE_MODE);
+ mCollector.expectKeyValueNotNull(request, NOISE_REDUCTION_MODE);
+ mCollector.expectKeyValueNotEquals(request, TONEMAP_MODE, TONEMAP_MODE_CONTRAST_CURVE);
}
- expectKeyEquals(request, CONTROL_CAPTURE_INTENT, template);
+ mCollector.expectKeyValueEquals(request, CONTROL_CAPTURE_INTENT, template);
// TODO: use the list of keys from CameraCharacteristics to avoid expecting
// keys which are not available by this CameraDevice.
}
private void captureTemplateTestByCamera(String cameraId, int template) throws Exception {
- CameraDevice camera = null;
try {
- camera = CameraTestUtils.openCamera(mCameraManager, cameraId, mHandler);
- assertNotNull(String.format("Failed to open camera device ID: %s", cameraId), camera);
+ openDevice(cameraId, mCameraMockListener);
+
assertTrue("Camera template " + template + " is out of range!",
template >= CameraDevice.TEMPLATE_PREVIEW
&& template <= CameraDevice.TEMPLATE_MANUAL);
mCollector.setCameraId(cameraId);
- CaptureRequest.Builder request = camera.createCaptureRequest(template);
+ CaptureRequest.Builder request = mCamera.createCaptureRequest(template);
assertNotNull("Failed to create capture request for template " + template, request);
- CameraCharacteristics props = mCameraManager.getCameraCharacteristics(cameraId);
+ CameraCharacteristics props = mStaticInfo.getCharacteristics();
checkRequestForTemplate(request, template, props);
}
finally {
- if (camera != null) {
- camera.close();
- }
+ closeDevice(cameraId, mCameraMockListener);
}
}
}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraManagerTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraManagerTest.java
index 6a567ac..9719278 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraManagerTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraManagerTest.java
@@ -28,6 +28,7 @@
import android.hardware.camera2.CameraDevice.StateListener;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.cts.CameraTestUtils.MockStateListener;
+import android.hardware.camera2.cts.helpers.CameraErrorCollector;
import android.os.Handler;
import android.os.HandlerThread;
import android.test.AndroidTestCase;
@@ -56,6 +57,7 @@
private HandlerThread mHandlerThread;
private Handler mHandler;
private BlockingStateListener mCameraListener;
+ private CameraErrorCollector mCollector;
@Override
public void setContext(Context context) {
@@ -70,18 +72,13 @@
@Override
protected void setUp() throws Exception {
super.setUp();
- /**
- * Workaround for mockito and JB-MR2 incompatibility
- *
- * Avoid java.lang.IllegalArgumentException: dexcache == null
- * https://code.google.com/p/dexmaker/issues/detail?id=2
- */
- System.setProperty("dexmaker.dexcache", mContext.getCacheDir().toString());
+
mCameraListener = spy(new BlockingStateListener());
mHandlerThread = new HandlerThread(TAG);
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
+ mCollector = new CameraErrorCollector();
}
@Override
@@ -89,7 +86,14 @@
mHandlerThread.quitSafely();
mHandler = null;
- super.tearDown();
+ try {
+ mCollector.verify();
+ } catch (Throwable e) {
+ // When new Exception(e) is used, exception info will be printed twice.
+ throw new Exception(e.getMessage());
+ } finally {
+ super.tearDown();
+ }
}
/**
@@ -394,14 +398,10 @@
public void testCameraManagerOpenCameraTwice() throws Exception {
String[] ids = mCameraManager.getCameraIdList();
- // No cameras available. Trivial pass.
- if (ids.length == 0) {
- return;
- }
-
// Test across every camera device.
for (int i = 0; i < ids.length; ++i) {
CameraDevice successCamera = null;
+ mCollector.setCameraId(ids[i]);
try {
MockStateListener mockSuccessListener = MockStateListener.mock();
@@ -419,18 +419,30 @@
mHandler);
} catch (CameraAccessException e) {
// Optional (but common). Camera might fail asynchronously only.
- assertEquals("If second camera open fails immediately, " +
- "must be due to camera being busy for ID: " + ids[i],
- CameraAccessException.CAMERA_ERROR,
- e.getReason());
+ // Don't assert here, otherwise, all subsequent tests will fail because the
+ // opened camera is never closed.
+ mCollector.expectEquals(
+ "If second camera open fails immediately, must be due to"
+ + "camera being busy for ID: " + ids[i],
+ CameraAccessException.CAMERA_ERROR, e.getReason());
}
+ successListener.waitForState(BlockingStateListener.STATE_OPENED,
+ CameraTestUtils.CAMERA_IDLE_TIMEOUT_MS);
+ // Have to get the successCamera here, otherwise, it won't be
+ // closed if STATE_ERROR timeout exception occurs.
+ ArgumentCaptor<CameraDevice> argument =
+ ArgumentCaptor.forClass(CameraDevice.class);
+ verify(mockSuccessListener, atLeastOnce()).onOpened(argument.capture());
+ successCamera = argument.getValue();
successListener.waitForState(BlockingStateListener.STATE_UNCONFIGURED,
CameraTestUtils.CAMERA_IDLE_TIMEOUT_MS);
+
failListener.waitForState(BlockingStateListener.STATE_ERROR,
CameraTestUtils.CAMERA_IDLE_TIMEOUT_MS);
- successCamera = verifyCameraStateOpenedThenUnconfigured(ids[i], mockSuccessListener);
+ successCamera = verifyCameraStateOpenedThenUnconfigured(
+ ids[i], mockSuccessListener);
verify(mockFailListener)
.onError(
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java
index 9a6c08f..14c59cf 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java
@@ -72,7 +72,16 @@
public static final int CAMERA_ACTIVE_TIMEOUT_MS = 1000;
public static final int CAMERA_BUSY_TIMEOUT_MS = 1000;
public static final int CAMERA_UNCONFIGURED_TIMEOUT_MS = 1000;
+ public static final int CAMERA_CONFIGURE_TIMEOUT_MS = 2000;
+ public static final int CAPTURE_RESULT_TIMEOUT_MS = 1000;
+ /**
+ * Dummy listener that release the image immediately once it is available.
+ *
+ * <p>
+ * It can be used for the case where we don't care the image data at all.
+ * </p>
+ */
public static class ImageDropperListener implements ImageReader.OnImageAvailableListener {
@Override
public void onImageAvailable(ImageReader reader) {
@@ -87,6 +96,34 @@
}
}
+ public static class SimpleImageReaderListener
+ implements ImageReader.OnImageAvailableListener {
+ private final LinkedBlockingQueue<Image> mQueue =
+ new LinkedBlockingQueue<Image>();
+
+ @Override
+ public void onImageAvailable(ImageReader reader) {
+ try {
+ mQueue.put(reader.acquireNextImage());
+ } catch (InterruptedException e) {
+ throw new UnsupportedOperationException(
+ "Can't handle InterruptedException in onImageAvailable");
+ }
+ }
+
+ /**
+ * Get an image from the image reader.
+ *
+ * @param timeout Timeout value for the wait.
+ * @return The image from the image reader.
+ */
+ public Image getImage(long timeout) throws InterruptedException {
+ Image image = mQueue.poll(timeout, TimeUnit.MILLISECONDS);
+ assertNotNull("Wait for an image timed out in " + timeout + "ms", image);
+ return image;
+ }
+ }
+
public static class SimpleCaptureListener extends CameraDevice.CaptureListener {
private final LinkedBlockingQueue<CaptureResult> mQueue =
new LinkedBlockingQueue<CaptureResult>();
@@ -117,10 +154,47 @@
int frameNumber) {
}
- public CaptureResult getCaptureResult(long timeout) throws InterruptedException {
- CaptureResult result = mQueue.poll(timeout, TimeUnit.MILLISECONDS);
- assertNotNull("Wait for a capture result timed out in " + timeout + "ms", result);
- return result;
+ public CaptureResult getCaptureResult(long timeout) {
+ try {
+ CaptureResult result = mQueue.poll(timeout, TimeUnit.MILLISECONDS);
+ assertNotNull("Wait for a capture result timed out in " + timeout + "ms", result);
+ return result;
+ } catch (InterruptedException e) {
+ throw new UnsupportedOperationException("Unhandled interrupted exception", e);
+ }
+ }
+
+ /**
+ * Get the {@link #CaptureResult capture result} for a given
+ * {@link #CaptureRequest capture request}.
+ *
+ * @param myRequest The {@link #CaptureRequest capture request} whose
+ * corresponding {@link #CaptureResult capture result} was
+ * being waited for
+ * @param numResultsWait Number of frames to wait for the capture result
+ * before timeout.
+ * @throws TimeoutRuntimeException If more than numResultsWait results are
+ * seen before the result matching myRequest arrives, or each
+ * individual wait for result times out after
+ * {@value #CAPTURE_RESULT_TIMEOUT_MS}ms.
+ */
+ public CaptureResult getCaptureResultForRequest(CaptureRequest myRequest,
+ int numResultsWait) {
+ if (numResultsWait < 0) {
+ throw new IllegalArgumentException("numResultsWait must be no less than 0");
+ }
+
+ CaptureResult result;
+ int i = 0;
+ do {
+ result = getCaptureResult(CAPTURE_RESULT_TIMEOUT_MS);
+ if (result.getRequest().equals(myRequest)) {
+ return result;
+ }
+ } while (i++ < numResultsWait);
+
+ throw new TimeoutRuntimeException("Unable to get the expected capture result after "
+ + "waiting for " + numResultsWait + " results");
}
}
@@ -187,7 +261,7 @@
* @param listener The callback CameraDevice will notify when capture results are available.
*/
public static void configureCameraOutputs(CameraDevice camera, List<Surface> outputSurfaces,
- BlockingStateListener listener) throws Exception {
+ BlockingStateListener listener) throws CameraAccessException {
camera.configureOutputs(outputSurfaces);
listener.waitForState(STATE_BUSY, CAMERA_BUSY_TIMEOUT_MS);
if (outputSurfaces == null || outputSurfaces.size() == 0) {
@@ -344,7 +418,7 @@
}
public static Size[] getSupportedSizeForFormat(int format, String cameraId,
- CameraManager cameraManager) throws Exception {
+ CameraManager cameraManager) throws CameraAccessException {
CameraMetadata.Key<Size[]> key = null;
CameraCharacteristics properties = cameraManager.getCameraCharacteristics(cameraId);
assertNotNull("Can't get camera characteristics!", properties);
@@ -386,46 +460,89 @@
* Get sorted size list in descending order. Remove the sizes larger than
* the bound. If the bound is null, don't do the size bound filtering.
*/
- static public List<Size> getSupportedPreviewSizes(
- String cameraId, CameraManager cameraManager, Size bound) throws Exception {
+ static public List<Size> getSupportedPreviewSizes(String cameraId,
+ CameraManager cameraManager, Size bound) throws CameraAccessException {
+ return getSortedSizesForFormat(cameraId, cameraManager, ImageFormat.YUV_420_888, bound);
+ }
+
+ /**
+ * Get sorted (descending order) size list for given format. Remove the sizes larger than
+ * the bound. If the bound is null, don't do the size bound filtering.
+ */
+ static private List<Size> getSortedSizesForFormat(String cameraId,
+ CameraManager cameraManager, int format, Size bound) throws CameraAccessException {
Comparator<Size> comparator = new SizeComparator();
- Size[] sizes = getSupportedSizeForFormat(ImageFormat.YUV_420_888, cameraId, cameraManager);
- List<Size> supportedPreviewSizes = null;
+ Size[] sizes = getSupportedSizeForFormat(format, cameraId, cameraManager);
+ List<Size> sortedSizes = null;
if (bound != null) {
- supportedPreviewSizes = new ArrayList<Size>(/* capacity */1);
+ sortedSizes = new ArrayList<Size>(/*capacity*/1);
for (Size sz : sizes) {
if (comparator.compare(sz, bound) <= 0) {
- supportedPreviewSizes.add(sz);
+ sortedSizes.add(sz);
}
}
} else {
- supportedPreviewSizes = Arrays.asList(sizes);
+ sortedSizes = Arrays.asList(sizes);
}
- assertTrue("Supported preview size should have at least one element",
- supportedPreviewSizes.size() > 0);
+ assertTrue("Supported size list should have at least one element",
+ sortedSizes.size() > 0);
- Collections.sort(supportedPreviewSizes, comparator);
+ Collections.sort(sortedSizes, comparator);
// Make it in descending order.
- Collections.reverse(supportedPreviewSizes);
- return supportedPreviewSizes;
+ Collections.reverse(sortedSizes);
+ return sortedSizes;
}
- static public List<Size> getSupportedVideoSizes(
- String cameraId, CameraManager cameraManager, Size bound) throws Exception {
- return getSupportedPreviewSizes(cameraId, cameraManager, bound);
+ /**
+ * Get supported video size list for a given camera device.
+ *
+ * <p>
+ * Filter out the sizes that are larger than the bound. If the bound is
+ * null, don't do the size bound filtering.
+ * </p>
+ */
+ static public List<Size> getSupportedVideoSizes(String cameraId,
+ CameraManager cameraManager, Size bound) throws CameraAccessException {
+ return getSortedSizesForFormat(cameraId, cameraManager, ImageFormat.YUV_420_888, bound);
+ }
+
+ /**
+ * Get supported video size list (descending order) for a given camera device.
+ *
+ * <p>
+ * Filter out the sizes that are larger than the bound. If the bound is
+ * null, don't do the size bound filtering.
+ * </p>
+ */
+ static public List<Size> getSupportedStillSizes(String cameraId,
+ CameraManager cameraManager, Size bound) throws CameraAccessException {
+ return getSortedSizesForFormat(cameraId, cameraManager, ImageFormat.JPEG, bound);
}
static public Size getMinPreviewSize(String cameraId, CameraManager cameraManager)
- throws Exception {
+ throws CameraAccessException {
List<Size> sizes = getSupportedPreviewSizes(cameraId, cameraManager, null);
return sizes.get(sizes.size() - 1);
}
+ /**
+ * Get max supported preview size for a camera device.
+ */
+ static public Size getMaxPreviewSize(String cameraId, CameraManager cameraManager)
+ throws CameraAccessException {
+ return getMaxPreviewSize(cameraId, cameraManager, /*bound*/null);
+ }
+
+ /**
+ * Get max preview size for a camera device in the supported sizes that are no larger
+ * than the bound.
+ */
static public Size getMaxPreviewSize(String cameraId, CameraManager cameraManager, Size bound)
- throws Exception {
+ throws CameraAccessException {
List<Size> sizes = getSupportedPreviewSizes(cameraId, cameraManager, bound);
return sizes.get(0);
}
+
/**
* Get the largest size by area.
*
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureRequestTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureRequestTest.java
index 3dd9325..6cdb26d 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureRequestTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureRequestTest.java
@@ -17,13 +17,13 @@
package android.hardware.camera2.cts;
import static android.hardware.camera2.cts.CameraTestUtils.*;
-import static android.hardware.camera2.CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE;
-import static android.hardware.camera2.CameraMetadata.*;
+import static android.hardware.camera2.CameraCharacteristics.*;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.Size;
+import android.hardware.camera2.cts.CameraTestUtils.SimpleCaptureListener;
import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase;
import android.util.Log;
@@ -42,12 +42,21 @@
private static final String TAG = "CaptureRequestTest";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
private static final int NUM_FRAMES_VERIFIED = 15;
- private static final long WAIT_FOR_RESULT_TIMEOUT_MS = 3000;
- private static final long DEFAULT_EXP_TIME_NS = 30000000L; // 30ms
- private static final int DEFAULT_SENSITIVITY = 100; // 10ms
+ /** 30ms exposure time must be supported by full capability devices. */
+ private static final long DEFAULT_EXP_TIME_NS = 30000000L;
+ private static final int DEFAULT_SENSITIVITY = 100;
private static final int RGGB_COLOR_CHANNEL_COUNT = 4;
private static final int MAX_SHADING_MAP_SIZE = 64 * 64 * RGGB_COLOR_CHANNEL_COUNT;
private static final int MIN_SHADING_MAP_SIZE = 1 * 1 * RGGB_COLOR_CHANNEL_COUNT;
+ private static final long IGORE_REQUESTED_EXPOSURE_TIME_CHECK = -1L;
+ private static final long EXPOSURE_TIME_BOUNDARY_50HZ_NS = 10000000L; // 10ms
+ private static final long EXPOSURE_TIME_BOUNDARY_60HZ_NS = 8300000L; // 8.3ms, Approximation.
+ private static final long EXPOSURE_TIME_ERROR_MARGIN_NS = 100000L; // 100us, Approximation.
+ private static final int SENSITIVITY_ERROR_MARGIN = 10; // 10
+ private static final int DEFAULT_NUM_EXPOSURE_TIME_STEPS = 10;
+ private static final int DEFAULT_NUM_SENSITIVITY_STEPS = 16;
+ private static final int DEFAULT_SENSITIVITY_STEP_SIZE = 100;
+ private static final int NUM_RESULTS_WAIT_TIMEOUT = 100;
@Override
protected void setUp() throws Exception {
@@ -172,9 +181,405 @@
}
}
+ /**
+ * Test {@link CaptureRequest#CONTROL_AE_ANTIBANDING_MODE} control.
+ * <p>
+ * Test all available anti-banding modes, check if the exposure time adjustment is
+ * correct.
+ * </p>
+ */
+ public void testAntiBandingModes() throws Exception {
+ for (int i = 0; i < mCameraIds.length; i++) {
+ try {
+ openDevice(mCameraIds[i]);
+
+ if (!mStaticInfo.isHardwareLevelFull()) {
+ continue;
+ }
+
+ SimpleCaptureListener listener = new SimpleCaptureListener();
+
+ byte[] modes = mStaticInfo.getAeAvailableAntiBandingModesChecked();
+
+ Size previewSz =
+ getMaxPreviewSize(mCamera.getId(), mCameraManager, PREVIEW_SIZE_BOUND);
+
+ for (byte mode : modes) {
+ antiBandingTestByMode(listener, previewSz, mode);
+ }
+ } finally {
+ closeDevice();
+ }
+ }
+
+ }
+
+ /**
+ * Test AE mode and lock.
+ *
+ * <p>
+ * For AE lock, when it is locked, exposure parameters shouldn't be changed.
+ * For AE modes, each mode should satisfy the per frame controls defined in
+ * API specifications.
+ * </p>
+ */
+ public void testAeModeAndLock() throws Exception {
+ for (int i = 0; i < mCameraIds.length; i++) {
+ try {
+ openDevice(mCameraIds[i]);
+
+ // Can only test full capability because test relies on per frame control
+ // and synchronization.
+ if (!mStaticInfo.isHardwareLevelFull()) {
+ continue;
+ }
+
+ Size maxPreviewSz = mOrderedPreviewSizes.get(0); // Max preview size.
+
+ // Update preview surface with given size for all sub-tests.
+ updatePreviewSurface(maxPreviewSz);
+
+ // Test aeMode and lock
+ byte[] aeModes = mStaticInfo.getAeAvailableModesChecked();
+ for (byte mode : aeModes) {
+ aeModeAndLockTestByMode(mode);
+ }
+ } finally {
+ closeDevice();
+ }
+ }
+ }
+
+ /** Test {@link CaptureRequest#FLASH_MODE} control.
+ * <p>
+ * For each {@link CaptureRequest#FLASH_MODE} mode, test the flash control
+ * and {@link CaptureResult#FLASH_STATE} result.
+ * </p>
+ */
+ public void testFlashControl() throws Exception {
+ for (int i = 0; i < mCameraIds.length; i++) {
+ try {
+ openDevice(mCameraIds[i]);
+
+ // Can only test full capability because test relies on per frame control
+ // and synchronization.
+ if (!mStaticInfo.isHardwareLevelFull()) {
+ continue;
+ }
+
+ SimpleCaptureListener listener = new SimpleCaptureListener();
+ CaptureRequest.Builder requestBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+
+ Size maxPreviewSz = mOrderedPreviewSizes.get(0); // Max preview size.
+
+ startPreview(requestBuilder, maxPreviewSz, listener);
+
+ // Flash control can only be used when the AE mode is ON or OFF.
+ flashTestByAeMode(listener, CaptureRequest.CONTROL_AE_MODE_ON);
+ flashTestByAeMode(listener, CaptureRequest.CONTROL_AE_MODE_OFF);
+
+ stopPreview();
+ } finally {
+ closeDevice();
+ }
+ }
+ }
+
// TODO: add 3A state machine test.
/**
+ * Test flash mode control by AE mode.
+ * <p>
+ * Only allow AE mode ON or OFF, because other AE mode could run into conflict with
+ * flash manual control. This function expects the camera to already have an active
+ * repeating request and be sending results to the listener.
+ * </p>
+ *
+ * @param listener The Capture listener that is used to wait for capture result
+ * @param aeMode The AE mode for flash to test with
+ */
+ private void flashTestByAeMode(SimpleCaptureListener listener, int aeMode) throws Exception {
+ CaptureRequest request;
+ CaptureResult result;
+ final int NUM_FLASH_REQUESTS_TESTED = 10;
+ CaptureRequest.Builder requestBuilder = createRequestForPreview();
+
+ if (aeMode == CaptureRequest.CONTROL_AE_MODE_ON) {
+ requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, aeMode);
+ } else if (aeMode == CaptureRequest.CONTROL_AE_MODE_OFF) {
+ changeExposure(requestBuilder, DEFAULT_EXP_TIME_NS, DEFAULT_SENSITIVITY);
+ } else {
+ throw new IllegalArgumentException("This test only works when AE mode is ON or OFF");
+ }
+
+ // For camera that doesn't have flash unit, flash state should always be UNAVAILABLE.
+ if (mStaticInfo.getFlashInfoChecked() == false) {
+ for (int i = 0; i < NUM_FLASH_REQUESTS_TESTED; i++) {
+ result = listener.getCaptureResult(CAPTURE_RESULT_TIMEOUT_MS);
+ mCollector.expectEquals("No flash unit available, flash state must be UNAVAILABLE"
+ + "for AE mode " + aeMode, CaptureResult.FLASH_STATE_UNAVAILABLE,
+ result.get(CaptureResult.FLASH_STATE));
+ }
+
+ return;
+ }
+
+ // Test flash SINGLE mode control. Wait for flash state to be READY first.
+ waitForResultValue(listener, CaptureResult.FLASH_STATE, CaptureResult.FLASH_STATE_READY,
+ NUM_RESULTS_WAIT_TIMEOUT);
+ requestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_SINGLE);
+ request = requestBuilder.build();
+ mCamera.capture(request, listener, mHandler);
+ result = listener.getCaptureResultForRequest(request,
+ NUM_RESULTS_WAIT_TIMEOUT);
+ // Result mode must be SINGLE, state must be FIRED.
+ mCollector.expectEquals("Flash mode result must be SINGLE",
+ CaptureResult.FLASH_MODE_SINGLE, result.get(CaptureResult.FLASH_MODE));
+ mCollector.expectEquals("Flash state result must be FIRED",
+ CaptureResult.FLASH_STATE_FIRED, result.get(CaptureResult.FLASH_STATE));
+
+ // Test flash TORCH mode control.
+ CaptureRequest[] requests = new CaptureRequest[NUM_FLASH_REQUESTS_TESTED];
+ requestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH);
+ for (int i = 0; i < NUM_FLASH_REQUESTS_TESTED; i++) {
+ requests[i] = requestBuilder.build();
+ mCamera.capture(requests[i], listener, mHandler);
+ }
+ // Verify the results
+ for (int i = 0; i < NUM_FLASH_REQUESTS_TESTED; i++) {
+ result = listener.getCaptureResultForRequest(requests[i],
+ NUM_RESULTS_WAIT_TIMEOUT);
+
+ // Result mode must be TORCH, state must be FIRED
+ mCollector.expectEquals("Flash mode result must be TORCH",
+ CaptureResult.FLASH_MODE_TORCH, result.get(CaptureResult.FLASH_MODE));
+ mCollector.expectEquals("Flash state result must be FIRED",
+ CaptureResult.FLASH_STATE_FIRED, result.get(CaptureResult.FLASH_STATE));
+ }
+
+ // Test flash OFF mode control
+ requestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);
+ request = requestBuilder.build();
+ mCamera.capture(request, listener, mHandler);
+ result = listener.getCaptureResultForRequest(request,
+ NUM_RESULTS_WAIT_TIMEOUT);
+ mCollector.expectEquals("Flash mode result must be OFF", CaptureResult.FLASH_MODE_OFF,
+ result.get(CaptureResult.FLASH_MODE));
+ }
+
+ private void verifyAntiBandingMode(SimpleCaptureListener listener, int numFramesVerified,
+ int mode, boolean isAeManual, long requestExpTime) throws Exception {
+ for (int i = 0; i < numFramesVerified; i++) {
+ CaptureResult result = listener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
+ Long resultExpTime = result.get(CaptureRequest.SENSOR_EXPOSURE_TIME);
+ assertNotNull("Exposure time shouldn't be null", resultExpTime);
+ Integer flicker = result.get(CaptureResult.STATISTICS_SCENE_FLICKER);
+ // Scene flicker result should be always available.
+ assertNotNull("Scene flicker must not be null", flicker);
+ assertTrue("Scene flicker is invalid", flicker >= STATISTICS_SCENE_FLICKER_NONE &&
+ flicker <= STATISTICS_SCENE_FLICKER_60HZ);
+
+ if (isAeManual) {
+ // First, round down not up, second, need close enough.
+ validateExposureTime(requestExpTime, resultExpTime);
+ return;
+ }
+
+ long expectedExpTime = resultExpTime; // Default, no exposure adjustment.
+ if (mode == CONTROL_AE_ANTIBANDING_MODE_50HZ) {
+ // result exposure time must be adjusted by 50Hz illuminant source.
+ expectedExpTime =
+ resultExpTime - (resultExpTime % EXPOSURE_TIME_BOUNDARY_50HZ_NS);
+ } else if (mode == CONTROL_AE_ANTIBANDING_MODE_60HZ) {
+ // result exposure time must be adjusted by 60Hz illuminant source.
+ expectedExpTime =
+ resultExpTime - (resultExpTime % EXPOSURE_TIME_BOUNDARY_60HZ_NS);
+ } else if (mode == CONTROL_AE_ANTIBANDING_MODE_AUTO){
+ /**
+ * Use STATISTICS_SCENE_FLICKER to tell the illuminant source
+ * and do the exposure adjustment.
+ */
+ expectedExpTime = resultExpTime;
+ if (flicker == STATISTICS_SCENE_FLICKER_60HZ) {
+ expectedExpTime = resultExpTime
+ - (resultExpTime % EXPOSURE_TIME_BOUNDARY_60HZ_NS);
+ } else if (flicker == STATISTICS_SCENE_FLICKER_50HZ) {
+ expectedExpTime = resultExpTime
+ - (resultExpTime % EXPOSURE_TIME_BOUNDARY_50HZ_NS);
+ }
+ }
+
+ if (Math.abs(resultExpTime - expectedExpTime) > EXPOSURE_TIME_ERROR_MARGIN_NS) {
+ mCollector.addMessage(String.format("Result exposure time %dns diverges too much"
+ + " from expected exposure time %dns for mode %d when AE is auto",
+ resultExpTime, expectedExpTime, mode));
+ }
+ }
+ }
+
+ private void antiBandingTestByMode(SimpleCaptureListener listener, Size size, int mode)
+ throws Exception {
+ if(VERBOSE) {
+ Log.v(TAG, "Anti-banding test for mode " + mode + " for camera " + mCamera.getId());
+ }
+ CaptureRequest.Builder requestBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+
+ requestBuilder.set(CaptureRequest.CONTROL_AE_ANTIBANDING_MODE, mode);
+
+ // Test auto AE mode anti-banding behavior
+ startPreview(requestBuilder, size, listener);
+ verifyAntiBandingMode(listener, NUM_FRAMES_VERIFIED, mode, /*isAeManual*/false,
+ IGORE_REQUESTED_EXPOSURE_TIME_CHECK);
+
+ // Test manual AE mode anti-banding behavior
+ // 65ms, must be supported by full capability devices.
+ final long TEST_MANUAL_EXP_TIME_NS = 65000000L;
+ changeExposure(requestBuilder, TEST_MANUAL_EXP_TIME_NS);
+ startPreview(requestBuilder, size, listener);
+ verifyAntiBandingMode(listener, NUM_FRAMES_VERIFIED, mode, /*isAeManual*/true,
+ TEST_MANUAL_EXP_TIME_NS);
+
+ stopPreview();
+ }
+
+ /**
+ * Test the all available AE modes and AE lock.
+ * <p>
+ * For manual AE mode, test iterates through different sensitivities and
+ * exposure times, validate the result exposure time correctness. For
+ * CONTROL_AE_MODE_ON_ALWAYS_FLASH mode, the AE lock and flash are tested.
+ * For the rest of the AUTO mode, AE lock is tested.
+ * </p>
+ *
+ * @param mode
+ */
+ private void aeModeAndLockTestByMode(int mode)
+ throws Exception {
+ switch (mode) {
+ case CONTROL_AE_MODE_OFF:
+ // Test manual exposure control.
+ aeManualControlTest();
+ break;
+ case CONTROL_AE_MODE_ON:
+ case CONTROL_AE_MODE_ON_AUTO_FLASH:
+ case CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE:
+ case CONTROL_AE_MODE_ON_ALWAYS_FLASH:
+ // Test AE lock for above AUTO modes.
+ aeAutoModeTestLock(mode);
+ break;
+ default:
+ throw new UnsupportedOperationException("Unhandled AE mode " + mode);
+ }
+ }
+
+ /**
+ * Test AE auto modes.
+ * <p>
+ * Use single request rather than repeating request to test AE lock per frame control.
+ * </p>
+ */
+ private void aeAutoModeTestLock(int mode) throws Exception {
+ CaptureRequest.Builder requestBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ requestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, false);
+ requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, mode);
+ configurePreviewOutput(requestBuilder);
+
+ final int MAX_NUM_CAPTURES_BEFORE_LOCK = 5;
+ for (int i = 1; i <= MAX_NUM_CAPTURES_BEFORE_LOCK; i++) {
+ autoAeMultipleCapturesThenTestLock(requestBuilder, mode, i);
+ }
+ }
+
+ /**
+ * Issue multiple auto AE captures, then lock AE, validate the AE lock vs.
+ * the last capture result before the AE lock.
+ */
+ private void autoAeMultipleCapturesThenTestLock(
+ CaptureRequest.Builder requestBuilder, int aeMode, int numCapturesBeforeLock)
+ throws Exception {
+ if (numCapturesBeforeLock < 1) {
+ throw new IllegalArgumentException("numCapturesBeforeLock must be no less than 1");
+ }
+ if (VERBOSE) {
+ Log.v(TAG, "Camera " + mCamera.getId() + ": Testing auto AE mode and lock for mode "
+ + aeMode + " with " + numCapturesBeforeLock + " captures before lock");
+ }
+
+ SimpleCaptureListener listener = new SimpleCaptureListener();
+ CaptureResult latestResult = null;
+
+ CaptureRequest request = requestBuilder.build();
+ for (int i = 0; i < numCapturesBeforeLock; i++) {
+ // Fire a capture, auto AE, lock off.
+ mCamera.capture(request, listener, mHandler);
+ }
+ // Then fire a capture to lock the AE,
+ requestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, true);
+ mCamera.capture(requestBuilder.build(), listener, mHandler);
+
+ // Get the latest exposure values of the last AE lock off requests.
+ for (int i = 0; i < numCapturesBeforeLock; i++) {
+ latestResult = listener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
+ }
+ int sensitivity = getValueNotNull(latestResult, CaptureResult.SENSOR_SENSITIVITY);
+ long expTime = getValueNotNull(latestResult, CaptureResult.SENSOR_EXPOSURE_TIME);
+
+ // Get the AE lock on result and validate the exposure values.
+ latestResult = listener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
+ int sensitivityLocked = getValueNotNull(latestResult, CaptureResult.SENSOR_SENSITIVITY);
+ long expTimeLocked = getValueNotNull(latestResult, CaptureResult.SENSOR_EXPOSURE_TIME);
+ mCollector.expectEquals("Locked exposure time shouldn't be changed for AE auto mode "
+ + aeMode + "after " + numCapturesBeforeLock + " captures", expTime, expTimeLocked);
+ mCollector.expectEquals("Locked sensitivity shouldn't be changed for AE auto mode " + aeMode
+ + "after " + numCapturesBeforeLock + " captures", sensitivity, sensitivityLocked);
+ }
+
+ /**
+ * Iterate through exposure times and sensitivities for manual AE control.
+ * <p>
+ * Use single request rather than repeating request to test manual exposure
+ * value change per frame control.
+ * </p>
+ */
+ private void aeManualControlTest()
+ throws Exception {
+ CaptureRequest.Builder requestBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+
+ requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CONTROL_AE_MODE_OFF);
+ configurePreviewOutput(requestBuilder);
+ SimpleCaptureListener listener = new SimpleCaptureListener();
+
+ long[] expTimes = getExposureTimeTestValues();
+ int[] sensitivities = getSensitivityTestValues();
+ // Submit single request at a time, then verify the result.
+ for (int i = 0; i < expTimes.length; i++) {
+ for (int j = 0; j < sensitivities.length; j++) {
+ if (VERBOSE) {
+ Log.v(TAG, "Camera " + mCamera.getId() + ": Testing sensitivity "
+ + sensitivities[j] + ", exposure time " + expTimes[i] + "ns");
+ }
+
+ changeExposure(requestBuilder, expTimes[i], sensitivities[j]);
+ mCamera.capture(requestBuilder.build(), listener, mHandler);
+
+ CaptureResult result = listener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
+ long resultExpTime = getValueNotNull(result, CaptureResult.SENSOR_EXPOSURE_TIME);
+ int resultSensitivity = getValueNotNull(result, CaptureResult.SENSOR_SENSITIVITY);
+ validateExposureTime(expTimes[i], resultExpTime);
+ validateSensitivity(sensitivities[j], resultSensitivity);
+ }
+ }
+ // TODO: Add another case to test where we can submit all requests, then wait for
+ // results, which will hide the pipeline latency. this is not only faster, but also
+ // test high speed per frame control and synchronization.
+
+ }
+
+ /**
* Verify black level lock control.
*/
private void verifyBlackLevelLockResults(SimpleCaptureListener listener, int numFramesVerified,
@@ -256,4 +661,110 @@
requestBuilder.set(CaptureRequest.SENSOR_EXPOSURE_TIME, expTime);
requestBuilder.set(CaptureRequest.SENSOR_SENSITIVITY, sensitivity);
}
+ /**
+ * Enable exposure manual control and change exposure time and
+ * clamp the value into the supported range.
+ *
+ * <p>The sensitivity is set to default value.</p>
+ */
+ private void changeExposure(CaptureRequest.Builder requestBuilder, long expTime) {
+ changeExposure(requestBuilder, expTime, DEFAULT_SENSITIVITY);
+ }
+
+ /**
+ * Enable exposure manual control and change sensitivity and
+ * clamp the value into the supported range.
+ *
+ * <p>The exposure time is set to default value.</p>
+ */
+ private void changeExposure(CaptureRequest.Builder requestBuilder, int sensitivity) {
+ changeExposure(requestBuilder, DEFAULT_EXP_TIME_NS, sensitivity);
+ }
+
+ /**
+ * Get the exposure time array that contains multiple exposure time steps in
+ * the exposure time range.
+ */
+ private long[] getExposureTimeTestValues() {
+ long[] testValues = new long[DEFAULT_NUM_EXPOSURE_TIME_STEPS + 1];
+ long maxExpTime = mStaticInfo.getExposureMaximumOrDefault(DEFAULT_EXP_TIME_NS);
+ long minxExpTime = mStaticInfo.getExposureMinimumOrDefault(DEFAULT_EXP_TIME_NS);
+
+ long range = maxExpTime - minxExpTime;
+ double stepSize = range / (double)DEFAULT_NUM_EXPOSURE_TIME_STEPS;
+ for (int i = 0; i < testValues.length; i++) {
+ testValues[i] = minxExpTime + (long)(stepSize * i);
+ testValues[i] = mStaticInfo.getExposureClampToRange(testValues[i]);
+ }
+
+ return testValues;
+ }
+
+ /**
+ * Get the sensitivity array that contains multiple sensitivity steps in the
+ * sensitivity range.
+ * <p>
+ * Sensitivity number of test values is determined by
+ * {@value #DEFAULT_SENSITIVITY_STEP_SIZE} and sensitivity range, and
+ * bounded by {@value #DEFAULT_NUM_SENSITIVITY_STEPS}.
+ * </p>
+ */
+ private int[] getSensitivityTestValues() {
+ int maxSensitivity = mStaticInfo.getSensitivityMaximumOrDefault(
+ DEFAULT_SENSITIVITY);
+ int minSensitivity = mStaticInfo.getSensitivityMinimumOrDefault(
+ DEFAULT_SENSITIVITY);
+
+ int range = maxSensitivity - minSensitivity;
+ int stepSize = DEFAULT_SENSITIVITY_STEP_SIZE;
+ int numSteps = range / stepSize;
+ // Bound the test steps to avoid supper long test.
+ if (numSteps > DEFAULT_NUM_SENSITIVITY_STEPS) {
+ numSteps = DEFAULT_NUM_SENSITIVITY_STEPS;
+ stepSize = range / numSteps;
+ }
+ int[] testValues = new int[numSteps + 1];
+ for (int i = 0; i < testValues.length; i++) {
+ testValues[i] = minSensitivity + stepSize * i;
+ testValues[i] = mStaticInfo.getSensitivityClampToRange(testValues[i]);
+ }
+
+ return testValues;
+ }
+
+ /**
+ * Validate the AE manual control exposure time.
+ *
+ * <p>Exposure should be close enough, and only round down if they are not equal.</p>
+ *
+ * @param request Request exposure time
+ * @param result Result exposure time
+ */
+ private void validateExposureTime(long request, long result) {
+ long expTimeDelta = request - result;
+ // First, round down not up, second, need close enough.
+ mCollector.expectTrue("Exposture time is invalid for AE manaul control test, request: "
+ + request + " result: " + result,
+ expTimeDelta < EXPOSURE_TIME_ERROR_MARGIN_NS && expTimeDelta >= 0);
+ }
+
+ /**
+ * Validate AE manual control sensitivity.
+ *
+ * @param request Request sensitivity
+ * @param result Result sensitivity
+ */
+ private void validateSensitivity(int request, int result) {
+ int sensitivityDelta = request - result;
+ // First, round down not up, second, need close enough.
+ mCollector.expectTrue("Sensitivity is invalid for AE manaul control test, request: "
+ + request + " result: " + result,
+ sensitivityDelta < SENSITIVITY_ERROR_MARGIN && sensitivityDelta >= 0);
+ }
+
+ private <T> T getValueNotNull(CaptureResult result, Key<T> key) {
+ T value = result.get(key);
+ assertNotNull("Value of Key " + key.getName() + "shouldn't be null", value);
+ return value;
+ }
}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureResultTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureResultTest.java
index 660fb1e..615f588 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureResultTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureResultTest.java
@@ -20,36 +20,23 @@
import android.graphics.ImageFormat;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
-import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.Size;
+import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
+
import static android.hardware.camera2.cts.CameraTestUtils.*;
-import android.media.ImageReader;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.test.AndroidTestCase;
+
import android.util.Log;
import android.view.Surface;
-import com.android.ex.camera2.blocking.BlockingStateListener;
-import static com.android.ex.camera2.blocking.BlockingStateListener.*;
-
import java.util.ArrayList;
import java.util.List;
-public class CaptureResultTest extends AndroidTestCase {
+public class CaptureResultTest extends Camera2AndroidTestCase {
private static final String TAG = "CaptureResultTest";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
-
- private CameraManager mCameraManager;
- private HandlerThread mHandlerThread;
- private Handler mHandler;
- private ImageReader mImageReader;
- private Surface mSurface;
- private BlockingStateListener mCameraListener;
-
private static final int MAX_NUM_IMAGES = 5;
private static final int NUM_FRAMES_VERIFIED = 300;
private static final long WAIT_FOR_RESULT_TIMEOUT_MS = 3000;
@@ -69,18 +56,11 @@
@Override
protected void setUp() throws Exception {
super.setUp();
- mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
- assertNotNull("Can't connect to camera manager", mCameraManager);
- mHandlerThread = new HandlerThread(TAG);
- mHandlerThread.start();
- mHandler = new Handler(mHandlerThread.getLooper());
- mCameraListener = new BlockingStateListener();
mFailedKeys.clear();
}
@Override
protected void tearDown() throws Exception {
- mHandlerThread.quitSafely();
super.tearDown();
}
@@ -120,33 +100,27 @@
}
// TODO: check for LIMITED keys
- CameraDevice camera = null;
try {
- Size[] sizes = CameraTestUtils.getSupportedSizeForFormat(
- ImageFormat.YUV_420_888, ids[i], mCameraManager);
- CameraTestUtils.assertArrayNotEmpty(sizes, "Available sizes shouldn't be empty");
- createDefaultSurface(sizes[0]);
-
+ // Create image reader and surface.
+ Size sz = getMaxPreviewSize(ids[i], mCameraManager);
+ createImageReader(sz, ImageFormat.YUV_420_888, MAX_NUM_IMAGES,
+ new ImageDropperListener());
if (VERBOSE) {
- Log.v(TAG, "Testing camera " + ids[i] + "for size " + sizes[0].toString());
+ Log.v(TAG, "Testing camera " + ids[i] + "for size " + sz.toString());
}
- camera = CameraTestUtils.openCamera(
- mCameraManager, ids[i], mCameraListener, mHandler);
- assertNotNull(
- String.format("Failed to open camera device %s", ids[i]), camera);
- mCameraListener.waitForState(STATE_UNCONFIGURED, CAMERA_OPEN_TIMEOUT_MS);
+ // Open camera.
+ openDevice(ids[i]);
+ // Configure output streams.
List<Surface> outputSurfaces = new ArrayList<Surface>(1);
- outputSurfaces.add(mSurface);
- camera.configureOutputs(outputSurfaces);
- mCameraListener.waitForState(STATE_BUSY, CAMERA_BUSY_TIMEOUT_MS);
- mCameraListener.waitForState(STATE_IDLE, CAMERA_IDLE_TIMEOUT_MS);
+ outputSurfaces.add(mReaderSurface);
+ configureCameraOutputs(mCamera, outputSurfaces, mCameraListener);;
CaptureRequest.Builder requestBuilder =
- camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
assertNotNull("Failed to create capture request", requestBuilder);
- requestBuilder.addTarget(mSurface);
+ requestBuilder.addTarget(mReaderSurface);
// Enable face detection if supported
byte[] faceModes = props.get(
@@ -167,59 +141,41 @@
requestBuilder.set(CaptureRequest.STATISTICS_LENS_SHADING_MAP_MODE,
CameraMetadata.STATISTICS_LENS_SHADING_MAP_MODE_ON);
+ // Start capture
SimpleCaptureListener captureListener = new SimpleCaptureListener();
- camera.setRepeatingRequest(requestBuilder.build(), captureListener, mHandler);
+ startCapture(requestBuilder.build(), /*repeating*/true, captureListener, mHandler);
- for (int m = 0; m < NUM_FRAMES_VERIFIED; m++) {
- if(VERBOSE) {
- Log.v(TAG, "Testing frame " + m);
- }
- validateCaptureResult(
- captureListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS),
- waiverkeys);
- }
+ // Verify results
+ validateCaptureResult(captureListener, waiverkeys, NUM_FRAMES_VERIFIED);
- // Stop repeat, wait for captures to complete, and disconnect from surfaces
- camera.configureOutputs(/*outputs*/ null);
- mCameraListener.waitForState(STATE_BUSY, CAMERA_BUSY_TIMEOUT_MS);
- mCameraListener.waitForState(STATE_UNCONFIGURED, CAMERA_IDLE_TIMEOUT_MS);
- // Camera has disconnected, clear out the reader
- mSurface.release();
- mImageReader.close();
+ stopCapture(/*fast*/false);
} finally {
- if (camera != null) {
- camera.close();
+ closeDevice(ids[i]);
+ closeImageReader();
+ }
+
+ }
+ }
+
+ private void validateCaptureResult(SimpleCaptureListener captureListener,
+ List<CameraMetadata.Key<?>> skippedKeys, int numFramesVerified) throws Exception {
+ CaptureResult result = null;
+ for (int i = 0; i < numFramesVerified; i++) {
+ result = captureListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
+ for (CameraMetadata.Key<?> key : mAllKeys) {
+ if (!skippedKeys.contains(key) && result.get(key) == null) {
+ mFailedKeys.add(key);
}
}
- }
- }
-
- private void validateCaptureResult(CaptureResult result,
- List<CameraMetadata.Key<?>> skippedKeys) throws Exception {
- for (CameraMetadata.Key<?> key : mAllKeys) {
- if (!skippedKeys.contains(key) && result.get(key) == null) {
- mFailedKeys.add(key);
+ StringBuffer failedKeyNames = new StringBuffer("Below Keys have null values:\n");
+ for (CameraMetadata.Key<?> key : mFailedKeys) {
+ failedKeyNames.append(key.getName() + "\n");
}
+
+ assertTrue("Some keys have null values, " + failedKeyNames.toString(),
+ mFailedKeys.isEmpty());
}
-
- StringBuffer failedKeyNames = new StringBuffer("Below Keys have null values:\n");
- for (CameraMetadata.Key<?> key : mFailedKeys) {
- failedKeyNames.append(key.getName() + "\n");
- }
-
- assertTrue("Some keys have null values, " + failedKeyNames.toString(),
- mFailedKeys.isEmpty());
- }
-
- private void createDefaultSurface(Size sz) {
- mImageReader =
- ImageReader.newInstance(sz.getWidth(),
- sz.getHeight(),
- ImageFormat.YUV_420_888,
- MAX_NUM_IMAGES);
- mImageReader.setOnImageAvailableListener(new ImageDropperListener(), mHandler);
- mSurface = mImageReader.getSurface();
}
/**
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/ImageReaderTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/ImageReaderTest.java
index 211b5d3..b8f0b3c 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/ImageReaderTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/ImageReaderTest.java
@@ -17,29 +17,21 @@
package android.hardware.camera2.cts;
import static android.hardware.camera2.cts.CameraTestUtils.*;
-import static com.android.ex.camera2.blocking.BlockingStateListener.*;
import android.content.Context;
import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
-import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraDevice;
-import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.Size;
+import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
import android.media.Image;
import android.media.ImageReader;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.test.AndroidTestCase;
+import android.os.ConditionVariable;
import android.util.Log;
import android.view.Surface;
-import com.android.ex.camera2.blocking.BlockingCameraManager.BlockingOpenException;
-import com.android.ex.camera2.blocking.BlockingStateListener;
-
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -54,74 +46,54 @@
* <p>Some invalid access test. </p>
* <p>TODO: Add more format tests? </p>
*/
-public class ImageReaderTest extends AndroidTestCase {
+public class ImageReaderTest extends Camera2AndroidTestCase {
private static final String TAG = "ImageReaderTest";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
private static final boolean DUMP_FILE = false;
- private static final String DEBUG_FILE_NAME_BASE =
- Environment.getExternalStorageDirectory().getPath();
// number of frame (for streaming requests) to be verified.
// TODO: Need extend it to bigger number
private static final int NUM_FRAME_VERIFIED = 1;
// Max number of images can be accessed simultaneously from ImageReader.
private static final int MAX_NUM_IMAGES = 5;
- private CameraManager mCameraManager;
- private CameraDevice mCamera;
- private BlockingStateListener mCameraListener;
- private String[] mCameraIds;
- private ImageReader mReader;
- private Handler mHandler;
private SimpleImageListener mListener;
- private HandlerThread mHandlerThread;
@Override
public void setContext(Context context) {
super.setContext(context);
- mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
- assertNotNull("Can't connect to camera manager!", mCameraManager);
}
@Override
protected void setUp() throws Exception {
super.setUp();
- mCameraIds = mCameraManager.getCameraIdList();
- mHandlerThread = new HandlerThread(TAG);
- mHandlerThread.start();
- mHandler = new Handler(mHandlerThread.getLooper());
- mCameraListener = new BlockingStateListener();
}
@Override
protected void tearDown() throws Exception {
- if (mCamera != null) {
- mCamera.close();
- mCamera = null;
- }
- if (mReader != null) {
- mReader.close();
- mReader = null;
- }
- mHandlerThread.quitSafely();
- mHandler = null;
super.tearDown();
}
public void testImageReaderFromCameraFlexibleYuv() throws Exception {
for (int i = 0; i < mCameraIds.length; i++) {
- Log.i(TAG, "Testing Camera " + mCameraIds[i]);
- openDevice(mCameraIds[i]);
- bufferFormatTestByCamera(ImageFormat.YUV_420_888, mCameraIds[i]);
- closeDevice();
+ try {
+ Log.i(TAG, "Testing Camera " + mCameraIds[i]);
+ openDevice(mCameraIds[i]);
+ bufferFormatTestByCamera(ImageFormat.YUV_420_888, mCameraIds[i]);
+ } finally {
+ closeDevice(mCameraIds[i]);
+ }
}
}
public void testImageReaderFromCameraJpeg() throws Exception {
for (int i = 0; i < mCameraIds.length; i++) {
- Log.v(TAG, "Testing Camera " + mCameraIds[i]);
- openDevice(mCameraIds[i]);
- bufferFormatTestByCamera(ImageFormat.JPEG, mCameraIds[i]);
- closeDevice();
+ try {
+ Log.v(TAG, "Testing Camera " + mCameraIds[i]);
+ openDevice(mCameraIds[i]);
+ bufferFormatTestByCamera(ImageFormat.JPEG, mCameraIds[i]);
+ } finally {
+ closeDevice(mCameraIds[i]);
+ }
}
}
@@ -150,64 +122,49 @@
// for each resolution, test imageReader:
for (Size sz : availableSizes) {
- if (VERBOSE) Log.v(TAG, "Testing size " + sz.toString() + " for camera " + cameraId);
+ try {
+ if (VERBOSE) Log.v(TAG, "Testing size " + sz.toString() + " for camera " + cameraId);
- prepareImageReader(sz, format);
+ // Create ImageReader.
+ mListener = new SimpleImageListener();
+ createImageReader(sz, format, MAX_NUM_IMAGES, mListener);
- CaptureRequest request = prepareCaptureRequest();
+ // Start capture.
+ CaptureRequest request = prepareCaptureRequest();
+ boolean repeating = format != ImageFormat.JPEG;
+ startCapture(request, repeating, null, null);
- captureAndValidateImage(request, sz, format);
+ // Validate images.
+ validateImage(sz, format);
- stopCapture();
+ // stop capture.
+ stopCapture(/*fast*/false);
+ } finally {
+ closeImageReader();
+ }
+
}
}
- private class SimpleImageListener implements ImageReader.OnImageAvailableListener {
- private int mPendingImages = 0;
- private final Object mImageSyncObject = new Object();
-
+ private final class SimpleImageListener implements ImageReader.OnImageAvailableListener {
+ private final ConditionVariable imageAvailable = new ConditionVariable();
@Override
public void onImageAvailable(ImageReader reader) {
+ if (mReader != reader) {
+ return;
+ }
+
if (VERBOSE) Log.v(TAG, "new image available");
- synchronized (mImageSyncObject) {
- mPendingImages++;
- mImageSyncObject.notifyAll();
- }
+ imageAvailable.open();
}
- public boolean isImagePending() {
- synchronized (mImageSyncObject) {
- return (mPendingImages > 0);
+ public void waitForAnyImageAvailable(long timeout) {
+ if (imageAvailable.block(timeout)) {
+ imageAvailable.close();
+ } else {
+ fail("wait for image available timed out after " + timeout + "ms");
}
}
-
- public void waitForImage() {
- final int TIMEOUT_MS = 5000;
- synchronized (mImageSyncObject) {
- while (mPendingImages == 0) {
- try {
- if (VERBOSE)
- Log.d(TAG, "waiting for next image");
- mImageSyncObject.wait(TIMEOUT_MS);
- if (mPendingImages == 0) {
- fail("wait for next image timed out");
- }
- } catch (InterruptedException ie) {
- throw new RuntimeException(ie);
- }
- }
- mPendingImages--;
- }
- }
- }
-
- private void prepareImageReader(Size sz, int format) throws Exception {
- int width = sz.getWidth();
- int height = sz.getHeight();
- mReader = ImageReader.newInstance(width, height, format, MAX_NUM_IMAGES);
- mListener = new SimpleImageListener();
- mReader.setOnImageAvailableListener(mListener, mHandler);
- if (VERBOSE) Log.v(TAG, "Preparing ImageReader size " + sz.toString());
}
private CaptureRequest prepareCaptureRequest() throws Exception {
@@ -215,9 +172,7 @@
Surface surface = mReader.getSurface();
assertNotNull("Fail to get surface from ImageReader", surface);
outputSurfaces.add(surface);
- mCamera.configureOutputs(outputSurfaces);
- mCameraListener.waitForState(STATE_BUSY, CAMERA_BUSY_TIMEOUT_MS);
- mCameraListener.waitForState(STATE_IDLE, CAMERA_IDLE_TIMEOUT_MS);
+ configureCameraOutputs(mCamera, outputSurfaces, mCameraListener);
CaptureRequest.Builder captureBuilder =
mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
@@ -227,74 +182,32 @@
return captureBuilder.build();
}
- private void captureAndValidateImage(CaptureRequest request,
- Size sz, int format) throws Exception {
+ private void validateImage(Size sz, int format) throws Exception {
// TODO: Add more format here, and wrap each one as a function.
Image img;
- int captureCount = NUM_FRAME_VERIFIED;
+ int captureCount = NUM_FRAME_VERIFIED;
// Only verify single image for still capture
if (format == ImageFormat.JPEG) {
captureCount = 1;
- mCamera.capture(request, null, null);
- } else {
- mCamera.setRepeatingRequest(request, null, null);
}
for (int i = 0; i < captureCount; i++) {
assertNotNull("Image listener is null", mListener);
if (VERBOSE) Log.v(TAG, "Waiting for an Image");
- mListener.waitForImage();
- img = mReader.acquireNextImage();
- if (VERBOSE) Log.v(TAG, "Got next image");
+ mListener.waitForAnyImageAvailable(CAPTURE_WAIT_TIMEOUT_MS);
+ /**
+ * Acquire the latest image in case the validation is slower than
+ * the image producing rate.
+ */
+ img = mReader.acquireLatestImage();
+ assertNotNull("Unable to acquire the latest image", img);
+ if (VERBOSE) Log.v(TAG, "Got the latest image");
validateImage(img, sz.getWidth(), sz.getHeight(), format);
img.close();
- // Return the pending images to producer in case the validation is slower
- // than the image producing rate. Otherwise, it could cause the producer
- // starvation.
- while (mListener.isImagePending()) {
- mListener.waitForImage();
- img = mReader.acquireNextImage();
- img.close();
- }
}
}
- private void stopCapture() throws CameraAccessException {
- if (VERBOSE) Log.v(TAG, "Stopping capture and waiting for idle");
- // Stop repeat, wait for captures to complete, and disconnect from surfaces
- mCamera.configureOutputs(/*outputs*/ null);
- mCameraListener.waitForState(STATE_BUSY, CAMERA_BUSY_TIMEOUT_MS);
- mCameraListener.waitForState(STATE_UNCONFIGURED, CAMERA_IDLE_TIMEOUT_MS);
- // Camera has disconnected, clear out the reader
- mReader.close();
- mReader = null;
- mListener = null;
- }
-
- private void openDevice(String cameraId) {
- if (mCamera != null) {
- throw new IllegalStateException("Already have open camera device");
- }
-
- try {
- mCamera = CameraTestUtils.openCamera(
- mCameraManager, cameraId, mCameraListener, mHandler);
- } catch (CameraAccessException e) {
- mCamera = null;
- fail("Fail to open camera, " + Log.getStackTraceString(e));
- } catch (BlockingOpenException e) {
- mCamera = null;
- fail("Fail to open camera, " + Log.getStackTraceString(e));
- }
- }
-
- private void closeDevice() {
- mCamera.close();
- mCameraListener.waitForState(STATE_CLOSED, CAMERA_CLOSE_TIMEOUT_MS);
- mCamera = null;
- }
-
private void validateImage(Image image, int width, int height, int format) {
checkImage(image, width, height, format);
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/RecordingTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/RecordingTest.java
new file mode 100644
index 0000000..e6cb543
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/RecordingTest.java
@@ -0,0 +1,627 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project Licensed under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
+ * or agreed to in writing, software distributed under the License is
+ * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+package android.hardware.camera2.cts;
+
+import static android.hardware.camera2.cts.CameraTestUtils.*;
+import static com.android.ex.camera2.blocking.BlockingStateListener.*;
+
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.Size;
+import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase;
+import android.media.CamcorderProfile;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaCodecInfo.CodecProfileLevel;
+import android.media.MediaCodecList;
+import android.media.MediaPlayer;
+import android.media.MediaRecorder;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+import android.view.SurfaceHolder;
+import android.view.Surface;
+
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * CameraDevice video recording use case tests by using MediaRecorder and
+ * MediaCodec.
+ */
+@LargeTest
+public class RecordingTest extends Camera2SurfaceViewTestCase {
+ private static final String TAG = "RecordingTest";
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+ private static final boolean DEBUG_DUMP = false;
+ private static final Size VIDEO_SIZE_BOUND = new Size(1920, 1080);
+ private static final int RECORDING_DURATION_MS = 2000;
+ private static final int DURATION_MARGIN_MS = 400;
+ private static final int WAIT_FOR_SURFACE_CHANGE_TIMEOUT_MS = 1000;
+ private static final int BIT_RATE_1080P = 16000000;
+ private static final int BIT_RATE_MIN = 64000;
+ private static final int BIT_RATE_MAX = 40000000;
+ private static final int VIDEO_FRAME_RATE = 30;
+ private final String VIDEO_FILE_PATH = Environment.getExternalStorageDirectory().getPath();
+ private static final int[] mCamcorderProfileList = {
+ CamcorderProfile.QUALITY_1080P,
+ CamcorderProfile.QUALITY_480P,
+ CamcorderProfile.QUALITY_720P,
+ CamcorderProfile.QUALITY_CIF,
+ CamcorderProfile.QUALITY_LOW,
+ CamcorderProfile.QUALITY_HIGH,
+ CamcorderProfile.QUALITY_QCIF,
+ CamcorderProfile.QUALITY_QVGA,
+ };
+
+ private List<Size> mSupportedVideoSizes;
+ private Surface mRecordingSurface;
+ private Surface mPreviewSurface;
+ private MediaRecorder mMediaRecorder;
+ private Size mPreviewSz = new Size(0, 0);
+ private String mOutMediaFileName;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ /**
+ * <p>
+ * Test basic camera recording.
+ * </p>
+ * <p>
+ * This test covers the typical basic use case of camera recording.
+ * MediaRecorder is used to record the audio and video, CamcorderProfile is
+ * used to configure the MediaRecorder. It goes through the pre-defined
+ * CamcorderProfile list, test each profile configuration and validate the
+ * recorded video. Preview is set to the video size.
+ * </p>
+ */
+ public void testBasicRecording() throws Exception {
+ for (int i = 0; i < mCameraIds.length; i++) {
+ try {
+ Log.i(TAG, "Testing basic recording for camera " + mCameraIds[i]);
+ // Re-use the MediaRecorder object for the same camera device.
+ mMediaRecorder = new MediaRecorder();
+ openDevice(mCameraIds[i]);
+ mSupportedVideoSizes = getSupportedVideoSizes(mCamera.getId(), mCameraManager,
+ VIDEO_SIZE_BOUND);
+
+ basicRecordingTestByCamera();
+ } finally {
+ closeDevice();
+ releasRecorder();
+ }
+ }
+ }
+
+ /**
+ * <p>
+ * Test camera recording for all supported sizes by using MediaRecorder.
+ * </p>
+ * <p>
+ * This test covers camera recording for all supported sizes by camera. MediaRecorder
+ * is used to encode the video. Preview is set to the video size. Recorded videos are
+ * validated according to the recording configuration.
+ * </p>
+ */
+ public void testSupportedVideoSizes() throws Exception {
+ for (int i = 0; i < mCameraIds.length; i++) {
+ try {
+ Log.i(TAG, "Testing supported video size recording for camera " + mCameraIds[i]);
+ // Re-use the MediaRecorder object for the same camera device.
+ mMediaRecorder = new MediaRecorder();
+ openDevice(mCameraIds[i]);
+
+ mSupportedVideoSizes = getSupportedVideoSizes(mCamera.getId(), mCameraManager,
+ VIDEO_SIZE_BOUND);
+
+ recordingSizeTestByCamera();
+ } finally {
+ closeDevice();
+ releasRecorder();
+ }
+ }
+ }
+
+ /**
+ * Test different start/stop orders of Camera and Recorder.
+ *
+ * <p>The recording should be working fine for any kind of start/stop orders.</p>
+ */
+ public void testCameraRecorderOrdering() {
+ // TODO: need implement
+ }
+
+ /**
+ * <p>
+ * Test camera recording for all supported sizes by using MediaCodec.
+ * </p>
+ * <p>
+ * This test covers video only recording for all supported sizes (camera and
+ * encoder). MediaCodec is used to encode the video. The recorded videos are
+ * validated according to the recording configuration.
+ * </p>
+ */
+ public void testMediaCodecRecording() throws Exception {
+ // TODO. Need implement.
+ }
+
+ /**
+ * <p>
+ * Test video snapshot for each camera by using MediaRecorder.
+ * </p>
+ * <p>
+ * This test covers video snapshot typical use case. The MediaRecorder is
+ * used to record the video for each supported CamcorderProfile
+ * configuration. The largest still capture size is selected to capture the
+ * JPEG image. The still capture images are validated according to the
+ * capture configuration. The preview/recording jitters are evaluated such
+ * that still capture doesn't disrupt the recording session.
+ * </p>
+ */
+ public void testVideoSnapShot() throws Exception {
+ // TODO. Need implement.
+ }
+
+ public void testTimelapseRecording() {
+ // TODO. Need implement.
+ }
+
+ /**
+ * Test camera recording by using each available CamcorderProfile for a
+ * given camera. preview size is set to the video size.
+ */
+ private void basicRecordingTestByCamera() throws Exception {
+ for (int profileId : mCamcorderProfileList) {
+ int cameraId = Integer.valueOf(mCamera.getId());
+ if (!CamcorderProfile.hasProfile(cameraId, profileId)) {
+ continue;
+ }
+
+ CamcorderProfile profile = CamcorderProfile.get(cameraId, profileId);
+ Size videoSz = new Size(profile.videoFrameWidth, profile.videoFrameHeight);
+ assertTrue("Video size " + videoSz.toString()
+ + " must be one of the camera device supported video size!",
+ mSupportedVideoSizes.contains(videoSz));
+
+ if (VERBOSE) {
+ Log.v(TAG, "Testing camera recording with video size " + videoSz.toString());
+ }
+
+ // Configure preview and recording surfaces.
+ mOutMediaFileName = VIDEO_FILE_PATH + "/test_video.mp4";
+ if (DEBUG_DUMP) {
+ mOutMediaFileName = VIDEO_FILE_PATH + "/test_video_" + cameraId + "_"
+ + videoSz.toString() + ".mp4";
+ }
+
+ prepareRecordingWithProfile(profile);
+
+ // prepare preview surface: preview size is same as video size.
+ preparePreview(videoSz);
+
+ // Start recording
+ startRecording(/* useMediaRecorder */true);
+
+ // Record certain duration.
+ SystemClock.sleep(RECORDING_DURATION_MS);
+
+ // Stop recording and preview
+ stopRecording(/* useMediaRecorder */true);
+
+ // Validation.
+ validateRecording(videoSz, RECORDING_DURATION_MS);
+ }
+ }
+
+ /**
+ * Test camera recording for each supported video size by camera, preview
+ * size is set to the video size.
+ */
+ private void recordingSizeTestByCamera() throws Exception {
+ for (Size sz : mSupportedVideoSizes) {
+ if (!isSupported(sz, VIDEO_FRAME_RATE, VIDEO_FRAME_RATE)) {
+ continue;
+ }
+
+ if (VERBOSE) {
+ Log.v(TAG, "Testing camera recording with video size " + sz.toString());
+ }
+
+ // Configure preview and recording surfaces.
+ mOutMediaFileName = VIDEO_FILE_PATH + "/test_video.mp4";
+ if (DEBUG_DUMP) {
+ mOutMediaFileName = VIDEO_FILE_PATH + "/test_video_" + mCamera.getId() + "_"
+ + sz.toString() + ".mp4";
+ }
+
+ // Use AVC and AAC a/v compression format.
+ prepareRecording(sz, VIDEO_FRAME_RATE);
+
+ // prepare preview surface: preview size is same as video size.
+ preparePreview(sz);
+
+ // Start recording
+ startRecording(/* useMediaRecorder */true);
+
+ // Record certain duration.
+ SystemClock.sleep(RECORDING_DURATION_MS);
+
+ // Stop recording and preview
+ stopRecording(/* useMediaRecorder */true);
+
+ // Validation.
+ validateRecording(sz, RECORDING_DURATION_MS);
+ }
+ }
+
+ /**
+ * Configure MediaRecorder recording session with CamcorderProfile, prepare
+ * the recording surface.
+ */
+ private void prepareRecordingWithProfile(CamcorderProfile profile)
+ throws Exception {
+ // Prepare MediaRecorder.
+ mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
+ mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
+ mMediaRecorder.setProfile(profile);
+ mMediaRecorder.setOutputFile(mOutMediaFileName);
+ mMediaRecorder.prepare();
+ mRecordingSurface = mMediaRecorder.getSurface();
+ assertNotNull("Recording surface must be non-null!", mRecordingSurface);
+ }
+
+ /**
+ * Configure MediaRecorder recording session with CamcorderProfile, prepare
+ * the recording surface. Use AVC for video compression, AAC for audio compression.
+ * Both are required for android devices by android CDD.
+ */
+ private void prepareRecording(Size sz, int frameRate) throws Exception {
+ // Prepare MediaRecorder.
+ mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
+ mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
+ mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
+ mMediaRecorder.setOutputFile(mOutMediaFileName);
+ mMediaRecorder.setVideoEncodingBitRate(getVideoBitRate(sz));
+ mMediaRecorder.setVideoFrameRate(frameRate);
+ mMediaRecorder.setVideoSize(sz.getWidth(), sz.getHeight());
+ mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
+ mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
+ mMediaRecorder.prepare();
+ mRecordingSurface = mMediaRecorder.getSurface();
+ assertNotNull("Recording surface must be non-null!", mRecordingSurface);
+ }
+
+ private void startRecording(boolean useMediaRecorder) throws Exception {
+ List<Surface> outputSurfaces = new ArrayList<Surface>(2);
+ assertTrue("Both preview and recording surfaces should be valid",
+ mPreviewSurface.isValid() && mRecordingSurface.isValid());
+ outputSurfaces.add(mPreviewSurface);
+ outputSurfaces.add(mRecordingSurface);
+ mCamera.configureOutputs(outputSurfaces);
+ mCameraListener.waitForState(STATE_BUSY, CAMERA_BUSY_TIMEOUT_MS);
+ mCameraListener.waitForState(STATE_IDLE, CAMERA_IDLE_TIMEOUT_MS);
+
+ CaptureRequest.Builder recordingRequestBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
+ // Make sure camera output frame rate is set to correct value.
+ int[] fpsRange = {VIDEO_FRAME_RATE, VIDEO_FRAME_RATE};
+ recordingRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange);
+ recordingRequestBuilder.addTarget(mRecordingSurface);
+ recordingRequestBuilder.addTarget(mPreviewSurface);
+ mCamera.setRepeatingRequest(recordingRequestBuilder.build(), null, null);
+
+ if (useMediaRecorder) {
+ mMediaRecorder.start();
+ } else {
+ // TODO: need implement MediaCodec path.
+ }
+ }
+
+ /**
+ * Set the preview surface with given size.
+ *
+ * <p>This method shouldn't be called from UI/mail thread.</p>
+ */
+ private void preparePreview(final Size sz) {
+ // Don't need change the preview size if it is same as current one.
+ if (sz.equals(mPreviewSz)) {
+ return;
+ }
+ mPreviewSz = sz;
+
+ Camera2SurfaceViewStubActivity stubActivity = getActivity();
+ final SurfaceHolder holder = stubActivity.getSurfaceView().getHolder();
+ Handler handler = new Handler(Looper.getMainLooper());
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ holder.setFixedSize(sz.getWidth(), sz.getHeight());
+ }
+ });
+
+ boolean res = stubActivity.waitForSurfaceSizeChanged(
+ WAIT_FOR_SURFACE_CHANGE_TIMEOUT_MS, sz.getWidth(), sz.getHeight());
+ assertTrue("wait for surface change to " + sz.toString() + " timed out", res);
+ mPreviewSurface = holder.getSurface();
+ assertTrue("Preview surface is invalid", mPreviewSurface.isValid());
+ }
+
+ private void stopCameraStreaming() throws Exception {
+ if (VERBOSE) {
+ Log.v(TAG, "Stopping camera streaming and waiting for idle");
+ }
+ // Stop repeating, wait for captures to complete, and disconnect from
+ // surfaces
+ mCamera.configureOutputs(/* outputs */null);
+ mCameraListener.waitForState(STATE_BUSY, CAMERA_BUSY_TIMEOUT_MS);
+ mCameraListener.waitForState(STATE_UNCONFIGURED, CAMERA_IDLE_TIMEOUT_MS);
+ }
+
+ private void stopRecording(boolean useMediaRecorder) throws Exception {
+ if (useMediaRecorder) {
+ stopCameraStreaming();
+
+ mMediaRecorder.stop();
+ // Can reuse the MediaRecorder object after reset.
+ mMediaRecorder.reset();
+ } else {
+ // TODO: need implement MediaCodec path.
+ }
+ if (mRecordingSurface != null) {
+ mRecordingSurface.release();
+ mRecordingSurface = null;
+ }
+ }
+
+ private void releasRecorder() {
+ if (mMediaRecorder != null) {
+ mMediaRecorder.release();
+ mMediaRecorder = null;
+ }
+ }
+
+ private void validateRecording(Size sz, int durationMs) throws Exception {
+ File outFile = new File(mOutMediaFileName);
+ assertTrue("No video is recorded", outFile.exists());
+
+ MediaPlayer mediaPlayer = new MediaPlayer();
+ try {
+ mediaPlayer.setDataSource(mOutMediaFileName);
+ mediaPlayer.prepare();
+ Size videoSz = new Size(mediaPlayer.getVideoWidth(), mediaPlayer.getVideoHeight());
+ assertTrue("Video size doesn't match", videoSz.equals(sz));
+ int duration = mediaPlayer.getDuration();
+ assertTrue(String.format(
+ "Video duration doesn't match: recorded %dms, expected %dms", duration,
+ durationMs), Math.abs(duration - durationMs) < DURATION_MARGIN_MS);
+ } finally {
+ mediaPlayer.release();
+ if (!DEBUG_DUMP) {
+ outFile.delete();
+ }
+ }
+ }
+
+ /**
+ * Calculate a video bit rate based on the size. The bit rate is scaled
+ * based on ratio of video size to 1080p size.
+ */
+ private int getVideoBitRate(Size sz) {
+ int rate = BIT_RATE_1080P;
+ float scaleFactor = sz.getHeight() * sz.getWidth() / (float)(1920 * 1080);
+ rate = (int)(rate * scaleFactor);
+
+ // Clamp to the MIN, MAX range.
+ return Math.max(BIT_RATE_MIN, Math.min(BIT_RATE_MAX, rate));
+ }
+
+ /**
+ * Check if the encoder and camera are able to support this size and frame rate.
+ * Assume the video compression format is AVC.
+ */
+ private boolean isSupported(Size sz, int captureRate, int encodingRate) throws Exception {
+ // Check camera capability.
+ if (!isSupportedByCamera(sz, captureRate)) {
+ return false;
+ }
+
+ // Check encode capability.
+ if (!isSupportedByAVCEncoder(sz, encodingRate)){
+ return false;
+ }
+
+ if(VERBOSE) {
+ Log.v(TAG, "Both encoder and camera support " + sz.toString() + "@" + encodingRate + "@"
+ + getVideoBitRate(sz) / 1000 + "Kbps");
+ }
+
+ return true;
+ }
+
+ private boolean isSupportedByCamera(Size sz, int frameRate) {
+ // Check if camera can support this sz and frame rate combination.
+ // FIXME: enable below code when bug 12957740 is fixed.
+ /*
+ CameraCharacteristics props = mCameraManager.getCameraCharacteristics(mCamera.getId());
+ assertNotNull("CameraCharacteristics shouldn't be null", props);
+ long[] minDurations = props.get(CameraCharacteristics.SCALER_AVAILABLE_MIN_FRAME_DURATIONS);
+ assertNotNull("android.scaler.availableMinFrameDurations shouldn't be null", minDurations);
+ // Layout of this array: (format, width, height, duration).
+ boolean foundSz = false;
+ int maxFrameRate = 0;
+ for (int i = 0; i < minDurations.length; i += 4) {
+ if (sz.getHeight() == minDurations[i + 1] && sz.getWidth() == minDurations[i + 2]) {
+ assertTrue("Min duration should be a positive number", minDurations[i + 3] > 0);
+ foundSz = true;
+ maxFrameRate = (int)(1e9f / minDurations[i + 3]);
+ break;
+ }
+ }
+ if (!foundSz || maxFrameRate < frameRate) {
+ return false;
+ }
+ */
+
+ return true;
+ }
+
+ /**
+ * Check if encoder can support this size and frame rate combination by querying
+ * MediaCodec capability. Check is based on size and frame rate. Ignore the bit rate
+ * as the bit rates targeted in this test are well below the bit rate max value specified
+ * by AVC specification for certain level.
+ */
+ private static boolean isSupportedByAVCEncoder(Size sz, int frameRate) {
+ String mimeType = "video/avc";
+ MediaCodecInfo codecInfo = getEncoderInfo(mimeType);
+ if (codecInfo == null) {
+ return false;
+ }
+ CodecCapabilities cap = codecInfo.getCapabilitiesForType(mimeType);
+ if (cap == null) {
+ return false;
+ }
+
+ int highestLevel = 0;
+ for (CodecProfileLevel lvl : cap.profileLevels) {
+ if (lvl.level > highestLevel) {
+ highestLevel = lvl.level;
+ }
+ }
+ // Don't support anything meaningful for level 1 or 2.
+ if (highestLevel <= CodecProfileLevel.AVCLevel2) {
+ return false;
+ }
+
+ if(VERBOSE) {
+ Log.v(TAG, "The highest level supported by encoder is: " + highestLevel);
+ }
+
+ // Put bitRate here for future use.
+ int maxW, maxH, bitRate;
+ // Max encoding speed.
+ int maxMacroblocksPerSecond = 0;
+ switch(highestLevel) {
+ case CodecProfileLevel.AVCLevel21:
+ maxW = 352;
+ maxH = 576;
+ bitRate = 4000000;
+ maxMacroblocksPerSecond = 19800;
+ break;
+ case CodecProfileLevel.AVCLevel22:
+ maxW = 720;
+ maxH = 480;
+ bitRate = 4000000;
+ maxMacroblocksPerSecond = 20250;
+ break;
+ case CodecProfileLevel.AVCLevel3:
+ maxW = 720;
+ maxH = 480;
+ bitRate = 10000000;
+ maxMacroblocksPerSecond = 40500;
+ break;
+ case CodecProfileLevel.AVCLevel31:
+ maxW = 1280;
+ maxH = 720;
+ bitRate = 14000000;
+ maxMacroblocksPerSecond = 108000;
+ break;
+ case CodecProfileLevel.AVCLevel32:
+ maxW = 1280;
+ maxH = 720;
+ bitRate = 20000000;
+ maxMacroblocksPerSecond = 216000;
+ break;
+ case CodecProfileLevel.AVCLevel4:
+ maxW = 1920;
+ maxH = 1088; // It should be 1088 in terms of AVC capability.
+ bitRate = 20000000;
+ maxMacroblocksPerSecond = 245760;
+ break;
+ case CodecProfileLevel.AVCLevel41:
+ maxW = 1920;
+ maxH = 1088; // It should be 1088 in terms of AVC capability.
+ bitRate = 50000000;
+ maxMacroblocksPerSecond = 245760;
+ break;
+ case CodecProfileLevel.AVCLevel42:
+ maxW = 2048;
+ maxH = 1088; // It should be 1088 in terms of AVC capability.
+ bitRate = 50000000;
+ maxMacroblocksPerSecond = 522240;
+ break;
+ case CodecProfileLevel.AVCLevel5:
+ maxW = 3672;
+ maxH = 1536;
+ bitRate = 135000000;
+ maxMacroblocksPerSecond = 589824;
+ break;
+ case CodecProfileLevel.AVCLevel51:
+ default:
+ maxW = 4096;
+ maxH = 2304;
+ bitRate = 240000000;
+ maxMacroblocksPerSecond = 983040;
+ break;
+ }
+
+ // Check size limit.
+ if (sz.getWidth() > maxW || sz.getHeight() > maxH) {
+ Log.i(TAG, "Requested resolution " + sz.toString() + " exceeds (" +
+ maxW + "," + maxH + ")");
+ return false;
+ }
+
+ // Check frame rate limit.
+ Size sizeInMb = new Size((sz.getWidth() + 15) / 16, (sz.getHeight() + 15) / 16);
+ int maxFps = maxMacroblocksPerSecond / (sizeInMb.getWidth() * sizeInMb.getHeight());
+ if (frameRate > maxFps) {
+ Log.i(TAG, "Requested frame rate " + frameRate + " exceeds " + maxFps);
+ return false;
+ }
+
+ return true;
+ }
+
+ private static MediaCodecInfo getEncoderInfo(String mimeType) {
+ int numCodecs = MediaCodecList.getCodecCount();
+ for (int i = 0; i < numCodecs; i++) {
+ MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
+
+ if (!codecInfo.isEncoder()) {
+ continue;
+ }
+
+ String[] types = codecInfo.getSupportedTypes();
+ for (int j = 0; j < types.length; j++) {
+ if (types[j].equalsIgnoreCase(mimeType)) {
+ return codecInfo;
+ }
+ }
+ }
+ return null;
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/AssertHelpers.java b/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/AssertHelpers.java
new file mode 100644
index 0000000..08aa8bf
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/AssertHelpers.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.camera2.cts.helpers;
+
+import static junit.framework.Assert.*;
+
+import java.util.Arrays;
+
+/**
+ * Helper set of methods to add extra useful assert functionality missing in junit.
+ */
+public class AssertHelpers {
+ /**
+ * Assert that at least one of the elements in data is non-zero.
+ *
+ * <p>An empty or a null array always fails.</p>
+ */
+ public static void assertArrayNotAllZeroes(String message, byte[] data) {
+ int size = data.length;
+
+ int i = 0;
+ for (i = 0; i < size; ++i) {
+ if (data[i] != 0) {
+ break;
+ }
+ }
+
+ assertTrue(message, i < size);
+ }
+
+ /**
+ * Assert that every element in left is less than or equals to the corresponding element in
+ * right.
+ *
+ * <p>Array sizes must match.</p>
+ *
+ * @param message Message to use in case the assertion fails
+ * @param left Left array
+ * @param right Right array
+ */
+ public static void assertArrayNotGreater(String message, float[] left, float[] right) {
+ assertEquals("Array lengths did not match", left.length, right.length);
+
+ String leftString = Arrays.toString(left);
+ String rightString = Arrays.toString(right);
+
+ for (int i = 0; i < left.length; ++i) {
+ String msg = String.format(
+ "%s: (%s should be less than or equals than %s; item index %d; left = %s; " +
+ "right = %s)",
+ message, left[i], right[i], i, leftString, rightString);
+
+ assertTrue(msg, left[i] <= right[i]);
+ }
+ }
+
+ /**
+ * Assert that every element in the value array is greater than the lower bound (exclusive).
+ *
+ * @param value an array of items
+ * @param lowerBound the exclusive lower bound
+ */
+ public static void assertArrayWithinLowerBound(String message, float[] value, float lowerBound)
+ {
+ for (int i = 0; i < value.length; ++i) {
+ assertTrue(
+ String.format("%s: (%s should be greater than than %s; item index %d in %s)",
+ message, value[i], lowerBound, i, Arrays.toString(value)),
+ value[i] > lowerBound);
+ }
+ }
+
+ /**
+ * Assert that every element in the value array is less than the upper bound (exclusive).
+ *
+ * @param value an array of items
+ * @param upperBound the exclusive upper bound
+ */
+ public static void assertArrayWithinUpperBound(String message, float[] value, float upperBound)
+ {
+ for (int i = 0; i < value.length; ++i) {
+ assertTrue(
+ String.format("%s: (%s should be less than than %s; item index %d in %s)",
+ message, value[i], upperBound, i, Arrays.toString(value)),
+ value[i] < upperBound);
+ }
+ }
+
+ /**
+ * Assert that {@code low <= value <= high}
+ */
+ public static void assertInRange(float value, float low, float high) {
+ assertTrue(
+ String.format("Value %s must be greater or equal to %s, but was lower", value, low),
+ value >= low);
+ assertTrue(
+ String.format("Value %s must be less than or equal to %s, but was higher",
+ value, high),
+ value <= high);
+
+ // TODO: generic by using comparators
+ }
+
+ // Suppress default constructor for noninstantiability
+ private AssertHelpers() { throw new AssertionError(); }
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/CameraErrorCollector.java b/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/CameraErrorCollector.java
index 7faeb68..891fecf 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/CameraErrorCollector.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/CameraErrorCollector.java
@@ -16,9 +16,16 @@
package android.hardware.camera2.cts.helpers;
+import android.hardware.camera2.CameraMetadata.Key;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureRequest.Builder;
+
+import org.hamcrest.CoreMatchers;
import org.hamcrest.Matcher;
import org.junit.rules.ErrorCollector;
+import java.util.Objects;
+
/**
* A camera test ErrorCollector class to gather the test failures during a test,
* instead of failing the test immediately for each failure.
@@ -71,6 +78,11 @@
}
+ /**
+ * Set the camera id to this error collector object for logging purpose.
+ *
+ * @param id The camera id to be set.
+ */
public void setCameraId(String id) {
if (id != null) {
mCameraMsg = "Test failed for camera " + id + ": ";
@@ -78,4 +90,106 @@
mCameraMsg = "";
}
}
+
+ /**
+ * Adds a failure to the table if {@code condition} is not {@code true}.
+ * <p>
+ * Execution continues, but the test will fail at the end if the condition
+ * failed.
+ * </p>
+ *
+ * @param msg Message to be logged when check fails.
+ * @param condition Log the failure if it is not true.
+ */
+ public boolean expectTrue(String msg, boolean condition) {
+ if (!condition) {
+ addMessage(msg);
+ }
+
+ return condition;
+ }
+
+ /**
+ * Check if the two values are equal.
+ *
+ * @param msg Message to be logged when check fails.
+ * @param expected Expected value to be checked against.
+ * @param actual Actual value to be checked.
+ * @return {@code true} if the two values are equal, {@code false} otherwise.
+ */
+ public <T> boolean expectEquals(String msg, T expected, T actual) {
+ if (!Objects.equals(expected, actual)) {
+ addMessage(String.format("%s (expected = %s, actual = %s) ", msg, expected.toString(),
+ actual.toString()));
+ return false;
+ }
+
+ return true;
+ }
+
+ public void expectNotNull(String msg, Object obj) {
+ checkThat(msg, obj, CoreMatchers.notNullValue());
+ }
+
+ /**
+ * Check if the key value is not null and return the value.
+ *
+ * @param request The {@link CaptureRequest#Builder} to get the key from.
+ * @param key The {@link CaptureRequest} key to be checked.
+ * @return The value of the key.
+ */
+ public <T> T expectKeyValueNotNull(Builder request, Key<T> key) {
+
+ T value = request.get(key);
+ if (value == null) {
+ addMessage("Key " + key.getName() + " shouldn't be null");
+ }
+
+ return value;
+ }
+
+ /**
+ * Check if the key is non-null and the value is not equal to target.
+ *
+ * @param request The The {@link CaptureRequest#Builder} to get the key from.
+ * @param key The {@link CaptureRequest} key to be checked.
+ * @param expected The expected value of the CaptureRequest key.
+ */
+ public <T> void expectKeyValueNotEquals(Builder request, Key<T> key, T expected) {
+ if (request == null || key == null || expected == null) {
+ throw new IllegalArgumentException("request, key and target shouldn't be null");
+ }
+
+ T value;
+ if ((value = expectKeyValueNotNull(request, key)) == null) {
+ return;
+ }
+
+ String reason = "Key " + key.getName() + " shouldn't have value " + value.toString();
+ checkThat(reason, value, CoreMatchers.not(expected));
+ }
+
+ /**
+ * Check if the key is non-null and the value is equal to target.
+ *
+ * <p>Only check non-null if the target is null.</p>
+ *
+ * @param request The The {@link CaptureRequest#Builder} to get the key from.
+ * @param key The {@link CaptureRequest} key to be checked.
+ * @param expected The expected value of the CaptureRequest key.
+ */
+ public <T> void expectKeyValueEquals(Builder request, Key<T> key, T expected) {
+ if (request == null || key == null || expected == null) {
+ throw new IllegalArgumentException("request, key and target shouldn't be null");
+ }
+
+ T value;
+ if ((value = expectKeyValueNotNull(request, key)) == null) {
+ return;
+ }
+
+ String reason = "Key " + key.getName() + " value " + value.toString()
+ + " doesn't match the expected value " + expected.toString();
+ checkThat(reason, value, CoreMatchers.equalTo(expected));
+ }
}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/MaybeNull.java b/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/MaybeNull.java
new file mode 100644
index 0000000..029ab03
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/MaybeNull.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.camera2.cts.helpers;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+/**
+ * Helper set of methods for dealing with objects that are sometimes {@code null}.
+ *
+ * <p>Used to remove common patterns like: <pre>{@code
+ * if (obj != null) {
+ * obj.doSomething();
+ * }</pre>
+ *
+ * If this is common, consider adding {@code doSomething} to this class so that the code
+ * looks more like <pre>{@code
+ * MaybeNull.doSomething(obj);
+ * }</pre>
+ */
+public class MaybeNull {
+ /**
+ * Close the underlying {@link AutoCloseable}, if it's not {@code null}.
+ *
+ * @param closeable An object which implements {@link AutoCloseable}.
+ * @throws Exception If {@link AutoCloseable#close} fails.
+ */
+ public static <T extends AutoCloseable> void close(T closeable) throws Exception {
+ if (closeable != null) {
+ closeable.close();
+ }
+ }
+
+ /**
+ * Close the underlying {@link UncheckedCloseable}, if it's not {@code null}.
+ *
+ * <p>No checked exceptions are thrown. An unknown runtime exception might still
+ * be raised.</p>
+ *
+ * @param closeable An object which implements {@link UncheckedCloseable}.
+ */
+ public static <T extends UncheckedCloseable> void close(T closeable) {
+ if (closeable != null) {
+ closeable.close();
+ }
+ }
+
+ /**
+ * Close the underlying {@link Closeable}, if it's not {@code null}.
+ *
+ * @param closeable An object which implements {@link Closeable}.
+ * @throws Exception If {@link Closeable#close} fails.
+ */
+ public static <T extends Closeable> void close(T closeable) throws IOException {
+ if (closeable != null) {
+ closeable.close();
+ }
+ }
+
+ // Suppress default constructor for noninstantiability
+ private MaybeNull() { throw new AssertionError(); }
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/Preconditions.java b/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/Preconditions.java
new file mode 100644
index 0000000..8520a48
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/Preconditions.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.camera2.cts.helpers;
+
+import java.util.Objects;
+
+/**
+ * Helper set of methods to perform precondition checks before starting method execution.
+ *
+ * <p>Typically used to sanity check arguments or the current object state.</p>
+ */
+public final class Preconditions {
+
+ /**
+ * Checks that the value has the expected bitwise flags set.
+ *
+ * @param argName Name of the argument
+ * @param arg Argument to check
+ * @param flagsName Name of the bitwise flags
+ * @param flags Bit flags to check.
+ * @return arg
+ *
+ * @throws IllegalArgumentException if the bitwise flags weren't set
+ */
+ public static int checkBitFlags(String argName, int arg, String flagsName, int flags) {
+ if ((arg & flags) == 0) {
+ throw new IllegalArgumentException(
+ String.format("Argument '%s' must have flags '%s' set", argName, flagsName));
+ }
+
+ return arg;
+ }
+
+ /**
+ * Checks that the value is {@link Object#equals equal} to the expected value.
+ *
+ * @param argName Name of the argument
+ * @param arg Argument to check
+ * @param expectedName Name of the expected value
+ * @param expectedValue Expected value
+ * @return arg
+ *
+ * @throws IllegalArgumentException if the values were not equal
+ */
+ public static <T> T checkEquals(String argName, T arg,
+ String expectedName, T expectedValue) {
+ if (!Objects.equals(arg, expectedValue)) {
+ throw new IllegalArgumentException(
+ String.format(
+ "Argument '%s' must be equal to '%s' (was '%s', but expected '%s')",
+ argName, expectedName, arg, expectedValue));
+ }
+
+ return arg;
+ }
+
+ /**
+ * Checks that the value is not {@code null}.
+ *
+ * <p>
+ * Returns the value directly, so you can use {@code checkNotNull("value", value)} inline.
+ * </p>
+ *
+ * @param argName Name of the argument
+ * @param arg Argument to check
+ * @return arg
+ *
+ * @throws NullPointerException if arg was {@code null}
+ */
+ public static <T> T checkNotNull(String argName, T arg) {
+ if (arg == null) {
+ throw new NullPointerException("Argument '" + argName + "' must not be null");
+ }
+
+ return arg;
+ }
+
+ /**
+ * Checks that the state is currently {@link true}.
+ *
+ * @param message Message to raise an exception with if the state checking fails.
+ * @param state State to check
+ *
+ * @throws IllegalStateException if state was {@code false}
+ *
+ * @return The state value (always {@code true}).
+ */
+ public static boolean checkState(String message, boolean state) {
+ if (!state) {
+ throw new IllegalStateException(message);
+ }
+
+ return state;
+ }
+
+ // Suppress default constructor for noninstantiability
+ private Preconditions() { throw new AssertionError(); }
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/StaticMetadata.java b/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/StaticMetadata.java
index d057f5f..d397fca 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/StaticMetadata.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/StaticMetadata.java
@@ -21,7 +21,11 @@
import android.hardware.camera2.CameraMetadata.Key;
import android.util.Log;
+import junit.framework.Assert;
+
import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.List;
/**
* Helpers to get common static info out of the camera.
@@ -41,29 +45,83 @@
private static final int SENSOR_INFO_EXPOSURE_TIME_RANGE_MIN = 0;
private static final int SENSOR_INFO_EXPOSURE_TIME_RANGE_MAX = 1;
private static final long SENSOR_INFO_EXPOSURE_TIME_RANGE_MIN_AT_MOST = 100000L; // 100us
- private static final long SENSOR_INFO_EXPOSURE_TIME_RANGE_MAX_AT_LEAST = 1000000000; // 1s
+ private static final long SENSOR_INFO_EXPOSURE_TIME_RANGE_MAX_AT_LEAST = 100000000; // 100ms
private static final int SENSOR_INFO_SENSITIVITY_RANGE_SIZE = 2;
private static final int SENSOR_INFO_SENSITIVITY_RANGE_MIN = 0;
private static final int SENSOR_INFO_SENSITIVITY_RANGE_MAX = 1;
private static final int SENSOR_INFO_SENSITIVITY_RANGE_MIN_AT_MOST = 100;
private static final int SENSOR_INFO_SENSITIVITY_RANGE_MAX_AT_LEAST = 1600;
-
// TODO: Consider making this work across any metadata object, not just camera characteristics
private final CameraCharacteristics mCharacteristics;
+ private final CheckLevel mLevel;
+ private final CameraErrorCollector mCollector;
+
+ public enum CheckLevel {
+ /** Only log warnings for metadata check failures. Execution continues. */
+ WARN,
+ /**
+ * Use ErrorCollector to collect the metadata check failures, Execution
+ * continues.
+ */
+ COLLECT,
+ /** Assert the metadata check failures. Execution aborts. */
+ ASSERT
+ }
/**
* Construct a new StaticMetadata object.
*
+ *<p> Default constructor, only log warnings for the static metadata check failures</p>
+ *
* @param characteristics static info for a camera
* @throws IllegalArgumentException if characteristics was null
*/
public StaticMetadata(CameraCharacteristics characteristics) {
+ this(characteristics, CheckLevel.WARN, /*collector*/null);
+ }
+
+ /**
+ * Construct a new StaticMetadata object with {@link CameraErrorCollector}.
+ * <p>
+ * When level is not {@link CheckLevel.COLLECT}, the {@link CameraErrorCollector} will be
+ * ignored, otherwise, it will be used to log the check failures.
+ * </p>
+ *
+ * @param characteristics static info for a camera
+ * @param collector The {@link CameraErrorCollector} used by this StaticMetadata
+ * @throws IllegalArgumentException if characteristics or collector was null.
+ */
+ public StaticMetadata(CameraCharacteristics characteristics, CameraErrorCollector collector) {
+ this(characteristics, CheckLevel.COLLECT, collector);
+ }
+
+ /**
+ * Construct a new StaticMetadata object with {@link CheckLevel} and
+ * {@link CameraErrorCollector}.
+ * <p>
+ * When level is not {@link CheckLevel.COLLECT}, the {@link CameraErrorCollector} will be
+ * ignored, otherwise, it will be used to log the check failures.
+ * </p>
+ *
+ * @param characteristics static info for a camera
+ * @param level The {@link CheckLevel} of this StaticMetadata
+ * @param collector The {@link CameraErrorCollector} used by this StaticMetadata
+ * @throws IllegalArgumentException if characteristics was null or level was
+ * {@link CheckLevel.COLLECT} but collector was null.
+ */
+ public StaticMetadata(CameraCharacteristics characteristics, CheckLevel level,
+ CameraErrorCollector collector) {
if (characteristics == null) {
throw new IllegalArgumentException("characteristics was null");
}
+ if (level == CheckLevel.COLLECT && collector == null) {
+ throw new IllegalArgumentException("collector must valid when COLLECT level is set");
+ }
mCharacteristics = characteristics;
+ mLevel = level;
+ mCollector = collector;
}
/**
@@ -122,14 +180,14 @@
long minExposure = getExposureMinimumOrDefault(Long.MAX_VALUE);
long maxExposure = getExposureMaximumOrDefault(Long.MIN_VALUE);
if (minExposure > SENSOR_INFO_EXPOSURE_TIME_RANGE_MIN_AT_MOST) {
- warnOnKey(CameraCharacteristics.SENSOR_INFO_EXPOSURE_TIME_RANGE,
+ failKeyCheck(CameraCharacteristics.SENSOR_INFO_EXPOSURE_TIME_RANGE,
String.format(
"Min value %d is too large, set to maximal legal value %d",
minExposure, SENSOR_INFO_EXPOSURE_TIME_RANGE_MIN_AT_MOST));
minExposure = SENSOR_INFO_EXPOSURE_TIME_RANGE_MIN_AT_MOST;
}
if (maxExposure < SENSOR_INFO_EXPOSURE_TIME_RANGE_MAX_AT_LEAST) {
- warnOnKey(CameraCharacteristics.SENSOR_INFO_EXPOSURE_TIME_RANGE,
+ failKeyCheck(CameraCharacteristics.SENSOR_INFO_EXPOSURE_TIME_RANGE,
String.format(
"Max value %d is too small, set to minimal legal value %d",
maxExposure, SENSOR_INFO_EXPOSURE_TIME_RANGE_MAX_AT_LEAST));
@@ -140,6 +198,44 @@
}
/**
+ * Get the available anti-banding modes.
+ *
+ * @return The array contains available anti-banding modes.
+ */
+ public byte[] getAeAvailableAntiBandingModesChecked() {
+ CameraMetadata.Key<byte[]> key =
+ CameraCharacteristics.CONTROL_AE_AVAILABLE_ANTIBANDING_MODES;
+ byte[] modes = getValueFromKeyNonNull(key);
+
+ boolean foundAuto = false;
+ for (byte mode : modes) {
+ checkTrueForKey(key, "mode value " + mode + " is out if range",
+ mode >= CameraMetadata.CONTROL_AE_ANTIBANDING_MODE_OFF ||
+ mode <= CameraMetadata.CONTROL_AE_ANTIBANDING_MODE_AUTO);
+ if (mode == CameraMetadata.CONTROL_AE_ANTIBANDING_MODE_AUTO) {
+ foundAuto = true;
+ return modes;
+ }
+ }
+ // Must contain AUTO mode.
+ checkTrueForKey(key, "AUTO mode is missing", foundAuto);
+
+ return modes;
+ }
+
+ public Boolean getFlashInfoChecked() {
+ CameraMetadata.Key<Boolean> key = CameraCharacteristics.FLASH_INFO_AVAILABLE;
+ Boolean hasFlash = getValueFromKeyNonNull(key);
+
+ // In case the failOnKey only gives warning.
+ if (hasFlash == null) {
+ return false;
+ }
+
+ return hasFlash;
+ }
+
+ /**
* Get the sensitivity value and clamp to the range if needed.
*
* @param sensitivity Input sensitivity value to check.
@@ -149,14 +245,14 @@
int minSensitivity = getSensitivityMinimumOrDefault(Integer.MAX_VALUE);
int maxSensitivity = getSensitivityMaximumOrDefault(Integer.MIN_VALUE);
if (minSensitivity > SENSOR_INFO_SENSITIVITY_RANGE_MIN_AT_MOST) {
- warnOnKey(CameraCharacteristics.SENSOR_INFO_SENSITIVITY_RANGE,
+ failKeyCheck(CameraCharacteristics.SENSOR_INFO_SENSITIVITY_RANGE,
String.format(
"Min value %d is too large, set to maximal legal value %d",
minSensitivity, SENSOR_INFO_SENSITIVITY_RANGE_MIN_AT_MOST));
minSensitivity = SENSOR_INFO_SENSITIVITY_RANGE_MIN_AT_MOST;
}
if (maxSensitivity < SENSOR_INFO_SENSITIVITY_RANGE_MAX_AT_LEAST) {
- warnOnKey(CameraCharacteristics.SENSOR_INFO_SENSITIVITY_RANGE,
+ failKeyCheck(CameraCharacteristics.SENSOR_INFO_SENSITIVITY_RANGE,
String.format(
"Max value %d is too small, set to minimal legal value %d",
maxSensitivity, SENSOR_INFO_SENSITIVITY_RANGE_MAX_AT_LEAST));
@@ -170,6 +266,19 @@
* Get the minimum value for a sensitivity range from android.sensor.info.sensitivityRange.
*
* <p>If the camera is incorrectly reporting values, log a warning and return
+ * the default value instead, which is the largest minimum value required to be supported
+ * by all camera devices.</p>
+ *
+ * @return The value reported by the camera device or the defaultValue otherwise.
+ */
+ public int getSensitivityMinimumOrDefault() {
+ return getSensitivityMinimumOrDefault(SENSOR_INFO_SENSITIVITY_RANGE_MIN_AT_MOST);
+ }
+
+ /**
+ * Get the minimum value for a sensitivity range from android.sensor.info.sensitivityRange.
+ *
+ * <p>If the camera is incorrectly reporting values, log a warning and return
* the default value instead.</p>
*
* @param defaultValue Value to return if no legal value is available
@@ -188,6 +297,19 @@
* Get the maximum value for a sensitivity range from android.sensor.info.sensitivityRange.
*
* <p>If the camera is incorrectly reporting values, log a warning and return
+ * the default value instead, which is the smallest maximum value required to be supported
+ * by all camera devices.</p>
+ *
+ * @return The value reported by the camera device or the defaultValue otherwise.
+ */
+ public int getSensitivityMaximumOrDefault() {
+ return getSensitivityMaximumOrDefault(SENSOR_INFO_SENSITIVITY_RANGE_MAX_AT_LEAST);
+ }
+
+ /**
+ * Get the maximum value for a sensitivity range from android.sensor.info.sensitivityRange.
+ *
+ * <p>If the camera is incorrectly reporting values, log a warning and return
* the default value instead.</p>
*
* @param defaultValue Value to return if no legal value is available
@@ -221,6 +343,19 @@
}
/**
+ * Get the minimum value for an exposure range from android.sensor.info.exposureTimeRange.
+ *
+ * <p>If the camera is incorrectly reporting values, log a warning and return
+ * the default value instead, which is the largest minimum value required to be supported
+ * by all camera devices.</p>
+ *
+ * @return The value reported by the camera device or the defaultValue otherwise.
+ */
+ public long getExposureMinimumOrDefault() {
+ return getExposureMinimumOrDefault(SENSOR_INFO_EXPOSURE_TIME_RANGE_MIN_AT_MOST);
+ }
+
+ /**
* Get the maximum value for an exposure range from android.sensor.info.exposureTimeRange.
*
* <p>If the camera is incorrectly reporting values, log a warning and return
@@ -239,6 +374,82 @@
}
/**
+ * Get the maximum value for an exposure range from android.sensor.info.exposureTimeRange.
+ *
+ * <p>If the camera is incorrectly reporting values, log a warning and return
+ * the default value instead, which is the smallest maximum value required to be supported
+ * by all camera devices.</p>
+ *
+ * @return The value reported by the camera device or the defaultValue otherwise.
+ */
+ public long getExposureMaximumOrDefault() {
+ return getExposureMaximumOrDefault(SENSOR_INFO_EXPOSURE_TIME_RANGE_MAX_AT_LEAST);
+ }
+
+ /**
+ * Get aeAvailableModes and do the sanity check.
+ *
+ * <p>Depending on the check level this class has, for WAR or COLLECT levels,
+ * If the aeMode list is invalid, return an empty mode array. The the caller doesn't
+ * have to abort the execution even the aeMode list is invalid.</p>
+ * @return AE available modes
+ */
+ public byte[] getAeAvailableModesChecked() {
+ CameraMetadata.Key<byte[]> modesKey = CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES;
+ byte[] modes = getValueFromKeyNonNull(modesKey);
+ if (modes == null) {
+ modes = new byte[0];
+ }
+ List<Integer> modeList = new ArrayList<Integer>();
+ for (byte mode : modes) {
+ modeList.add((int)(mode));
+ }
+ checkTrueForKey(modesKey, "value is empty", !modeList.isEmpty());
+
+ // All camera device must support ON
+ checkTrueForKey(modesKey, "values " + modeList.toString() + " must contain ON mode",
+ modeList.contains(CameraMetadata.CONTROL_AE_MODE_ON));
+
+ // All camera devices with flash units support ON_AUTO_FLASH and ON_ALWAYS_FLASH
+ CameraMetadata.Key<Boolean> flashKey= CameraCharacteristics.FLASH_INFO_AVAILABLE;
+ Boolean hasFlash = getValueFromKeyNonNull(flashKey);
+ if (hasFlash == null) {
+ hasFlash = false;
+ }
+ if (hasFlash) {
+ boolean flashModeConsistentWithFlash =
+ modeList.contains(CameraMetadata.CONTROL_AE_MODE_ON_AUTO_FLASH) &&
+ modeList.contains(CameraMetadata.CONTROL_AE_MODE_ON_ALWAYS_FLASH);
+ checkTrueForKey(modesKey,
+ "value must contain ON_AUTO_FLASH and ON_ALWAYS_FLASH and when flash is" +
+ "available", flashModeConsistentWithFlash);
+ } else {
+ boolean flashModeConsistentWithoutFlash =
+ !(modeList.contains(CameraMetadata.CONTROL_AE_MODE_ON_AUTO_FLASH) ||
+ modeList.contains(CameraMetadata.CONTROL_AE_MODE_ON_ALWAYS_FLASH) ||
+ modeList.contains(CameraMetadata.CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE));
+ checkTrueForKey(modesKey,
+ "value must not contain ON_AUTO_FLASH, ON_ALWAYS_FLASH and" +
+ "ON_AUTO_FLASH_REDEYE when flash is unavailable",
+ flashModeConsistentWithoutFlash);
+ }
+
+ // FULL mode camera devices always support OFF mode.
+ boolean condition =
+ !isHardwareLevelFull() || modeList.contains(CameraMetadata.CONTROL_AE_MODE_OFF);
+ checkTrueForKey(modesKey, "Full capability device must have OFF mode", condition);
+
+ // Boundary check.
+ for (byte mode : modes) {
+ checkTrueForKey(modesKey, "Value " + mode + " is out of bound",
+ mode >= CameraMetadata.CONTROL_AE_MODE_OFF
+ && mode <= CameraMetadata.CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE);
+ }
+
+ return modes;
+ }
+
+ /**
* Get the value in index for a fixed-size array from a given key.
*
* <p>If the camera device is incorrectly reporting values, log a warning and return
@@ -260,7 +471,7 @@
size);
if (elementValue == null) {
- warnOnKey(key,
+ failKeyCheck(key,
"had no valid " + name + " value; using default of " + defaultValue);
elementValue = defaultValue;
}
@@ -308,7 +519,7 @@
if (size != IGNORE_SIZE_CHECK) {
int actualLength = Array.getLength(array);
if (actualLength != size) {
- warnOnKey(key,
+ failKeyCheck(key,
String.format("had the wrong number of elements (%d), expected (%d)",
actualLength, size));
return null;
@@ -319,7 +530,7 @@
T val = (T) Array.get(array, element);
if (val == null) {
- warnOnKey(key, "had a null element at index" + element);
+ failKeyCheck(key, "had a null element at index" + element);
return null;
}
@@ -337,15 +548,33 @@
T value = mCharacteristics.get(key);
if (value == null) {
- warnOnKey(key, "was null");
+ failKeyCheck(key, "was null");
}
return value;
}
- private static <T> void warnOnKey(Key<T> key, String message) {
+ private <T> void checkTrueForKey(Key<T> key, String message, boolean condition) {
+ if (!condition) {
+ failKeyCheck(key, message);
+ }
+ }
+
+ private <T> void failKeyCheck(Key<T> key, String message) {
// TODO: Consider only warning once per key/message combination if it's too spammy.
// TODO: Consider offering other options such as throwing an assertion exception
- Log.w(TAG, String.format("The static info key '%s' %s", key.getName(), message));
+ String failureCause = String.format("The static info key '%s' %s", key.getName(), message);
+ switch (mLevel) {
+ case WARN:
+ Log.w(TAG, failureCause);
+ break;
+ case COLLECT:
+ mCollector.addMessage(failureCause);
+ break;
+ case ASSERT:
+ Assert.fail(failureCause);
+ default:
+ throw new UnsupportedOperationException("Unhandled level " + mLevel);
+ }
}
}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/UncheckedCloseable.java b/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/UncheckedCloseable.java
new file mode 100644
index 0000000..570ef2c
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/UncheckedCloseable.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.camera2.cts.helpers;
+
+/**
+ * Defines an interface for classes that can (or need to) be closed once they
+ * are not used any longer; calling the {@code close} method releases resources
+ * that the object holds.</p>
+ *
+ * <p>This signifies that the implementor will never throw checked exceptions when closing,
+ * allowing for more fine grained exception handling at call sites handling this interface
+ * generically.</p>
+ *
+ * <p>A common pattern for using an {@code UncheckedCloseable} resource:
+ * <pre> {@code
+ * // where <Foo extends UncheckedCloseable>
+ * UncheckedCloseable foo = new Foo();
+ * try {
+ * ...;
+ * } finally {
+ * foo.close();
+ * }
+ * }</pre>
+ */
+public interface UncheckedCloseable extends AutoCloseable {
+
+ /**
+ * Closes the object and release any system resources it holds.
+ *
+ * <p>Does not throw any checked exceptions.</p>
+ */
+ @Override
+ void close();
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/rs/AllocationCache.java b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/AllocationCache.java
new file mode 100644
index 0000000..e65e819
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/AllocationCache.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.camera2.cts.rs;
+
+import android.hardware.camera2.cts.helpers.UncheckedCloseable;
+import android.renderscript.Allocation;
+import android.renderscript.RenderScript;
+import android.renderscript.Type;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static android.hardware.camera2.cts.helpers.Preconditions.*;
+
+/**
+ * Cache {@link Allocation} objects based on their type and usage.
+ *
+ * <p>This avoids expensive re-allocation of objects when they are used over and over again
+ * by different scripts.</p>
+ */
+public class AllocationCache implements UncheckedCloseable {
+
+ private static final String TAG = "AllocationCache";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private static int sDebugHits = 0;
+ private static int sDebugMisses = 0;
+
+ private final RenderScript mRS;
+ private final HashMap<AllocationKey, List<Allocation>> mAllocationMap =
+ new HashMap<AllocationKey, List<Allocation>>();
+ private boolean mClosed = false;
+
+ /**
+ * Create a new cache with the specified RenderScript context.
+ *
+ * @param rs A non-{@code null} RenderScript context.
+ *
+ * @throws NullPointerException if rs was null
+ */
+ public AllocationCache(RenderScript rs) {
+ mRS = checkNotNull("rs", rs);
+ }
+
+ /**
+ * Returns the {@link RenderScript} context associated with this AllocationCache.
+ *
+ * @return A non-{@code null} RenderScript value.
+ */
+ public RenderScript getRenderScript() {
+ return mRS;
+ }
+
+ /**
+ * Try to lookup a compatible Allocation from the cache, create one if none exist.
+ *
+ * @param type A non-{@code null} RenderScript Type.
+ * @throws NullPointerException if type was null
+ * @throws IllegalStateException if the cache was closed with {@link #close}
+ */
+ public synchronized Allocation getOrCreateTyped(Type type, int usage) {
+ checkNotNull("type", type);
+ checkNotClosed();
+
+ AllocationKey key = new AllocationKey(type, usage);
+ List<Allocation> list = mAllocationMap.get(key);
+
+ Allocation alloc;
+
+ if (list == null || list.isEmpty()) {
+ alloc = Allocation.createTyped(mRS, type, usage);
+
+ if (DEBUG) {
+ sDebugMisses++;
+ Log.d(TAG, String.format(
+ "Cache MISS (%d): type = '%s', usage = '%x'", sDebugMisses, type, usage));
+ }
+ } else {
+ alloc = list.remove(list.size() - 1);
+
+ if (DEBUG) {
+ sDebugHits++;
+ Log.d(TAG, String.format(
+ "Cache HIT (%d): type = '%s', usage = '%x'", sDebugHits, type, usage));
+ }
+ }
+
+ return alloc;
+ }
+
+ /**
+ * Return the Allocation to the cache.
+ *
+ * <p>Future calls to getOrCreateTyped with the same type and usage may
+ * return this allocation.</p>
+ *
+ * <p>Allocations that have usage {@link Allocation#USAGE_IO_INPUT} get their
+ * listeners reset. Those that have {@link Allocation#USAGE_IO_OUTPUT} get their
+ * surfaces reset.</p>
+ *
+ * @param allocation A non-{@code null} RenderScript {@link Allocation}
+ * @throws NullPointerException if allocation was null
+ * @throws IllegalArgumentException if the allocation was already returned previously
+ * @throws IllegalStateException if the cache was closed with {@link #close}
+ */
+ public synchronized void returnToCache(Allocation allocation) {
+ checkNotNull("allocation", allocation);
+ checkNotClosed();
+
+ int usage = allocation.getUsage();
+ AllocationKey key = new AllocationKey(allocation.getType(), usage);
+ List<Allocation> value = mAllocationMap.get(key);
+
+ if (value != null && value.contains(allocation)) {
+ throw new IllegalArgumentException("allocation was already returned to the cache");
+ }
+
+ if ((usage & Allocation.USAGE_IO_INPUT) != 0) {
+ allocation.setOnBufferAvailableListener(null);
+ }
+ if ((usage & Allocation.USAGE_IO_OUTPUT) != 0) {
+ allocation.setSurface(null);
+ }
+
+ if (value == null) {
+ value = new ArrayList<Allocation>(/*capacity*/1);
+ mAllocationMap.put(key, value);
+ }
+
+ value.add(allocation);
+
+ // TODO: Evict existing allocations from cache when we get too many items in it,
+ // to avoid running out of memory
+
+ // TODO: move to using android.util.LruCache under the hood
+ }
+
+ /**
+ * Return the allocation to the cache, if it wasn't {@code null}.
+ *
+ * <p>Future calls to getOrCreateTyped with the same type and usage may
+ * return this allocation.</p>
+ *
+ * <p>Allocations that have usage {@link Allocation#USAGE_IO_INPUT} get their
+ * listeners reset. Those that have {@link Allocation#USAGE_IO_OUTPUT} get their
+ * surfaces reset.</p>
+ *
+ * <p>{@code null} values are a no-op.</p>
+ *
+ * @param allocation A potentially {@code null} RenderScript {@link Allocation}
+ * @throws IllegalArgumentException if the allocation was already returned previously
+ * @throws IllegalStateException if the cache was closed with {@link #close}
+ */
+ public synchronized void returnToCacheIfNotNull(Allocation allocation) {
+ if (allocation != null) {
+ returnToCache(allocation);
+ }
+ }
+
+ /**
+ * Closes the object and destroys any Allocations still in the cache.
+ */
+ @Override
+ public synchronized void close() {
+ if (mClosed) return;
+
+ for (Map.Entry<AllocationKey, List<Allocation>> entry : mAllocationMap.entrySet()) {
+ List<Allocation> value = entry.getValue();
+
+ for (Allocation alloc : value) {
+ alloc.destroy();
+ }
+
+ value.clear();
+ }
+
+ mAllocationMap.clear();
+ mClosed = true;
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ close();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Holder class to check if one allocation is compatible with another.
+ *
+ * <p>An Allocation is considered compatible if both it's Type and usage is equivalent.</p>
+ */
+ private static class AllocationKey {
+ private final Type mType;
+ private final int mUsage;
+
+ public AllocationKey(Type type, int usage) {
+ mType = type;
+ mUsage = usage;
+ }
+
+ @Override
+ public int hashCode() {
+ return mType.hashCode() ^ mUsage;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof AllocationKey){
+ AllocationKey otherKey = (AllocationKey) other;
+
+ return otherKey.mType.equals(mType) && otherKey.mUsage == otherKey.mUsage;
+ }
+
+ return false;
+ }
+ }
+
+ private void checkNotClosed() {
+ if (mClosed == true) {
+ throw new IllegalStateException("AllocationCache has already been closed");
+ }
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/rs/AllocationInfo.java b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/AllocationInfo.java
new file mode 100644
index 0000000..15a5ea6
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/AllocationInfo.java
@@ -0,0 +1,441 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.camera2.cts.rs;
+
+import static android.hardware.camera2.cts.helpers.Preconditions.*;
+
+import android.graphics.ImageFormat;
+import android.graphics.PixelFormat;
+import android.hardware.camera2.Size;
+import android.renderscript.Allocation;
+import android.renderscript.Element;
+import android.renderscript.RenderScript;
+import android.renderscript.Type;
+import android.util.Log;
+
+/**
+ * Abstract the information necessary to create new {@link Allocation allocations} with
+ * their size, element, type, and usage.
+ *
+ * <p>This also includes convenience functions for printing to a string, something RenderScript
+ * lacks at the time of writing.</p>
+ *
+ * <p>Note that when creating a new {@link AllocationInfo} the usage flags <b>always</b> get ORd
+ * to {@link Allocation#USAGE_IO_SCRIPT}.</p>
+ */
+public class AllocationInfo {
+
+ private final RenderScript mRS = RenderScriptSingleton.getRS();
+
+ private final Size mSize;
+ private final Element mElement;
+ private final Type mType;
+ private final int mUsage;
+
+ private static final String TAG = "AllocationInfo";
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+ /**
+ * Create a new {@link AllocationInfo} holding the element, size, and usage
+ * from an existing {@link Allocation}.
+ *
+ * @param allocation {@link Allocation}
+ *
+ * @return A new {@link AllocationInfo}
+ *
+ * @throws NullPointerException if allocation was {@code null}.
+ */
+ public static AllocationInfo newInstance(Allocation allocation) {
+ checkNotNull("allocation", allocation);
+
+ return new AllocationInfo(allocation.getElement(),
+ new Size(allocation.getType().getX(), allocation.getType().getY()),
+ allocation.getUsage());
+ }
+
+ /**
+ * Create a new {@link AllocationInfo} holding the specified format, {@link Size},
+ * and {@link Allocation#USAGE_SCRIPT usage}.
+ *
+ * <p>The usage is always ORd with {@link Allocation#USAGE_SCRIPT}.</p>
+ *
+ * <p>The closest {@link Element} possible is created from the format.</p>
+ *
+ * @param size {@link Size}
+ * @param format An int format
+ * @param usage Usage flags
+ *
+ * @return A new {@link AllocationInfo} holding the given arguments.
+ *
+ * @throws NullPointerException if size was {@code null}.
+ *
+ * @see ImageFormat
+ * @see PixelFormat
+ */
+ public static AllocationInfo newInstance(Size size, int format, int usage) {
+ RenderScript rs = RenderScriptSingleton.getRS();
+
+ Element element;
+ switch (format) {
+ case ImageFormat.YUV_420_888:
+ element = Element.YUV(rs);
+ break;
+ case PixelFormat.RGBA_8888:
+ element = Element.RGBA_8888(rs);
+ break;
+ // TODO: map more formats here
+ default:
+ throw new UnsupportedOperationException("Unsupported format " + format);
+ }
+
+ return new AllocationInfo(element, size, usage);
+ }
+
+
+ /**
+ * Create a new {@link AllocationInfo} holding the specified format, {@link Size},
+ * with the default usage.
+ *
+ * <p>The default usage is always {@link Allocation#USAGE_SCRIPT}.</p>
+ *
+ * <p>The closest {@link Element} possible is created from the format.</p>
+ *
+ * @param size {@link Size}
+ * @param format An int format
+ *
+ * @return A new {@link AllocationInfo} holding the given arguments.
+ *
+ * @throws NullPointerException if size was {@code null}.
+ *
+ * @see ImageFormat
+ * @see PixelFormat
+ */
+ public static AllocationInfo newInstance(Size size, int format) {
+ return newInstance(size, format, Allocation.USAGE_SCRIPT);
+ }
+
+ /**
+ * Create a new {@link AllocationInfo} holding the specified {@link Element}, {@link Size},
+ * with the default usage.
+ *
+ * <p>The default usage is always {@link Allocation#USAGE_SCRIPT}.</p>
+ *
+ * @param element {@link Element}
+ * @param size {@link Size}
+ *
+ * @return A new {@link AllocationInfo} holding the given arguments.
+ *
+ * @throws NullPointerException if size was {@code null}.
+ * @throws NullPointerException if element was {@code null}.
+ */
+ public static AllocationInfo newInstance(Element element, Size size) {
+ return new AllocationInfo(element, size, Allocation.USAGE_SCRIPT);
+ }
+
+ /**
+ * Create a new {@link AllocationInfo} holding the specified {@link Element}, {@link Size},
+ * and {@link Allocation#USAGE_SCRIPT usage}.
+ *
+ * <p>The usage is always ORd with {@link Allocation#USAGE_SCRIPT}.</p>
+ *
+ * @param element {@link Element}
+ * @param size {@link Size}
+ * @param usage usage flags
+ *
+ * @return A new {@link AllocationInfo} holding the given arguments.
+ *
+ * @throws NullPointerException if size was {@code null}.
+ * @throws NullPointerException if element was {@code null}.
+ */
+ public static AllocationInfo newInstance(Element element, Size size, int usage) {
+ return new AllocationInfo(element, size, usage);
+ }
+
+ /**
+ * Create a new {@link AllocationInfo} by copying the existing data but appending
+ * the new usage flags to the old usage flags.
+ *
+ * @param usage usage flags
+ *
+ * @return A new {@link AllocationInfo} with new usage flags ORd to the old ones.
+ */
+ public AllocationInfo addExtraUsage(int usage) {
+ return new AllocationInfo(mElement, mSize, mUsage | usage);
+ }
+
+ /**
+ * Create a new {@link AllocationInfo} by copying the existing data but changing the format,
+ * and appending the new usage flags to the old usage flags.
+ *
+ * @param format Format
+ * @param usage usage flags
+ *
+ * @return A new {@link AllocationInfo} with new format/usage.
+ *
+ * @see ImageFormat
+ * @see PixelFormat
+ */
+ public AllocationInfo changeFormatAndUsage(int format, int usage) {
+ return newInstance(getSize(), format, usage);
+ }
+
+ /**
+ * Create a new {@link AllocationInfo} by copying the existing data but replacing the old
+ * usage with the new usage flags.
+ *
+ * @param usage usage flags
+ *
+ * @return A new {@link AllocationInfo} with new format/usage.
+ *
+ * @see ImageFormat
+ * @see PixelFormat
+ */
+ public AllocationInfo changeElementWithDefaultUsage(Element element) {
+ return newInstance(element, getSize());
+ }
+
+ /**
+ * Create a new {@link AllocationInfo} by copying the existing data but changing the format,
+ * and replacing the old usage flags with default usage flags.
+ *
+ * @param format Format
+ *
+ * @return A new {@link AllocationInfo} with new format/usage.
+ *
+ * @see ImageFormat
+ * @see PixelFormat
+ */
+ public AllocationInfo changeFormatWithDefaultUsage(int format) {
+ return newInstance(getSize(), format, Allocation.USAGE_SCRIPT);
+ }
+
+ private AllocationInfo(Element element, Size size, int usage) {
+ checkNotNull("element", element);
+ checkNotNull("size", size);
+
+ mElement = element;
+ mSize = size;
+ mUsage = usage;
+
+ Type.Builder typeBuilder = typeBuilder(element, size);
+
+ if (element.equals(Element.YUV(mRS))) {
+ typeBuilder.setYuvFormat(ImageFormat.YUV_420_888);
+ }
+
+ mType = typeBuilder.create();
+ }
+
+ /**
+ * Get the {@link Type type} for this info.
+ *
+ * <p>Note that this is the same type that would get used by the {@link Allocation}
+ * created with {@link #createAllocation()}.
+ *
+ * @return The type (never {@code null}).
+ */
+ public Type getType() {
+ return mType;
+ }
+
+ /**
+ * Get the usage.
+ *
+ * <p>The bit for {@link Allocation#USAGE_SCRIPT} will always be set to 1.</p>
+ *
+ * @return usage flags
+ */
+ public int getUsage() {
+ return mUsage;
+ }
+
+ /**
+ * Get the size.
+ *
+ * @return The size (never {@code null}).
+ */
+ public Size getSize() {
+ return mSize;
+ }
+
+ /**
+ * Get the {@link Element}.
+ *
+ * @return The element (never {@code null}).
+ */
+ public Element getElement() {
+ return mElement;
+ }
+
+ /**
+ * Convenience enum to represent commonly-used elements without needing a RenderScript object.
+ */
+ public enum ElementInfo {
+ YUV,
+ RGBA_8888,
+ U8_3,
+ U8_4;
+
+ private static final String TAG = "ElementInfo";
+
+ /**
+ * Create an {@link ElementInfo} by converting it from a {@link Element}.
+ *
+ * @param element The element for which you want to get an enum for.
+ *
+ * @return The element info is a corresponding one exists, or {@code null} otherwise.
+ */
+ public static ElementInfo fromElement(Element element) {
+ checkNotNull("element", element);
+
+ if (element.equals(Element.YUV(RenderScriptSingleton.getRS()))) {
+ return YUV;
+ } else if (element.equals(Element.RGBA_8888(RenderScriptSingleton.getRS()))) {
+ return RGBA_8888;
+ } else if (element.equals(Element.U8_3(RenderScriptSingleton.getRS()))) {
+ return U8_3;
+ } else if (element.equals(Element.U8_4(RenderScriptSingleton.getRS()))) {
+ return U8_4;
+ }
+ // TODO: add more comparisons here as necessary
+
+ Log.w(TAG, "Unknown element of data kind " + element.getDataKind());
+ return null;
+ }
+ }
+
+ /**
+ * Compare the current element against the suggested element (info).
+ *
+ * @param element The other element to compare against.
+ *
+ * @return true if the elements are equal, false otherwise.
+ */
+ public boolean isElementEqualTo(ElementInfo element) {
+ checkNotNull("element", element);
+
+ Element comparison;
+ switch (element) {
+ case YUV:
+ comparison = Element.YUV(mRS);
+ break;
+ case RGBA_8888:
+ comparison = Element.RGBA_8888(mRS);
+ break;
+ case U8_3:
+ comparison = Element.U8_3(mRS);
+ break;
+ case U8_4:
+ comparison = Element.U8_4(mRS);
+ break;
+ default:
+ // TODO: add more comparisons here as necessary
+ comparison = null;
+ }
+
+ return mElement.equals(comparison);
+ }
+
+ /**
+ * Human-readable representation of this info.
+ */
+ @Override
+ public String toString() {
+ return String.format("Size: %s, Element: %s, Usage: %x", mSize,
+ ElementInfo.fromElement(mElement), mUsage);
+ }
+
+ /**
+ * Compare against another object.
+ *
+ * <p>Comparisons against objects that are not instances of {@link AllocationInfo}
+ * always return {@code false}.</p>
+ *
+ * <p>Two {@link AllocationInfo infos} are considered equal only if their elements,
+ * sizes, and usage flags are also equal.</p>
+ *
+ * @param other Another info object
+ *
+ * @return true if this is equal to other
+ */
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof AllocationInfo) {
+ return equals((AllocationInfo)other);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Compare against another object.
+ *
+ * <p>Two {@link AllocationInfo infos} are considered equal only if their elements,
+ * sizes, and usage flags are also equal.</p>
+ *
+ * @param other Another info object
+ *
+ * @return true if this is equal to other
+ */
+ public boolean equals(AllocationInfo other) {
+ if (other == null) {
+ return false;
+ }
+
+ // Element, Size equality is already incorporated into Type equality
+ return mType.equals(other.mType) && mUsage == other.mUsage;
+ }
+
+ /**
+ * Create a new {@link Allocation} using the {@link #getType type} and {@link #getUsage usage}
+ * from this info object.
+ *
+ * <p>The allocation is always created from a {@link AllocationCache cache}. If possible,
+ * return it to the cache once done (although this is not necessary).</p>
+ *
+ * @return a new {@link Allocation}
+ */
+ public Allocation createAllocation() {
+ if (VERBOSE) Log.v(TAG, "createAllocation - for info =" + toString());
+ return RenderScriptSingleton.getCache().getOrCreateTyped(mType, mUsage);
+ }
+
+ /**
+ * Create a new {@link Allocation} using the {@link #getType type} and {@link #getUsage usage}
+ * from this info object; immediately wrap inside a new {@link BlockingInputAllocation}.
+ *
+ * <p>The allocation is always created from a {@link AllocationCache cache}. If possible,
+ * return it to the cache once done (although this is not necessary).</p>
+ *
+ * @return a new {@link Allocation}
+ *
+ * @throws IllegalArgumentException
+ * If the usage did not have one of {@code USAGE_IO_INPUT} or {@code USAGE_IO_OUTPUT}
+ */
+ public BlockingInputAllocation createBlockingInputAllocation() {
+ Allocation alloc = createAllocation();
+ return BlockingInputAllocation.wrap(alloc);
+ }
+
+ private static Type.Builder typeBuilder(Element element, Size size) {
+ Type.Builder builder = (new Type.Builder(RenderScriptSingleton.getRS(), element))
+ .setX(size.getWidth())
+ .setY(size.getHeight());
+
+ return builder;
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/rs/BlockingInputAllocation.java b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/BlockingInputAllocation.java
new file mode 100644
index 0000000..0305540
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/BlockingInputAllocation.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.camera2.cts.rs;
+
+import static android.hardware.camera2.cts.helpers.Preconditions.*;
+
+import android.hardware.camera2.cts.helpers.UncheckedCloseable;
+import android.renderscript.Allocation;
+import android.util.Log;
+
+import com.android.ex.camera2.exceptions.TimeoutRuntimeException;
+
+/**
+ * An {@link Allocation} wrapper that can be used to block until new buffers are available.
+ *
+ * <p>Can only be used only with {@link Allocation#USAGE_IO_INPUT} usage Allocations.</p>
+ *
+ * <p>When used with a {@link android.hardware.camera2.CameraDevice CameraDevice} this
+ * must be used as an output surface.</p>
+ */
+class BlockingInputAllocation implements UncheckedCloseable {
+
+ private static final String TAG = BlockingInputAllocation.class.getSimpleName();
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+ private final Allocation mAllocation;
+ private final OnBufferAvailableListener mListener;
+ private boolean mClosed;
+
+ /**
+ * Wrap an existing Allocation with this {@link BlockingInputAllocation}.
+ *
+ * <p>Doing this will clear any existing associated buffer listeners and replace
+ * it with a new one.</p>
+ *
+ * @param allocation A non-{@code null} {@link Allocation allocation}
+ * @return a new {@link BlockingInputAllocation} instance
+ *
+ * @throws NullPointerException
+ * If {@code allocation} was {@code null}
+ * @throws IllegalArgumentException
+ * If {@code allocation}'s usage did not have one of USAGE_IO_INPUT or USAGE_IO_OUTPUT
+ * @throws IllegalStateException
+ * If this object has already been {@link #close closed}
+ */
+ public static BlockingInputAllocation wrap(Allocation allocation) {
+ checkNotNull("allocation", allocation);
+ checkBitFlags("usage", allocation.getUsage(), "USAGE_IO_INPUT", Allocation.USAGE_IO_INPUT);
+
+ return new BlockingInputAllocation(allocation);
+ }
+
+ /**
+ * Get the Allocation backing this {@link BlockingInputAllocation}.
+ *
+ * @return Allocation instance (non-{@code null}).
+ *
+ * @throws IllegalStateException If this object has already been {@link #close closed}
+ */
+ public Allocation getAllocation() {
+ checkNotClosed();
+
+ return mAllocation;
+ }
+
+ /**
+ * Waits for a buffer to become available, then immediately
+ * {@link Allocation#ioReceive receives} it.
+ *
+ * <p>After calling this, the next script used with this allocation will use the
+ * newer buffer.</p>
+ *
+ * @throws TimeoutRuntimeException If waiting for the buffer has timed out.
+ * @throws IllegalStateException If this object has already been {@link #close closed}
+ */
+ public synchronized void waitForBufferAndReceive() {
+ checkNotClosed();
+
+ if (VERBOSE) Log.v(TAG, "waitForBufferAndReceive - begin");
+
+ mListener.waitForBuffer();
+ mAllocation.ioReceive();
+
+ if (VERBOSE) Log.v(TAG, "waitForBufferAndReceive - Allocation#ioReceive");
+ }
+
+ /**
+ * If there are multiple pending buffers, {@link Allocation#ioReceive receive} the latest one.
+ *
+ * <p>Does not block if there are no currently pending buffers.</p>
+ *
+ * @return {@code true} only if any buffers were received.
+ *
+ * @throws IllegalStateException If this object has already been {@link #close closed}
+ */
+ public synchronized boolean receiveLatestAvailableBuffers() {
+ checkNotClosed();
+
+ int updatedBuffers = 0;
+ while (mListener.isBufferPending()) {
+ mListener.waitForBuffer();
+ mAllocation.ioReceive();
+ updatedBuffers++;
+ }
+
+ if (VERBOSE) Log.v(TAG, "receiveLatestAvailableBuffers - updated = " + updatedBuffers);
+
+ return updatedBuffers > 0;
+ }
+
+ /**
+ * Closes the object and detaches the listener from the {@link Allocation}.
+ *
+ * <p>This has a side effect of calling {@link #receiveLatestAvailableBuffers}
+ *
+ * <p>Does <i>not</i> destroy the underlying {@link Allocation}.</p>
+ */
+ @Override
+ public synchronized void close() {
+ if (mClosed) return;
+
+ receiveLatestAvailableBuffers();
+ mAllocation.setOnBufferAvailableListener(/*callback*/null);
+ mClosed = true;
+ }
+
+ protected void checkNotClosed() {
+ if (mClosed) {
+ throw new IllegalStateException(TAG + " has been closed");
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ close();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ private BlockingInputAllocation(Allocation allocation) {
+ mAllocation = allocation;
+
+ mListener = new OnBufferAvailableListener();
+ mAllocation.setOnBufferAvailableListener(mListener);
+ }
+
+ // TODO: refactor with the ImageReader Listener code to use a LinkedBlockingQueue
+ private static class OnBufferAvailableListener implements Allocation.OnBufferAvailableListener {
+ private int mPendingBuffers = 0;
+ private final Object mBufferSyncObject = new Object();
+ private static final int TIMEOUT_MS = 5000;
+
+ public boolean isBufferPending() {
+ synchronized (mBufferSyncObject) {
+ return (mPendingBuffers > 0);
+ }
+ }
+
+ /**
+ * Waits for a buffer. Caller must call ioReceive exactly once after calling this.
+ *
+ * @throws TimeoutRuntimeException If waiting for the buffer has timed out.
+ */
+ public void waitForBuffer() {
+ synchronized (mBufferSyncObject) {
+ while (mPendingBuffers == 0) {
+ try {
+ if (VERBOSE) Log.v(TAG, "waiting for next buffer");
+ mBufferSyncObject.wait(TIMEOUT_MS);
+ if (mPendingBuffers == 0) {
+ throw new TimeoutRuntimeException("wait for buffer image timed out");
+ }
+ } catch (InterruptedException ie) {
+ throw new AssertionError(ie);
+ }
+ }
+ mPendingBuffers--;
+ }
+ }
+
+ @Override
+ public void onBufferAvailable(Allocation a) {
+ if (VERBOSE) Log.v(TAG, "new buffer in allocation available");
+ synchronized (mBufferSyncObject) {
+ mPendingBuffers++;
+ mBufferSyncObject.notifyAll();
+ }
+ }
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/rs/RenderScriptSingleton.java b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/RenderScriptSingleton.java
new file mode 100644
index 0000000..8e4c8e9
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/RenderScriptSingleton.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.camera2.cts.rs;
+
+import android.content.Context;
+import android.renderscript.RenderScript;
+import android.util.Log;
+
+// TODO : Replace with dependency injection
+/**
+ * Singleton to hold {@link RenderScript} and {@link AllocationCache} objects.
+ *
+ * <p>The test method must call {@link #setContext} before attempting to retrieve
+ * the underlying objects.</p> *
+ */
+public class RenderScriptSingleton {
+
+ private static final String TAG = "RenderScriptSingleton";
+
+ private static Context sContext;
+ private static RenderScript sRS;
+ private static AllocationCache sCache;
+
+ /**
+ * Initialize the singletons from the given context; the
+ * {@link RenderScript} and {@link AllocationCache} objects are instantiated.
+ *
+ * @param context a non-{@code null} Context.
+ *
+ * @throws IllegalStateException If this was called repeatedly without {@link #clearContext}
+ */
+ public static synchronized void setContext(Context context) {
+ if (context.equals(sContext)) {
+ return;
+ } else if (sContext != null) {
+ Log.v(TAG,
+ "Trying to set new context " + context +
+ ", before clearing previous "+ sContext);
+ throw new IllegalStateException(
+ "Call #clearContext before trying to set a new context");
+ }
+
+ sRS = RenderScript.create(context);
+ sContext = context;
+ sCache = new AllocationCache(sRS);
+ }
+
+ /**
+ * Clean up the singletons from the given context; the
+ * {@link RenderScript} and {@link AllocationCache} objects are destroyed.
+ *
+ * <p>Safe to call multiple times; subsequent invocations have no effect.</p>
+ */
+ public static synchronized void clearContext() {
+ if (sContext != null) {
+ sCache.close();
+ sCache = null;
+
+ sRS.destroy();
+ sRS = null;
+ sContext = null;
+ }
+ }
+
+ /**
+ * Get the current {@link RenderScript} singleton.
+ *
+ * @return A non-{@code null} {@link RenderScript} object.
+ *
+ * @throws IllegalStateException if {@link #setContext} was not called prior to this
+ */
+ public static synchronized RenderScript getRS() {
+ if (sRS == null) {
+ throw new IllegalStateException("Call #setContext before using #get");
+ }
+
+ return sRS;
+ }
+ /**
+ * Get the current {@link AllocationCache} singleton.
+ *
+ * @return A non-{@code null} {@link AllocationCache} object.
+ *
+ * @throws IllegalStateException if {@link #setContext} was not called prior to this
+ */
+ public static synchronized AllocationCache getCache() {
+ if (sCache == null) {
+ throw new IllegalStateException("Call #setContext before using #getCache");
+ }
+
+ return sCache;
+ }
+
+ // Suppress default constructor for noninstantiability
+ private RenderScriptSingleton() { throw new AssertionError(); }
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/rs/Script.java b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/Script.java
new file mode 100644
index 0000000..92ff1cb
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/Script.java
@@ -0,0 +1,368 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.camera2.cts.rs;
+
+import static android.hardware.camera2.cts.helpers.Preconditions.*;
+
+import android.hardware.camera2.cts.helpers.UncheckedCloseable;
+import android.renderscript.Allocation;
+import android.renderscript.RenderScript;
+import android.util.Log;
+
+import java.util.HashMap;
+
+/**
+ * Base class for all renderscript script abstractions.
+ *
+ * <p>Each script has exactly one input and one output allocation, and is able to execute
+ * one {@link android.renderscript.Script} script file.</p>
+ *
+ * <p>Each script owns it's input allocation, but not the output allocation.</p>
+ *
+ * <p>Subclasses of this class must implement exactly one of two constructors:
+ * <ul>
+ * <li>{@code ScriptSubclass(AllocationInfo inputInfo)}
+ * - if it expects 0 parameters
+ * <li>{@code ScriptSubclass(AllocationInfo inputInfo, ParameterMap<T> parameterMap))}
+ * - if it expects 1 or more parameters
+ * </ul>
+ *
+ * @param <T> A concrete subclass of {@link android.renderscript.Script}
+ */
+public abstract class Script<T extends android.renderscript.Script> implements UncheckedCloseable {
+
+ /**
+ * A type-safe heterogenous parameter map for script parameters.
+ *
+ * @param <ScriptT> A concrete subclass of {@link Script}.
+ */
+ public static class ParameterMap<ScriptT extends Script<?>> {
+ private final HashMap<Script.ScriptParameter<ScriptT, ?>, Object> mParameterMap =
+ new HashMap<Script.ScriptParameter<ScriptT, ?>, Object>();
+
+ /**
+ * Create a new parameter map with 0 parameters.</p>
+ */
+ public ParameterMap() {}
+
+ /**
+ * Get the value associated with the given parameter key.
+ *
+ * @param parameter A type-safe key corresponding to a parameter.
+ *
+ * @return The value, or {@code null} if none was set.
+ *
+ * @param <T> The type of the value
+ *
+ * @throws NullPointerException if parameter was {@code null}
+ */
+ @SuppressWarnings("unchecked")
+ public <T> T get(Script.ScriptParameter<ScriptT, T> parameter) {
+ checkNotNull("parameter", parameter);
+
+ return (T) mParameterMap.get(parameter);
+ }
+
+ /**
+ * Sets the value associated with the given parameter key.
+ *
+ * @param parameter A type-safe key corresponding to a parameter.
+ * @param value The value
+ *
+ * @param <T> The type of the value
+ *
+ * @throws NullPointerException if parameter was {@code null}
+ * @throws NullPointerException if value was {@code null}
+ */
+ public <T> void set(Script.ScriptParameter<ScriptT, T> parameter, T value) {
+ checkNotNull("parameter", parameter);
+ checkNotNull("value", value);
+
+ if (!parameter.getValueClass().isInstance(value)) {
+ throw new IllegalArgumentException(
+ "Runtime type mismatch between " + parameter + " and value " + value);
+ }
+
+ mParameterMap.put(parameter, value);
+ }
+
+ /**
+ * Whether or not at least one parameter has been {@link #set}.
+ *
+ * @return true if there is at least one element in the map
+ */
+ public boolean isEmpty() {
+ return mParameterMap.isEmpty();
+ }
+
+ /**
+ * Check if the parameter has been {@link #set} to a value.
+ *
+ * @param parameter A type-safe key corresponding to a parameter.
+ * @return true if there is a value corresponding to this parameter, false otherwise.
+ */
+ public boolean contains(Script.ScriptParameter<ScriptT, ?> parameter) {
+ checkNotNull("parameter", parameter);
+
+ return mParameterMap.containsKey(parameter);
+ }
+ }
+
+ /**
+ * A type-safe parameter key to be used with {@link ParameterMap}.
+ *
+ * @param <J> A concrete subclass of {@link Script}.
+ * @param <K> The type of the value that the parameter holds.
+ */
+ public static class ScriptParameter<J extends Script<?>, K> {
+ private final Class<J> mScriptClass;
+ private final Class<K> mValueClass;
+
+ ScriptParameter(Class<J> jClass, Class<K> kClass) {
+ checkNotNull("jClass", jClass);
+ checkNotNull("kClass", kClass);
+
+ mScriptClass = jClass;
+ mValueClass = kClass;
+ }
+
+ /**
+ * Get the runtime class associated with the value.
+ */
+ public Class<K> getValueClass() {
+ return mValueClass;
+ }
+
+ /**
+ * Compare with another object.
+ *
+ * <p>Two script parameters are considered equal only if their script class and value
+ * class are both equal.</p>
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof ScriptParameter) {
+ ScriptParameter<J, K> otherParam = (ScriptParameter<J,K>) other;
+
+ return mScriptClass.equals(otherParam.mScriptClass) &&
+ mValueClass.equals(otherParam.mValueClass);
+ }
+
+ return false;
+ }
+
+ /**
+ * Gets the hash code for this object.
+ */
+ @Override
+ public int hashCode() {
+ return mScriptClass.hashCode() ^ mValueClass.hashCode();
+ }
+ }
+
+ private static final String TAG = "Script";
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+ protected final AllocationCache mCache = RenderScriptSingleton.getCache();
+ protected final RenderScript mRS = RenderScriptSingleton.getRS();
+
+ protected final AllocationInfo mInputInfo;
+ protected final AllocationInfo mOutputInfo;
+
+ protected Allocation mOutputAllocation;
+ protected Allocation mInputAllocation;
+
+ protected final T mScript;
+ private boolean mClosed = false;
+
+ /**
+ * Gets the {@link AllocationInfo info} associated with this script's input.
+ *
+ * @return A non-{@code null} {@link AllocationInfo} object.
+ *
+ * @throws IllegalStateException If the script has already been {@link #close closed}.
+ */
+ public AllocationInfo getInputInfo() {
+ checkNotClosed();
+
+ return mInputInfo;
+ }
+ /**
+ * Gets the {@link AllocationInfo info} associated with this script's output.
+ *
+ * @return A non-{@code null} {@link AllocationInfo} object.
+ *
+ * @throws IllegalStateException If the script has already been {@link #close closed}.
+ */
+ public AllocationInfo getOutputInfo() {
+ checkNotClosed();
+
+ return mOutputInfo;
+ }
+
+ /**
+ * Set the input.
+ *
+ * <p>Must be called before executing any scripts.</p>
+ *
+ * @throws IllegalStateException If the script has already been {@link #close closed}.
+ */
+ void setInput(Allocation allocation) {
+ checkNotClosed();
+ checkNotNull("allocation", allocation);
+ checkEquals("allocation info", AllocationInfo.newInstance(allocation),
+ "input info", mInputInfo);
+
+ // Scripts own the input, so return old input to cache if the input changes
+ if (mInputAllocation != allocation) {
+ mCache.returnToCacheIfNotNull(mInputAllocation);
+ }
+
+ mInputAllocation = allocation;
+ updateScriptInput();
+ }
+
+ protected abstract void updateScriptInput();
+
+ /**
+ * Set the output.
+ *
+ * <p>Must be called before executing any scripts.</p>
+ *
+ * @throws IllegalStateException If the script has already been {@link #close closed}.
+ */
+ void setOutput(Allocation allocation) {
+ checkNotClosed();
+ checkNotNull("allocation", allocation);
+ checkEquals("allocation info", AllocationInfo.newInstance(allocation),
+ "output info", mOutputInfo);
+
+ // Scripts do not own the output, simply set a reference to the new one.
+ mOutputAllocation = allocation;
+ }
+
+ protected Script(AllocationInfo inputInfo, AllocationInfo outputInfo, T rsScript) {
+ checkNotNull("inputInfo", inputInfo);
+ checkNotNull("outputInfo", outputInfo);
+ checkNotNull("rsScript", rsScript);
+
+ mInputInfo = inputInfo;
+ mOutputInfo = outputInfo;
+ mScript = rsScript;
+
+ if (VERBOSE) {
+ Log.v(TAG, String.format("%s - inputInfo = %s, outputInfo = %s, rsScript = %s",
+ getName(), inputInfo, outputInfo, rsScript));
+ }
+ }
+
+ /**
+ * Get the {@link Allocation} associated with this script's input.</p>
+ *
+ * @return The input {@link Allocation}, which is never {@code null}.
+ *
+ * @throws IllegalStateException If the script has already been {@link #close closed}.
+ */
+ public Allocation getInput() {
+ checkNotClosed();
+
+ return mInputAllocation;
+ }
+ /**
+ * Get the {@link Allocation} associated with this script's output.</p>
+ *
+ * @return The output {@link Allocation}, which is never {@code null}.
+ *
+ * @throws IllegalStateException If the script has already been {@link #close closed}.
+ */
+ public Allocation getOutput() {
+ checkNotClosed();
+
+ return mOutputAllocation;
+ }
+
+ /**
+ * Execute the script's kernel against the input/output {@link Allocation allocations}.
+ *
+ * <p>Once this is complete, the output will have the new data available (for either
+ * the next script, or to read out with a copy).</p>
+ *
+ * @throws IllegalStateException If the script has already been {@link #close closed}.
+ */
+ public void execute() {
+ checkNotClosed();
+
+ if (mInputAllocation == null || mOutputAllocation == null) {
+ throw new IllegalStateException("Both inputs and outputs must have been set");
+ }
+
+ executeUnchecked();
+ }
+
+ /**
+ * Get the name of this script.
+ *
+ * <p>The name is the short hand name of the concrete class backing this script.</p>
+ *
+ * <p>This method works even if the script has already been {@link #close closed}.</p>
+ *
+ * @return A string representing the script name.
+ */
+ public String getName() {
+ return getClass().getSimpleName();
+ }
+
+ protected abstract void executeUnchecked();
+
+ protected void checkNotClosed() {
+ if (mClosed) {
+ throw new IllegalStateException("Script has been closed");
+ }
+ }
+
+ /**
+ * Destroy the underlying script object and return the input allocation back to the
+ * {@link AllocationCache cache}.
+ *
+ * <p>This method has no effect if called more than once.</p>
+ */
+ @Override
+ public void close() {
+ if (mClosed) return;
+
+ // Scripts own the input allocation. They do NOT own outputs.
+ mCache.returnToCacheIfNotNull(mInputAllocation);
+
+ mScript.destroy();
+
+ mClosed = true;
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ close();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ protected static RenderScript getRS() {
+ return RenderScriptSingleton.getRS();
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/rs/ScriptGraph.java b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/ScriptGraph.java
new file mode 100644
index 0000000..9129a6d
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/ScriptGraph.java
@@ -0,0 +1,656 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.camera2.cts.rs;
+
+import static android.hardware.camera2.cts.helpers.Preconditions.*;
+import static junit.framework.Assert.*;
+
+import android.graphics.ImageFormat;
+import android.graphics.PixelFormat;
+import android.hardware.camera2.Size;
+import android.hardware.camera2.cts.helpers.MaybeNull;
+import android.hardware.camera2.cts.helpers.UncheckedCloseable;
+import android.hardware.camera2.cts.rs.Script.ParameterMap;
+import android.renderscript.Allocation;
+import android.util.Log;
+import android.view.Surface;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.List;
+import com.android.ex.camera2.exceptions.TimeoutRuntimeException;
+
+/**
+ * An abstraction to simplify chaining together the execution of multiple RenderScript
+ * {@link android.renderscript.Script scripts} and managing their {@link Allocation allocations}.
+ *
+ * <p>Create a new script graph by using {@link #create}, configure the input with
+ * {@link Builder#configureInput}, then configure one or more scripts with
+ * {@link Builder#configureScript} or {@link Builder#chainScript}. Finally, freeze the graph
+ * with {@link Builder#buildGraph}.</p>
+ *
+ * <p>Once a script graph has been built, all underlying scripts and allocations are instantiated.
+ * Each script may be executed with {@link #execute}. Scripts are executed in the order that they
+ * were configured, with each previous script's output used as the input for the next script.
+ * </p>
+ *
+ * <p>In case the input {@link Allocation} is actually backed by a {@link Surface}, convenience
+ * methods ({@link #advanceInputWaiting} and {@link #advanceInputAndDrop} are provided to
+ * automatically update the {@link Allocation allocation} with the latest buffer available.</p>
+ *
+ * <p>All resources are managed by the {@link ScriptGraph} and {@link #close closing} the graph
+ * will release all underlying resources. See {@link #close} for more details.</p>
+ */
+public class ScriptGraph implements UncheckedCloseable {
+
+ private static final String TAG = "ScriptGraph";
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+ private static final int INPUT_SCRIPT_LOCATION = 0;
+ private final int OUTPUT_SCRIPT_LOCATION; // calculated in constructor
+
+ private final AllocationCache mCache = RenderScriptSingleton.getCache();
+
+ private final Size mSize;
+ private final int mFormat;
+ private final int mUsage;
+ private final List<Script<?>> mScripts;
+
+ private final BlockingInputAllocation mInputBlocker;
+ private final Allocation mOutputAllocation;
+ private boolean mClosed = false;
+
+ /**
+ * Create a new {@link Builder} that will be used to configure the graph's inputs
+ * and scripts (and parameters).
+ *
+ * <p>Once a graph has been fully built, the configuration is immutable.</p>
+ *
+ * @return a {@link Builder} that will be used to configure the graph settings
+ */
+ public static Builder create() {
+ return new Builder();
+ }
+
+ /**
+ * Wait until another buffer is produced into the input {@link Surface}, then
+ * update the backing input {@link Allocation} with the latest buffer with
+ * {@link Allocation#ioReceive ioReceive}.
+ *
+ * @throws IllegalArgumentException
+ * if the graph wasn't configured with
+ * {@link Builder#configureInputWithSurface configureInputWithSurface}
+ * @throws TimeoutRuntimeException
+ * if waiting for the buffer times out
+ */
+ public void advanceInputWaiting() {
+ checkNotClosed();
+ if (!isInputFromSurface()) {
+ throw new IllegalArgumentException("Graph was not configured with USAGE_IO_INPUT");
+ }
+
+ mInputBlocker.waitForBufferAndReceive();
+ }
+
+ /**
+ * Update the backing input {@link Allocation} with the latest buffer with
+ * {@link Allocation#ioReceive ioReceive} repeatedly until no more buffers are pending.
+ *
+ * <p>Does not wait for new buffers to become available if none are currently available
+ * (i.e. {@code false} is returned immediately).</p>
+ *
+ * @return true if any buffers were pending
+ *
+ * @throws IllegalArgumentException
+ * if the graph wasn't configured with
+ * {@link Builder#configureInputWithSurface configureInputWithSurface}
+ * @throws TimeoutRuntimeException
+ * if waiting for the buffer times out
+ */
+ public boolean advanceInputAndDrop() {
+ checkNotClosed();
+ if (!isInputFromSurface()) {
+ throw new IllegalArgumentException("Graph was not configured with USAGE_IO_INPUT");
+ }
+
+ return mInputBlocker.receiveLatestAvailableBuffers();
+ }
+
+ /**
+ * Execute each script in the graph, with each next script's input using the
+ * previous script's output.
+ *
+ * <p>Scripts are executed in the same order that they were configured by the {@link Builder}.
+ * </p>
+ *
+ * @throws IllegalStateException if the graph was already {@link #close closed}
+ */
+ public void execute() {
+ checkNotClosed();
+
+ // TODO: Can we use android.renderscript.ScriptGroup here to make it faster?
+
+ int i = 0;
+ for (Script<?> script : mScripts) {
+ script.execute();
+ i++;
+ }
+
+ if (VERBOSE) Log.v(TAG, "execute - invoked " + i + " scripts");
+ }
+
+ /**
+ * Copies the data from the last script's output {@link Allocation} into a byte array.
+ *
+ * <p>The output allocation must be of an 8 bit integer
+ * {@link android.renderscript.Element Element} type.</p>
+ *
+ * @return A byte[] copy.
+ *
+ * @throws IllegalStateException if the graph was already {@link #close closed}
+ */
+ public byte[] getOutputData() {
+ checkNotClosed();
+
+ Allocation outputAllocation = getOutputAllocation();
+
+ byte[] destination = new byte[outputAllocation.getBytesSize()];
+ outputAllocation.copyTo(destination);
+
+ return destination;
+ }
+
+ /**
+ * Copies the data from the first script's input {@link Allocation} into a byte array.
+ *
+ * <p>The input allocation must be of an 8 bit integer
+ * {@link android.renderscript.Element Element} type.</p>
+ *
+ * @return A byte[] copy.
+ *
+ * @throws IllegalStateException if the graph was already {@link #close closed}
+ */
+ public byte[] getInputData() {
+ checkNotClosed();
+
+ Allocation inputAllocation = getInputAllocation();
+
+ byte[] destination = new byte[inputAllocation.getBytesSize()];
+ inputAllocation.copyTo(destination);
+
+ return destination;
+ }
+
+ /**
+ * Builds a {@link ScriptGraph} by configuring input size/format/usage,
+ * the script classes in the graph, and the parameters passed to the scripts.
+ *
+ * @see ScriptGraph#create
+ */
+ public static class Builder {
+
+ private Size mSize;
+ private int mFormat;
+ private int mUsage;
+
+ private final List<ScriptBuilder<? extends Script<?>>> mChainedScriptBuilders =
+ new ArrayList<ScriptBuilder<? extends Script<?>>>();
+
+ /**
+ * Configure the {@link Allocation} that will be used as the input to the first
+ * script, using the default usage.
+ *
+ * <p>Short hand for calling {@link #configureInput(int, int, int, int)} with a
+ * {@code 0} usage.</p>
+ *
+ * @param width Width in pixels
+ * @param height Height in pixels
+ * @param format Format from {@link ImageFormat} or {@link PixelFormat}
+ *
+ * @return The current builder ({@code this}). Use for chaining method calls.
+ */
+ public Builder configureInput(int width, int height, int format) {
+ return configureInput(new Size(width, height), format, /*usage*/0);
+ }
+
+ /**
+ * Configure the {@link Allocation} that will be used as the input to the first
+ * script.
+ *
+ * <p>The {@code usage} is always ORd together with {@link Allocation#USAGE_SCRIPT}.</p>
+ *
+ * @param width Width in pixels
+ * @param height Height in pixels
+ * @param format Format from {@link ImageFormat} or {@link PixelFormat}
+ * @param usage Usage flags such as {@link Allocation#USAGE_IO_INPUT}
+ *
+ * @return The current builder ({@code this}). Use for chaining method calls.
+ */
+ public Builder configureInput(int width, int height, int format, int usage) {
+ return configureInput(new Size(width, height), format, usage);
+ }
+
+ /**
+ * Configure the {@link Allocation} that will be used as the input to the first
+ * script, using the default usage.
+ *
+ * <p>Short hand for calling {@link #configureInput(Size, int, int)} with a
+ * {@code 0} usage.</p>
+ *
+ * @param size Size (width, height)
+ * @param format Format from {@link ImageFormat} or {@link PixelFormat}
+ *
+ * @return The current builder ({@code this}). Use for chaining method calls.
+ *
+ * @throws NullPointerException if size was {@code null}
+ */
+ public Builder configureInput(Size size, int format) {
+ return configureInput(size, format, /*usage*/0);
+ }
+
+ /**
+ * Configure the {@link Allocation} that will use a {@link Surface} to produce input into
+ * the first script.
+ *
+ * <p>Short hand for calling {@link #configureInput(Size, int, int)} with the
+ * {@link Allocation#USAGE_IO_INPUT} usage.</p>
+ *
+ * <p>The {@code usage} is always ORd together with {@link Allocation#USAGE_SCRIPT}.</p>
+ *
+ * @param size Size (width, height)
+ * @param format Format from {@link ImageFormat} or {@link PixelFormat}
+ *
+ * @return The current builder ({@code this}). Use for chaining method calls.
+ *
+ * @throws NullPointerException if size was {@code null}
+ */
+ public Builder configureInputWithSurface(Size size, int format) {
+ return configureInput(size, format, Allocation.USAGE_IO_INPUT);
+ }
+
+ /**
+ * Configure the {@link Allocation} that will be used as the input to the first
+ * script.
+ *
+ * <p>The {@code usage} is always ORd together with {@link Allocation#USAGE_SCRIPT}.</p>
+ *
+ * @param size Size (width, height)
+ * @param format Format from {@link ImageFormat} or {@link PixelFormat}
+ * @param usage Usage flags such as {@link Allocation#USAGE_IO_INPUT}
+ *
+ * @return The current builder ({@code this}). Use for chaining method calls.
+ *
+ * @throws NullPointerException if size was {@code null}
+ */
+ public Builder configureInput(Size size, int format, int usage) {
+ checkNotNull("size", size);
+
+ mSize = size;
+ mFormat = format;
+ mUsage = usage | Allocation.USAGE_SCRIPT;
+
+ return this;
+ }
+
+ /**
+ * Build a {@link Script} by setting parameters it might require for execution.
+ *
+ * <p>Refer to the documentation for {@code T} to see if there are any
+ * {@link Script.ScriptParameter parameters} in it.
+ * </p>
+ *
+ * @param <T> Concrete type subclassing the {@link Script} class.
+ */
+ public class ScriptBuilder<T extends Script<?>> {
+
+ private final Class<T> mScriptClass;
+
+ private ScriptBuilder(Class<T> scriptClass) {
+ mScriptClass = scriptClass;
+ }
+
+ private final ParameterMap<T> mParameterMap = new ParameterMap<T>();
+
+ /**
+ * Set a script parameter to the specified value.
+ *
+ * @param parameter The {@link Script.ScriptParameter parameter key} in {@code T}
+ * @param value A value of type {@code K} that the script expects.
+ * @param <K> The type of the parameter {@code value}.
+ *
+ * @return The current builder ({@code this}). Use to chain method calls.
+ *
+ * @throws NullPointerException if parameter was {@code null}
+ * @throws NullPointerException if value was {@code null}
+ * @throws IllegalStateException if the parameter was already {@link #set}
+ */
+ public <K> ScriptBuilder<T> set(Script.ScriptParameter<T, K> parameter, K value) {
+ checkNotNull("parameter", parameter);
+ checkNotNull("value", value);
+ checkState("Parameter has already been set", !mParameterMap.contains(parameter));
+
+ mParameterMap.set(parameter, value);
+
+ return this;
+ }
+
+ ParameterMap<T> getParameterMap() {
+ return mParameterMap;
+ }
+
+ Class<T> getScriptClass() {
+ return mScriptClass;
+ }
+
+ /**
+ * Build the script and freeze the parameter list to what was {@link #set}.
+ *
+ * @return
+ * The {@link ScriptGraph#Builder} that was used to configure
+ * {@link this} script.</p>
+ */
+ public Builder buildScript() {
+ mChainedScriptBuilders.add(this);
+
+ return Builder.this;
+ }
+ }
+
+ /**
+ * Configure the script with no parameters.
+ *
+ * <p>Short hand for invoking {@link #configureScript} immediately followed by
+ * {@link ScriptBuilder#buildScript()}.
+ *
+ * @param scriptClass A concrete class that subclasses {@link Script}
+ * @return The current builder ({@code this}). Use to chain method calls.
+ *
+ * @throws NullPointerException if {@code scriptClass} was {@code null}
+ */
+ public <T extends Script<?>> Builder chainScript(Class<T> scriptClass) {
+ checkNotNull("scriptClass", scriptClass);
+
+ return (new ScriptBuilder<T>(scriptClass)).buildScript();
+ }
+
+ /**
+ * Configure the script with parameters.
+ *
+ * <p>Only useful when the {@code scriptClass} has one or more
+ * {@link Script.ScriptParameter script parameters} defined.</p>
+ *
+ * @param scriptClass A concrete class that subclasses {@link Script}
+ * @return A script configuration {@link ScriptBuilder builder}. Use to chain method calls.
+ *
+ * @throws NullPointerException if {@code scriptClass} was {@code null}
+ */
+ public <T extends Script<?>> ScriptBuilder<T> configureScript(Class<T> scriptClass) {
+ checkNotNull("scriptClass", scriptClass);
+
+ return new ScriptBuilder<T>(scriptClass);
+ }
+
+ /**
+ * Finish configuring the graph and freeze the settings, instantiating all
+ * the {@link Script scripts} and {@link Allocation allocations}.
+ *
+ * @return A constructed {@link ScriptGraph}.
+ */
+ public ScriptGraph buildGraph() {
+ return new ScriptGraph(this);
+ }
+
+ private Builder() {}
+ }
+
+ private ScriptGraph(Builder builder) {
+ mSize = builder.mSize;
+ mFormat = builder.mFormat;
+ mUsage = builder.mUsage;
+ List<Builder.ScriptBuilder<? extends Script<?>>> chainedScriptBuilders =
+ builder.mChainedScriptBuilders;
+ mScripts = new ArrayList<Script<?>>(/*capacity*/chainedScriptBuilders.size());
+ OUTPUT_SCRIPT_LOCATION = chainedScriptBuilders.size() - 1;
+
+ if (mSize == null) {
+ throw new IllegalArgumentException("Inputs were not configured");
+ }
+
+ if (chainedScriptBuilders.isEmpty()) {
+ throw new IllegalArgumentException("At least one script should be chained");
+ }
+
+ /*
+ * The first input is special since it could be USAGE_IO_INPUT.
+ */
+ AllocationInfo inputInfo = AllocationInfo.newInstance(mSize, mFormat, mUsage);
+ Allocation inputAllocation;
+
+ // Create an Allocation with a Surface if the input to the graph requires it
+ if (isInputFromSurface()) {
+ mInputBlocker = inputInfo.createBlockingInputAllocation();
+ inputAllocation = mInputBlocker.getAllocation();
+ } else {
+ mInputBlocker = null;
+ inputAllocation = inputInfo.createAllocation();
+ }
+
+ if (VERBOSE) Log.v(TAG, "ScriptGraph() - Instantiating all script classes");
+
+ // Create all scripts.
+ for (Builder.ScriptBuilder<? extends Script<?>> scriptBuilder: chainedScriptBuilders) {
+
+ @SuppressWarnings("unchecked")
+ Class<Script<?>> scriptClass = (Class<Script<?>>) scriptBuilder.getScriptClass();
+
+ @SuppressWarnings("unchecked")
+ ParameterMap<Script<?>> parameters = (ParameterMap<Script<?>>)
+ scriptBuilder.getParameterMap();
+
+ Script<?> script = instantiateScript(scriptClass, inputInfo, parameters);
+ mScripts.add(script);
+
+ // The next script's input info is the current script's output info
+ inputInfo = script.getOutputInfo();
+ }
+
+ if (VERBOSE) Log.v(TAG, "ScriptGraph() - Creating all inputs");
+
+ // Create and wire up all inputs.
+ int i = 0;
+ Script<?> inputScript = mScripts.get(INPUT_SCRIPT_LOCATION);
+ do {
+ if (VERBOSE) {
+ Log.v(TAG, "ScriptGraph() - Setting input for script " + inputScript.getName());
+ }
+
+ inputScript.setInput(inputAllocation);
+
+ i++;
+
+ if (i >= mScripts.size()) {
+ break;
+ }
+
+ // Use the graph input for the first loop iteration
+ inputScript = mScripts.get(i);
+ inputInfo = inputScript.getInputInfo();
+ inputAllocation = inputInfo.createAllocation();
+ } while (true);
+
+ if (VERBOSE) Log.v(TAG, "ScriptGraph() - Creating all outputs");
+
+ // Create and wire up all outputs.
+ Allocation lastOutput = null;
+ for (i = 0; i < mScripts.size(); ++i) {
+ Script<?> script = mScripts.get(i);
+ Script<?> nextScript = (i + 1 < mScripts.size()) ? mScripts.get(i + 1) : null;
+
+ // Each script's output uses the next script's input.
+ // -- Since the last script has no next script, we own its output allocation.
+ lastOutput = (nextScript != null) ? nextScript.getInput()
+ : script.getOutputInfo().createAllocation();
+
+ if (VERBOSE) {
+ Log.v(TAG, "ScriptGraph() - Setting output for script " + script.getName());
+ }
+
+ script.setOutput(lastOutput);
+ }
+ mOutputAllocation = checkNotNull("lastOutput", lastOutput);
+
+ // Done. Safe to execute the graph now.
+
+ if (VERBOSE) Log.v(TAG, "ScriptGraph() - Graph has been built");
+ }
+
+ /**
+ * Construct the script by instantiating it via reflection.
+ *
+ * <p>The {@link Script scriptClass} should have a {@code Script(AllocationInfo inputInfo)}
+ * constructor if it expects an empty parameter map.</p>
+ *
+ * <p>If it expects a non-empty parameter map, it should have a
+ * {@code Script(AllocationInfo inputInfo, ParameterMap<T> parameterMap)} constructor.</p>
+ */
+ private static <T extends Script<?>> T instantiateScript(
+ Class<T> scriptClass, AllocationInfo inputInfo, ParameterMap<T> parameterMap) {
+
+ Constructor<T> ctor;
+ try {
+ // TODO: Would be better if we looked at the script class to see if it expects params
+ if (parameterMap.isEmpty()) {
+ // Script(AllocationInfo inputInfo)
+ ctor = scriptClass.getConstructor(AllocationInfo.class);
+ } else {
+ // Script(AllocationInfo inputInfo, ParameterMap<T> parameterMap)
+ ctor = scriptClass.getConstructor(AllocationInfo.class, ParameterMap.class);
+ }
+ } catch (NoSuchMethodException e) {
+ throw new UnsupportedOperationException(
+ "Script class " + scriptClass + " must have a matching constructor", e);
+ }
+
+ try {
+ if (parameterMap.isEmpty()) {
+ return ctor.newInstance(inputInfo);
+ } else {
+ return ctor.newInstance(inputInfo, parameterMap);
+ }
+ } catch (InstantiationException e) {
+ throw new UnsupportedOperationException(e);
+ } catch (IllegalAccessException e) {
+ throw new UnsupportedOperationException(e);
+ } catch (IllegalArgumentException e) {
+ throw new UnsupportedOperationException(e);
+ } catch (InvocationTargetException e) {
+ throw new UnsupportedOperationException(e);
+ }
+ }
+
+ private boolean isInputFromSurface() {
+ return (mUsage & Allocation.USAGE_IO_INPUT) != 0;
+ }
+
+ /**
+ * Get the input {@link Allocation} that is used by the first script as the input.
+ *
+ * @return An {@link Allocation} (never {@code null}).
+ *
+ * @throws IllegalStateException if the graph was already {@link #close closed}
+ */
+ public Allocation getInputAllocation() {
+ checkNotClosed();
+
+ return mScripts.get(INPUT_SCRIPT_LOCATION).getInput();
+ }
+
+ /**
+ * Get the output {@link Allocation} that is used by the last script as the output.
+ *
+ * @return An {@link Allocation} (never {@code null}).
+ *
+ * @throws IllegalStateException if the graph was already {@link #close closed}
+ */
+ public Allocation getOutputAllocation() {
+ checkNotClosed();
+ Allocation output = mScripts.get(OUTPUT_SCRIPT_LOCATION).getOutput();
+
+ assertEquals("Graph's output should match last script's output", mOutputAllocation, output);
+
+ return output;
+ }
+
+ /**
+ * Get the {@link Surface} that can be used produce buffers into the
+ * {@link #getInputAllocation input allocation}.
+ *
+ * @throws IllegalStateException
+ * if input wasn't configured with {@link Allocation#USAGE_IO_INPUT} {@code usage}.
+ * @throws IllegalStateException
+ * if the graph was already {@link #close closed}
+ *
+ * @return A {@link Surface} (never {@code null}).
+ */
+ public Surface getInputSurface() {
+ checkNotClosed();
+ checkState("This graph was not configured with IO_USAGE_INPUT", isInputFromSurface());
+
+ return getInputAllocation().getSurface();
+ }
+
+ private void checkNotClosed() {
+ checkState("ScriptGraph has been closed", !mClosed);
+ }
+
+ /**
+ * Releases all underlying resources associated with this {@link ScriptGraph}.
+ *
+ * <p>In particular, all underlying {@link Script scripts} and all
+ * {@link Allocation allocations} are also closed.</p>
+ *
+ * <p>All further calls to any other public methods (other than {@link #close}) will throw
+ * an {@link IllegalStateException}.</p>
+ *
+ * <p>This method is idempotent; calling it more than once will
+ * have no further effect.</p>
+ */
+ @Override
+ public synchronized void close() {
+ if (mClosed) return;
+
+ for (Script<?> script : mScripts) {
+ script.close();
+ }
+ mScripts.clear();
+
+ MaybeNull.close(mInputBlocker);
+ mCache.returnToCache(mOutputAllocation);
+
+ mClosed = true;
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ close();
+ } finally {
+ super.finalize();
+ }
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/rs/ScriptYuvCrop.java b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/ScriptYuvCrop.java
new file mode 100644
index 0000000..e1cdf03
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/ScriptYuvCrop.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.camera2.cts.rs;
+
+import static android.hardware.camera2.cts.helpers.Preconditions.*;
+
+import android.graphics.ImageFormat;
+import android.graphics.RectF;
+import android.hardware.camera2.Size;
+import android.hardware.camera2.cts.ScriptC_crop_yuvf_420_to_yuvx_444;
+import android.hardware.camera2.cts.rs.AllocationInfo.ElementInfo;
+import android.renderscript.Element;
+import android.util.Log;
+
+/**
+ * Crop {@link ImageFormat#YUV_420_888 flexible-YUV} {@link Allocation allocations} into
+ * a {@link ElementInfo#U8_3 U8_3} {@link Allocation allocation}.
+ *
+ * <p>Users of this script must configure it with the
+ * {@link ScriptYuvCrop#CROP_WINDOW crop window} parameter.</p>
+ *
+ */
+public class ScriptYuvCrop extends Script<ScriptC_crop_yuvf_420_to_yuvx_444> {
+ private static final String TAG = "ScriptYuvCrop";
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+ /**
+ * A rectangle holding the top,left,right,bottom normalized coordinates each within [0,1].
+ *
+ * <p>The output will be a cropped copy of the input to only this crop window.</p>
+ */
+ // TODO: Change this to use Patch
+ public static final Script.ScriptParameter<ScriptYuvCrop, RectF> CROP_WINDOW =
+ new Script.ScriptParameter<ScriptYuvCrop, RectF>(ScriptYuvCrop.class,
+ RectF.class);
+
+ private final RectF mCropWindow;
+
+ private static AllocationInfo createOutputInfo(AllocationInfo inputInfo,
+ ParameterMap<ScriptYuvCrop> parameters) {
+ checkNotNull("inputInfo", inputInfo);
+ checkNotNull("parameters", parameters);
+
+ if (!parameters.contains(CROP_WINDOW)) {
+ throw new IllegalArgumentException("Script's CROP_WINDOW was not set");
+ }
+
+ Size inputSize = inputInfo.getSize();
+ RectF crop = parameters.get(CROP_WINDOW);
+ Size outputSize = new Size(
+ (int)(crop.width() * inputSize.getWidth()),
+ (int)(crop.height() * inputSize.getHeight()));
+
+ if (VERBOSE) Log.v(TAG, String.format("createOutputInfo - outputSize is %s", outputSize));
+
+ /**
+ * Input YUV W x H
+ * Output U8_3 CropW x CropH
+ */
+ return AllocationInfo.newInstance(Element.U8_3(getRS()), outputSize);
+ }
+
+ public ScriptYuvCrop(AllocationInfo inputInfo,
+ ParameterMap<ScriptYuvCrop> parameterMap) {
+ super(inputInfo,
+ createOutputInfo(inputInfo, parameterMap),
+ new ScriptC_crop_yuvf_420_to_yuvx_444(getRS()));
+
+ // YUV_420_888 is the only supported format here
+ if (!inputInfo.isElementEqualTo(ElementInfo.YUV)) {
+ throw new UnsupportedOperationException("Unsupported element "
+ + inputInfo.getElement());
+ }
+
+ mCropWindow = parameterMap.get(CROP_WINDOW);
+ }
+
+ @Override
+ protected void executeUnchecked() {
+ mScript.forEach_crop(mOutputAllocation);
+
+ if (VERBOSE) { Log.v(TAG, "executeUnchecked - forEach_crop done"); }
+ }
+
+ @Override
+ protected void updateScriptInput() {
+ int x = (int)(mCropWindow.left * mInputInfo.getSize().getWidth());
+ int y = (int)(mCropWindow.top * mInputInfo.getSize().getHeight());
+
+ mScript.set_src_x(x);
+ mScript.set_src_y(y);
+
+ mScript.set_mInput(mInputAllocation);
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/rs/ScriptYuvMeans1d.java b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/ScriptYuvMeans1d.java
new file mode 100644
index 0000000..60dca59
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/ScriptYuvMeans1d.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.camera2.cts.rs;
+
+import static android.hardware.camera2.cts.helpers.Preconditions.*;
+
+import android.hardware.camera2.Size;
+import android.hardware.camera2.cts.ScriptC_means_yuvx_444_1d_to_single;
+import android.hardware.camera2.cts.rs.AllocationInfo.ElementInfo;
+import android.renderscript.Element;
+
+/**
+ * Average a {@code Hx1} {@link ElementInfo#U8_3 U8_3} {@link Allocation allocation} into a 1x1
+ * {@code U8x3} {@link Allocation allocation}.
+ *
+ * <p>Users of this script should chain {@link ScriptYuvMeans1d} immediately before this
+ * to average the input down to a 1x1 element.</p>
+ */
+public class ScriptYuvMeans1d extends Script<ScriptC_means_yuvx_444_1d_to_single>{
+ private static final String TAG = "ScriptYuvMeans1d";
+
+ private static final Size UNIT_SQUARE = new Size(/*width*/1, /*height*/1);
+
+ private static AllocationInfo createOutputInfo(AllocationInfo inputInfo) {
+ checkNotNull("inputInfo", inputInfo);
+ // (input) Hx1 -> 1x1
+ return AllocationInfo.newInstance(Element.U8_3(getRS()), UNIT_SQUARE);
+ }
+
+ public ScriptYuvMeans1d(AllocationInfo inputInfo) {
+ super(inputInfo,
+ createOutputInfo(inputInfo),
+ new ScriptC_means_yuvx_444_1d_to_single(getRS()));
+
+ // U8x3 is the only supported element here
+ if (!inputInfo.isElementEqualTo(ElementInfo.U8_3)) {
+ throw new UnsupportedOperationException("Unsupported element "
+ + inputInfo.getElement());
+ }
+ }
+
+ @Override
+ protected void executeUnchecked() {
+ mScript.forEach_means_yuvx_444(mOutputAllocation);
+ }
+
+ @Override
+ protected void updateScriptInput() {
+ mScript.set_mInput(mInputAllocation);
+
+ int width = mInputAllocation.getType().getX();
+ mScript.set_width(width);
+ mScript.set_inv_width(1.0f / width);
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/rs/ScriptYuvMeans2dTo1d.java b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/ScriptYuvMeans2dTo1d.java
new file mode 100644
index 0000000..335e631
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/ScriptYuvMeans2dTo1d.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.camera2.cts.rs;
+
+import static android.hardware.camera2.cts.helpers.Preconditions.*;
+
+import android.graphics.ImageFormat;
+import android.graphics.RectF;
+import android.hardware.camera2.Size;
+import android.hardware.camera2.cts.ScriptC_means_yuvx_444_2d_to_1d;
+import android.hardware.camera2.cts.rs.AllocationInfo.ElementInfo;
+import android.util.Log;
+
+/**
+ * Average pixels from a {@link ImageFormat#YUV_420_888 flexible-YUV} or
+ * {@link ElementInfo#U8_3 U8_3} {@link Allocation allocations} into a 1D Hx1
+ * {@link ElementInfo#U8_3 U8_3} {@link Allocation allocation}.
+ *
+ * <p>Users of this script should chain {@link ScriptYuvMeans1d} immediately afterwards
+ * to average the output down to a 1x1 element.</p>
+ */
+public class ScriptYuvMeans2dTo1d extends Script<ScriptC_means_yuvx_444_2d_to_1d> {
+
+ private static final String TAG = "ScriptYuvMeans2dTo1d";
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+ private static AllocationInfo createOutputInfo(AllocationInfo inputInfo) {
+ checkNotNull("inputInfo", inputInfo);
+ // (input) WxH -> (output) Hx1
+ return AllocationInfo.newInstance(inputInfo.getElement(),
+ new Size(inputInfo.getSize().getHeight(), /*height*/1));
+ }
+
+ public ScriptYuvMeans2dTo1d(AllocationInfo inputInfo) {
+ super(inputInfo,
+ createOutputInfo(inputInfo),
+ new ScriptC_means_yuvx_444_2d_to_1d(getRS()));
+
+ // YUV_420_888 and U8_3 is the only supported format here
+ if (!inputInfo.isElementEqualTo(ElementInfo.YUV) &&
+ !inputInfo.isElementEqualTo(ElementInfo.U8_3)) {
+ throw new UnsupportedOperationException("Unsupported element "
+ + inputInfo.getElement());
+ }
+ }
+
+ @Override
+ protected void executeUnchecked() {
+ // TODO: replace with switch statement
+ if (mInputInfo.isElementEqualTo(ElementInfo.YUV)) {
+ mScript.forEach_means_yuvf_420(mOutputAllocation);
+
+ if (VERBOSE) Log.v(TAG, "executeUnchecked - forEach_means_yuvf_420");
+ } else if (mInputInfo.isElementEqualTo(ElementInfo.U8_3)) {
+ mScript.forEach_means_yuvx_444(mOutputAllocation);
+
+ if (VERBOSE) Log.v(TAG, "executeUnchecked - forEach_means_yuvx_444");
+ } else {
+ throw new UnsupportedOperationException("Unsupported element "
+ + mInputInfo.getElement());
+ }
+ }
+
+ @Override
+ protected void updateScriptInput() {
+ mScript.set_mInput(mInputAllocation);
+
+ int width = mInputAllocation.getType().getX();
+ mScript.set_width(width);
+ mScript.set_inv_width(1.0f / width);
+
+ // Do not crop. Those who want to crop should use ScriptYuvCrop.class
+ mScript.set_src_x(0);
+ mScript.set_src_y(0);
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/rs/ScriptYuvToRgb.java b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/ScriptYuvToRgb.java
new file mode 100644
index 0000000..5cdc0a0
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/ScriptYuvToRgb.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.hardware.camera2.cts.rs;
+
+import static android.hardware.camera2.cts.helpers.Preconditions.*;
+
+import android.graphics.ImageFormat;
+import android.graphics.PixelFormat;
+import android.hardware.camera2.cts.rs.AllocationInfo.ElementInfo;
+import android.renderscript.Element;
+import android.renderscript.ScriptIntrinsicYuvToRGB;
+import android.util.Log;
+
+/**
+ * Convert {@link ImageFormat#YUV_420_888 flexible-YUV} {@link Allocation allocations} into
+ * a {@link ElementInfo#RGBA_8888 RGBA_8888} {@link Allocation allocation}.
+ */
+public class ScriptYuvToRgb extends Script<ScriptIntrinsicYuvToRGB> {
+ private static final String TAG = "ScriptYuvToRgb";
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+ private static AllocationInfo createOutputInfo(AllocationInfo outputInfo) {
+ checkNotNull("outputInfo", outputInfo);
+ return outputInfo.changeFormatWithDefaultUsage(PixelFormat.RGBA_8888);
+ }
+
+ public ScriptYuvToRgb(AllocationInfo inputInfo) {
+ super(inputInfo,
+ createOutputInfo(inputInfo),
+ ScriptIntrinsicYuvToRGB.create(getRS(), Element.YUV(getRS())));
+
+ // YUV_420_888 is the only supported format here
+ // XX: Supports any YUV 4:2:0 such as NV21/YV12 or just YUV_420_888 ?
+ if (!inputInfo.isElementEqualTo(ElementInfo.YUV)) {
+ throw new UnsupportedOperationException("Unsupported element "
+ + inputInfo.getElement());
+ }
+ }
+
+ @Override
+ protected void executeUnchecked() {
+ mScript.forEach(mOutputAllocation);
+
+ if (VERBOSE) { Log.v(TAG, "executeUnchecked - forEach done"); }
+ }
+
+ @Override
+ protected void updateScriptInput() {
+ mScript.setInput(mInputAllocation);
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestCase.java b/tests/tests/hardware/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestCase.java
new file mode 100644
index 0000000..c0a94b9
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestCase.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.camera2.cts.testcases;
+
+import static android.hardware.camera2.cts.CameraTestUtils.*;
+import static com.android.ex.camera2.blocking.BlockingStateListener.*;
+
+import android.content.Context;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraDevice.CaptureListener;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.Size;
+import android.hardware.camera2.cts.CameraTestUtils;
+import android.hardware.camera2.cts.helpers.CameraErrorCollector;
+import android.hardware.camera2.cts.helpers.StaticMetadata;
+import android.hardware.camera2.cts.helpers.StaticMetadata.CheckLevel;
+import android.media.Image;
+import android.media.ImageReader;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.test.AndroidTestCase;
+import android.util.Log;
+import android.view.Surface;
+
+import com.android.ex.camera2.blocking.BlockingStateListener;
+
+import java.util.List;
+
+public class Camera2AndroidTestCase extends AndroidTestCase {
+ private static final String TAG = "Camera2AndroidTestCase";
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+ protected static final String DEBUG_FILE_NAME_BASE =
+ Environment.getExternalStorageDirectory().getPath();
+ // Default capture size: VGA size is required by CDD.
+ protected static final Size DEFAULT_CAPTURE_SIZE = new Size(640, 480);
+ protected static final int CAPTURE_WAIT_TIMEOUT_MS = 5000;
+
+ protected CameraManager mCameraManager;
+ protected CameraDevice mCamera;
+ protected BlockingStateListener mCameraListener;
+ protected String[] mCameraIds;
+ protected ImageReader mReader;
+ protected Surface mReaderSurface;
+ protected Handler mHandler;
+ protected HandlerThread mHandlerThread;
+ protected StaticMetadata mStaticInfo;
+ protected CameraErrorCollector mCollector;
+ protected List<Size> mOrderedPreviewSizes; // In descending order.
+ protected List<Size> mOrderedVideoSizes; // In descending order.
+ protected List<Size> mOrderedStillSizes; // In descending order.
+
+ @Override
+ public void setContext(Context context) {
+ super.setContext(context);
+ mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
+ assertNotNull("Can't connect to camera manager!", mCameraManager);
+ }
+
+ /**
+ * Set up the camera2 test case required environments, including CameraManager,
+ * HandlerThread, Camera IDs, and CameraStateListener etc.
+ */
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mCameraIds = mCameraManager.getCameraIdList();
+ assertNotNull("Camera ids shouldn't be null", mCameraIds);
+ mHandlerThread = new HandlerThread(TAG);
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+ mCameraListener = new BlockingStateListener();
+ mCollector = new CameraErrorCollector();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ mHandlerThread.quitSafely();
+ mHandler = null;
+ closeImageReader();
+
+ try {
+ mCollector.verify();
+ } catch (Throwable e) {
+ // When new Exception(e) is used, exception info will be printed twice.
+ throw new Exception(e.getMessage());
+ } finally {
+ super.tearDown();
+ }
+ }
+
+ /**
+ * Start capture with given {@link #CaptureRequest}.
+ *
+ * @param request The {@link #CaptureRequest} to be captured.
+ * @param repeating If the capture is single capture or repeating.
+ * @param listener The {@link #CaptureListener} camera device used to notify callbacks.
+ * @param handler The handler camera device used to post callbacks.
+ */
+ protected void startCapture(CaptureRequest request, boolean repeating,
+ CaptureListener listener, Handler handler) throws Exception {
+ if (VERBOSE) Log.v(TAG, "Starting capture");
+
+ if (repeating) {
+ mCamera.setRepeatingRequest(request, listener, handler);
+ } else {
+ mCamera.capture(request, listener, handler);
+ }
+ }
+
+ /**
+ * Stop the current active capture.
+ *
+ * @param fast When it is true, {@link CameraDevice#flush} is called, the stop capture
+ * could be faster.
+ */
+ protected void stopCapture(boolean fast) throws Exception {
+ if (VERBOSE) Log.v(TAG, "Stopping capture");
+
+ if (fast) {
+ /**
+ * Flush is useful for canceling long exposure single capture, it also could help
+ * to make the streaming capture stop sooner.
+ */
+ mCamera.flush();
+ mCameraListener.waitForState(STATE_IDLE, CAMERA_IDLE_TIMEOUT_MS);
+ } else {
+ configureCameraOutputs(mCamera, /*outputSurfaces*/null, mCameraListener);
+ }
+ }
+
+ /**
+ * Open a {@link #CameraDevice camera device} and get the StaticMetadata for a given camera id.
+ * The default mCameraListener is used to wait for states.
+ *
+ * @param cameraId The id of the camera device to be opened.
+ */
+ protected void openDevice(String cameraId) throws Exception {
+ openDevice(cameraId, mCameraListener);
+ }
+
+ /**
+ * Open a {@link #CameraDevice} and get the StaticMetadata for a given camera id and listener.
+ *
+ * @param cameraId The id of the camera device to be opened.
+ * @param listener The {@link #BlockingStateListener} used to wait for states.
+ */
+ protected void openDevice(String cameraId, BlockingStateListener listener) throws Exception {
+ mCamera = CameraTestUtils.openCamera(
+ mCameraManager, cameraId, listener, mHandler);
+ mCollector.setCameraId(cameraId);
+ mStaticInfo = new StaticMetadata(mCameraManager.getCameraCharacteristics(cameraId),
+ CheckLevel.ASSERT, /*collector*/null);
+ mOrderedPreviewSizes = getSupportedPreviewSizes(
+ cameraId, mCameraManager, PREVIEW_SIZE_BOUND);
+ mOrderedVideoSizes = getSupportedVideoSizes(cameraId, mCameraManager, PREVIEW_SIZE_BOUND);
+ mOrderedStillSizes = getSupportedStillSizes(cameraId, mCameraManager, null);
+
+ if (VERBOSE) {
+ Log.v(TAG, "Camera " + cameraId + " is opened");
+ }
+ }
+
+ /**
+ * Close a {@link #CameraDevice camera device} and clear the associated StaticInfo field for a
+ * given camera id. The default mCameraListener is used to wait for states.
+ * <p>
+ * This function must be used along with the {@link #openDevice} for the
+ * same camera id.
+ * </p>
+ *
+ * @param cameraId The id of the {@link #CameraDevice camera device} to be closed.
+ */
+ protected void closeDevice(String cameraId) {
+ closeDevice(cameraId, mCameraListener);
+ }
+
+ /**
+ * Close a {@link #CameraDevice camera device} and clear the associated StaticInfo field for a
+ * given camera id and listener.
+ * <p>
+ * This function must be used along with the {@link #openDevice} for the
+ * same camera id.
+ * </p>
+ *
+ * @param cameraId The id of the camera device to be closed.
+ * @param listener The BlockingStateListener used to wait for states.
+ */
+ protected void closeDevice(String cameraId, BlockingStateListener listener) {
+ if (mCamera != null) {
+ if (!cameraId.equals(mCamera.getId())) {
+ throw new IllegalStateException("Try to close a device that is not opened yet");
+ }
+ mCamera.close();
+ listener.waitForState(STATE_CLOSED, CAMERA_CLOSE_TIMEOUT_MS);
+ mCamera = null;
+ mStaticInfo = null;
+ mOrderedPreviewSizes = null;
+ mOrderedVideoSizes = null;
+ mOrderedStillSizes = null;
+
+ if (VERBOSE) {
+ Log.v(TAG, "Camera " + cameraId + " is closed");
+ }
+ }
+ }
+
+ /**
+ * Create an {@link ImageReader} object and get the surface.
+ *
+ * @param size The size of this ImageReader to be created.
+ * @param format The format of this ImageReader to be created
+ * @param maxNumImages The max number of images that can be acquired simultaneously.
+ * @param listener The listener used by this ImageReader to notify callbacks.
+ */
+ protected void createImageReader(Size size, int format, int maxNumImages,
+ ImageReader.OnImageAvailableListener listener) throws Exception {
+ closeImageReader();
+
+ mReader = ImageReader.newInstance(size.getWidth(), size.getHeight(), format, maxNumImages);
+ mReaderSurface = mReader.getSurface();
+ mReader.setOnImageAvailableListener(listener, mHandler);
+ if (VERBOSE) Log.v(TAG, "Created ImageReader size " + size.toString());
+ }
+
+ /**
+ * Close the pending images then close current active {@link ImageReader} object.
+ */
+ protected void closeImageReader() {
+ if (mReader != null) {
+ try {
+ // Close all possible pending images first.
+ Image image = mReader.acquireLatestImage();
+ if (image != null) {
+ image.close();
+ }
+ } finally {
+ mReader.close();
+ mReader = null;
+ }
+ }
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/testcases/Camera2SurfaceViewTestCase.java b/tests/tests/hardware/src/android/hardware/camera2/cts/testcases/Camera2SurfaceViewTestCase.java
index 7712440..c7c50ab 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/testcases/Camera2SurfaceViewTestCase.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/testcases/Camera2SurfaceViewTestCase.java
@@ -17,7 +17,10 @@
package android.hardware.camera2.cts.testcases;
import static android.hardware.camera2.cts.CameraTestUtils.*;
+import static com.android.ex.camera2.blocking.BlockingStateListener.STATE_CLOSED;
+import android.media.Image;
+import android.media.ImageReader;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
@@ -26,16 +29,23 @@
import android.view.Surface;
import android.view.SurfaceHolder;
import android.content.Context;
+import android.graphics.ImageFormat;
+import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CameraMetadata.Key;
import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.Size;
import android.hardware.camera2.CameraDevice.CaptureListener;
import android.hardware.camera2.cts.Camera2SurfaceViewStubActivity;
import android.hardware.camera2.cts.CameraTestUtils;
+import android.hardware.camera2.cts.helpers.CameraErrorCollector;
import android.hardware.camera2.cts.helpers.StaticMetadata;
+import android.hardware.camera2.cts.helpers.StaticMetadata.CheckLevel;
import com.android.ex.camera2.blocking.BlockingStateListener;
+import com.android.ex.camera2.exceptions.TimeoutRuntimeException;
import java.util.ArrayList;
import java.util.List;
@@ -55,19 +65,28 @@
private static final String TAG = "SurfaceViewTestCase";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
private static final int WAIT_FOR_SURFACE_CHANGE_TIMEOUT_MS = 1000;
+ private static final int MAX_READER_IMAGES = 5;
private Size mPreviewSize;
private Surface mPreviewSurface;
+ protected static final int WAIT_FOR_RESULT_TIMEOUT_MS = 3000;
+
protected Context mContext;
protected CameraManager mCameraManager;
protected String[] mCameraIds;
protected HandlerThread mHandlerThread;
protected Handler mHandler;
protected BlockingStateListener mCameraListener;
+ protected CameraErrorCollector mCollector;
// Per device fields:
protected StaticMetadata mStaticInfo;
protected CameraDevice mCamera;
+ protected ImageReader mReader;
+ protected Surface mReaderSurface;
+ protected List<Size> mOrderedPreviewSizes; // In descending order.
+ protected List<Size> mOrderedVideoSizes; // In descending order.
+ protected List<Size> mOrderedStillSizes; // In descending order.
public Camera2SurfaceViewTestCase() {
super(Camera2SurfaceViewStubActivity.class);
@@ -89,14 +108,7 @@
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
mCameraListener = new BlockingStateListener();
-
- /**
- * Workaround for mockito and JB-MR2 incompatibility
- *
- * Avoid java.lang.IllegalArgumentException: dexcache == null
- * https://code.google.com/p/dexmaker/issues/detail?id=2
- */
- System.setProperty("dexmaker.dexcache", mContext.getCacheDir().toString());
+ mCollector = new CameraErrorCollector();
}
@Override
@@ -106,50 +118,70 @@
mHandler = null;
mCameraListener = null;
- super.tearDown();
+ try {
+ mCollector.verify();
+ } catch (Throwable e) {
+ // When new Exception(e) is used, exception info will be printed twice.
+ throw new Exception(e.getMessage());
+ } finally {
+ super.tearDown();
+ }
}
/**
- * Start camera preview by using the given request, preview size and capture listener.
+ * Start camera preview by using the given request, preview size and capture
+ * listener.
+ * <p>
+ * If preview is already started, calling this function will stop the
+ * current preview stream and start a new preview stream with given
+ * parameters. No need to call stopPreview between two startPreview calls.
+ * </p>
*
* @param request The request builder used to start the preview.
* @param previewSz The size of the camera device output preview stream.
- * @param listener The callbacks the camera device will notify when preview capture is
- * available.
+ * @param listener The callbacks the camera device will notify when preview
+ * capture is available.
*/
protected void startPreview(CaptureRequest.Builder request, Size previewSz,
CaptureListener listener) throws Exception {
- if (!previewSz.equals(mPreviewSize)) {
- mPreviewSize = previewSz;
- Camera2SurfaceViewStubActivity stubActivity = getActivity();
- final SurfaceHolder holder = getActivity().getSurfaceView().getHolder();
- Handler handler = new Handler(Looper.getMainLooper());
- handler.post(new Runnable() {
- @Override
- public void run() {
- holder.setFixedSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
- }
- });
-
- boolean res = stubActivity.waitForSurfaceSizeChanged(
- WAIT_FOR_SURFACE_CHANGE_TIMEOUT_MS, mPreviewSize.getWidth(), mPreviewSize.getHeight());
- assertTrue("wait for surface change to " + mPreviewSize.toString() + " timed out", res);
- mPreviewSurface = holder.getSurface();
- assertTrue("Preview surface is invalid", mPreviewSurface.isValid());
- }
-
+ // Update preview size.
+ updatePreviewSurface(previewSz);
if (VERBOSE) {
Log.v(TAG, "start preview with size " + mPreviewSize.toString());
}
+ configurePreviewOutput(request);
+
+ mCamera.setRepeatingRequest(request.build(), listener, mHandler);
+ }
+
+ /**
+ * Configure the preview output stream.
+ *
+ * @param request The request to be configured with preview surface
+ */
+ protected void configurePreviewOutput(CaptureRequest.Builder request)
+ throws CameraAccessException {
List<Surface> outputSurfaces = new ArrayList<Surface>(/*capacity*/1);
outputSurfaces.add(mPreviewSurface);
configureCameraOutputs(mCamera, outputSurfaces, mCameraListener);
request.addTarget(mPreviewSurface);
-
- mCamera.setRepeatingRequest(request.build(), listener, mHandler);
}
+
+ /**
+ * Create a {@link CaptureRequest#Builder} and add the default preview surface.
+ *
+ * @return The {@link CaptureRequest#Builder} to be created
+ * @throws CameraAccessException When create capture request from camera fails
+ */
+ protected CaptureRequest.Builder createRequestForPreview() throws CameraAccessException {
+ CaptureRequest.Builder requestBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ requestBuilder.addTarget(mPreviewSurface);
+ return requestBuilder;
+ }
+
/**
* Stop preview for current camera device.
*/
@@ -160,6 +192,114 @@
}
/**
+ * Setup still capture configuration and start preview.
+ *
+ * @param request The capture request to be captured
+ * @param previewSz Preview size
+ * @param stillSz Still capture size
+ * @param resultListener Capture result listener
+ * @param imageListener Still capture image listener
+ */
+ protected void prepareStillCaptureAndStartPreview(CaptureRequest.Builder request,
+ Size previewSz, Size stillSz, CaptureListener resultListener,
+ ImageReader.OnImageAvailableListener imageListener) throws Exception {
+ if (VERBOSE) {
+ Log.v(TAG, String.format("Prepare still (%s) and preview (%s)", previewSz.toString(),
+ stillSz.toString()));
+ }
+
+ // Update preview size.
+ updatePreviewSurface(previewSz);
+
+ // Create ImageReader.
+ createImageReader(stillSz, ImageFormat.JPEG, MAX_READER_IMAGES, imageListener);
+
+ // Configure output streams and request.
+ List<Surface> outputSurfaces = new ArrayList<Surface>(/*capacity*/1);
+ outputSurfaces.add(mPreviewSurface);
+ outputSurfaces.add(mReaderSurface);
+ configureCameraOutputs(mCamera, outputSurfaces, mCameraListener);
+ request.addTarget(mPreviewSurface);
+ request.addTarget(mReaderSurface);
+
+ // Start preview.
+ mCamera.setRepeatingRequest(request.build(), resultListener, mHandler);
+ }
+
+ /**
+ * Wait for expected result key value available in a certain number of results.
+ *
+ * <p>
+ * Check the result immediately if numFramesWait is 0.
+ * </p>
+ *
+ * @param listener The capture listener to get capture result
+ * @param resultKey The capture result key associated with the result value
+ * @param expectedValue The result value need to be waited for
+ * @param numResultsWait Number of frame to wait before times out
+ * @throws TimeoutRuntimeException If more than numResultsWait results are
+ * seen before the result matching myRequest arrives, or each individual wait
+ * for result times out after {@value #WAIT_FOR_RESULT_TIMEOUT_MS}ms.
+ */
+ protected <T> void waitForResultValue(SimpleCaptureListener listener, Key<T> resultKey,
+ T expectedValue, int numResultsWait) {
+ if (numResultsWait < 0 || listener == null) {
+ throw new IllegalArgumentException(
+ "Input must be non-negative number and listener must be non-null");
+ }
+
+ int i = 0;
+ CaptureResult result;
+ do {
+ result = listener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
+ T value = result.get(resultKey);
+ if (value.equals(expectedValue)) {
+ return;
+ }
+ } while (i++ < numResultsWait);
+
+ throw new TimeoutRuntimeException(
+ "Unable to get the expected result value " + expectedValue + " for key " +
+ resultKey.getName() + " after waiting for " + numResultsWait + " results");
+ }
+
+ /**
+ * Create an {@link ImageReader} object and get the surface.
+ *
+ * @param size The size of this ImageReader to be created.
+ * @param format The format of this ImageReader to be created
+ * @param maxNumImages The max number of images that can be acquired simultaneously.
+ * @param listener The listener used by this ImageReader to notify callbacks.
+ */
+ protected void createImageReader(Size size, int format, int maxNumImages,
+ ImageReader.OnImageAvailableListener listener) throws Exception {
+ closeImageReader();
+
+ mReader = ImageReader.newInstance(size.getWidth(), size.getHeight(), format, maxNumImages);
+ mReaderSurface = mReader.getSurface();
+ mReader.setOnImageAvailableListener(listener, mHandler);
+ if (VERBOSE) Log.v(TAG, "Created ImageReader size " + size.toString());
+ }
+
+ /**
+ * Close the pending images then close current active {@link ImageReader} object.
+ */
+ protected void closeImageReader() {
+ if (mReader != null) {
+ try {
+ // Close all possible pending images first.
+ Image image = mReader.acquireLatestImage();
+ if (image != null) {
+ image.close();
+ }
+ } finally {
+ mReader.close();
+ mReader = null;
+ }
+ }
+ }
+
+ /**
* Open a camera device and get the StaticMetadata for a given camera id.
*
* @param cameraId The id of the camera device to be opened.
@@ -167,8 +307,12 @@
protected void openDevice(String cameraId) throws Exception {
mCamera = CameraTestUtils.openCamera(
mCameraManager, cameraId, mCameraListener, mHandler);
- assertNotNull("Failed to open camera device " + cameraId, mCamera);
- mStaticInfo = new StaticMetadata(mCameraManager.getCameraCharacteristics(cameraId));
+ mCollector.setCameraId(cameraId);
+ mStaticInfo = new StaticMetadata(mCameraManager.getCameraCharacteristics(cameraId),
+ CheckLevel.ASSERT, /*collector*/null);
+ mOrderedPreviewSizes = getSupportedPreviewSizes(cameraId, mCameraManager, PREVIEW_SIZE_BOUND);
+ mOrderedVideoSizes = getSupportedVideoSizes(cameraId, mCameraManager, PREVIEW_SIZE_BOUND);
+ mOrderedStillSizes = getSupportedStillSizes(cameraId, mCameraManager, null);
}
/**
@@ -177,8 +321,36 @@
protected void closeDevice() {
if (mCamera != null) {
mCamera.close();
+ mCameraListener.waitForState(STATE_CLOSED, CAMERA_CLOSE_TIMEOUT_MS);
mCamera = null;
+ mStaticInfo = null;
+ mOrderedPreviewSizes = null;
+ mOrderedVideoSizes = null;
+ mOrderedStillSizes = null;
}
- mStaticInfo = null;
+ }
+
+ protected void updatePreviewSurface(Size size) {
+ if (size.equals(mPreviewSize)) {
+ return;
+ }
+
+ mPreviewSize = size;
+ Camera2SurfaceViewStubActivity stubActivity = getActivity();
+ final SurfaceHolder holder = stubActivity.getSurfaceView().getHolder();
+ Handler handler = new Handler(Looper.getMainLooper());
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ holder.setFixedSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
+ }
+ });
+
+ boolean res = stubActivity.waitForSurfaceSizeChanged(
+ WAIT_FOR_SURFACE_CHANGE_TIMEOUT_MS, mPreviewSize.getWidth(),
+ mPreviewSize.getHeight());
+ assertTrue("wait for surface change to " + mPreviewSize.toString() + " timed out", res);
+ mPreviewSurface = holder.getSurface();
+ assertTrue("Preview surface is invalid", mPreviewSurface.isValid());
}
}
diff --git a/tests/tests/hardware/src/android/hardware/cts/SensorIntegrationTests.java b/tests/tests/hardware/src/android/hardware/cts/SensorIntegrationTests.java
index bcf9755..c27d108 100644
--- a/tests/tests/hardware/src/android/hardware/cts/SensorIntegrationTests.java
+++ b/tests/tests/hardware/src/android/hardware/cts/SensorIntegrationTests.java
@@ -25,7 +25,8 @@
import android.hardware.cts.helpers.sensoroperations.ParallelSensorOperation;
import android.hardware.cts.helpers.sensoroperations.RepeatingSensorOperation;
import android.hardware.cts.helpers.sensoroperations.SequentialSensorOperation;
-import android.hardware.cts.helpers.sensoroperations.VerifySensorOperation;
+import android.hardware.cts.helpers.sensoroperations.TestSensorOperation;
+import android.hardware.cts.helpers.sensorverification.EventOrderingVerification;
import junit.framework.Test;
import junit.framework.TestSuite;
@@ -96,22 +97,22 @@
ParallelSensorOperation operation = new ParallelSensorOperation();
for(int sensorType : sensorTypes) {
- VerifySensorOperation continuousOperation = new VerifySensorOperation(
+ TestSensorOperation continuousOperation = new TestSensorOperation(
context,
sensorType,
SensorManager.SENSOR_DELAY_NORMAL,
0 /* reportLatencyInUs */,
100 /* event count */);
- continuousOperation.verifyEventOrdering();
+ continuousOperation.addVerification(new EventOrderingVerification());
operation.add(new RepeatingSensorOperation(continuousOperation, ITERATIONS));
- VerifySensorOperation batchingOperation = new VerifySensorOperation(
+ TestSensorOperation batchingOperation = new TestSensorOperation(
context,
sensorType,
SensorTestInformation.getMaxSamplingRateInUs(context, sensorType),
SensorCtsHelper.getSecondsAsMicroSeconds(BATCHING_RATE_IN_SECONDS),
100);
- batchingOperation.verifyEventOrdering();
+ batchingOperation.addVerification(new EventOrderingVerification());
operation.add(new RepeatingSensorOperation(batchingOperation, ITERATIONS));
}
operation.execute();
@@ -154,13 +155,13 @@
for(int instance = 0; instance < INSTANCES_TO_USE; ++instance) {
SequentialSensorOperation sequentialOperation = new SequentialSensorOperation();
for(int iteration = 0; iteration < ITERATIONS_TO_EXECUTE; ++iteration) {
- VerifySensorOperation sensorOperation = new VerifySensorOperation(
+ TestSensorOperation sensorOperation = new TestSensorOperation(
this.getContext(),
sensorType,
this.generateSamplingRateInUs(sensorType),
this.generateReportLatencyInUs(),
100);
- sensorOperation.verifyEventOrdering();
+ sensorOperation.addVerification(new EventOrderingVerification());
sequentialOperation.add(sensorOperation);
}
operation.add(sequentialOperation);
@@ -218,21 +219,21 @@
public void testSensorStoppingInteraction() throws Throwable {
Context context = this.getContext();
- VerifySensorOperation tester = new VerifySensorOperation(
+ TestSensorOperation tester = new TestSensorOperation(
context,
mSensorTypeTester,
SensorManager.SENSOR_DELAY_NORMAL,
0 /*reportLatencyInUs*/,
100 /* event count */);
- tester.verifyEventOrdering();
+ tester.addVerification(new EventOrderingVerification());
- VerifySensorOperation testee = new VerifySensorOperation(
+ TestSensorOperation testee = new TestSensorOperation(
context,
mSensorTypeTestee,
SensorManager.SENSOR_DELAY_UI,
0 /*reportLatencyInUs*/,
100 /* event count */);
- testee.verifyEventOrdering();
+ testee.addVerification(new EventOrderingVerification());
ParallelSensorOperation operation = new ParallelSensorOperation();
operation.add(tester, testee);
diff --git a/tests/tests/hardware/src/android/hardware/cts/SingleSensorTests.java b/tests/tests/hardware/src/android/hardware/cts/SingleSensorTests.java
index 27e7b02..d280b21 100644
--- a/tests/tests/hardware/src/android/hardware/cts/SingleSensorTests.java
+++ b/tests/tests/hardware/src/android/hardware/cts/SingleSensorTests.java
@@ -22,11 +22,13 @@
import android.hardware.cts.helpers.SensorStats;
import android.hardware.cts.helpers.SensorTestCase;
import android.hardware.cts.helpers.SensorTestInformation;
-import android.hardware.cts.helpers.sensoroperations.VerifySensorOperation;
+import android.hardware.cts.helpers.sensoroperations.TestSensorOperation;
+import android.util.Log;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.concurrent.TimeUnit;
/**
* Set of tests to verify that sensors operate correctly when operating alone.
@@ -204,10 +206,46 @@
* Helper to setup the test and run it.
*/
private void sensorTestHelper(int sensorType) throws Throwable {
- VerifySensorOperation op = new VerifySensorOperation(this.getContext(), sensorType,
- SensorManager.SENSOR_DELAY_FASTEST, 0, 100);
- op.setDefaultVerifications();
- op.execute();
- SensorStats.logStats(TAG, op.getStats());
+ int minDelay = SensorCtsHelper.getSensor(mContext, sensorType).getMinDelay();
+ int[] rateUss = {
+ SensorManager.SENSOR_DELAY_FASTEST, // Should be the same as min delay
+ (int) (minDelay * 1.5),
+ minDelay * 2,
+ minDelay * 5,
+ minDelay * 8,
+ };
+ int[] maxBatchReportLatencyUss = {
+ 0, // No batching
+ (int) TimeUnit.MICROSECONDS.convert(2, TimeUnit.SECONDS),
+ };
+
+ AssertionError firstError = null;
+
+ for (int rateUs : rateUss) {
+ for (int maxBatchReportLatencyUs : maxBatchReportLatencyUss) {
+ Log.v(TAG, String.format("Run on %d with rate %d and batch %d", sensorType, rateUs,
+ maxBatchReportLatencyUs));
+ TestSensorOperation op = new TestSensorOperation(this.getContext(), sensorType,
+ rateUs, maxBatchReportLatencyUs, 5, TimeUnit.SECONDS);
+ op.setDefaultVerifications();
+ try {
+ op.execute();
+ } catch (AssertionError e) {
+ if (firstError == null) {
+ firstError = e;
+ }
+ }
+
+ SensorStats.logStats(TAG, op.getStats());
+
+ String fileName = String.format("single_sensor_%d_%d_%d.txt", sensorType,
+ rateUs, maxBatchReportLatencyUs);
+ SensorStats.logStatsToFile(fileName, op.getStats());
+ }
+ }
+
+ if (firstError != null) {
+ throw firstError;
+ }
}
}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/CollectingSensorEventListener.java b/tests/tests/hardware/src/android/hardware/cts/helpers/CollectingSensorEventListener.java
new file mode 100644
index 0000000..981d74c
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/CollectingSensorEventListener.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.cts.helpers;
+
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener2;
+import android.os.SystemClock;
+
+import java.util.concurrent.ConcurrentLinkedDeque;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A {@link TestSensorEventListener} which collects events to be processed after the test is run.
+ * This should only be used for short tests.
+ */
+public class CollectingSensorEventListener extends TestSensorEventListener {
+ private final ConcurrentLinkedDeque<TestSensorEvent> mSensorEventsList =
+ new ConcurrentLinkedDeque<TestSensorEvent>();
+
+ /**
+ * Constructs a {@link CollectingSensorEventListener} with an additional
+ * {@link SensorEventListener2}.
+ */
+ public CollectingSensorEventListener(SensorEventListener2 listener) {
+ super(listener);
+ }
+
+ /**
+ * Constructs a {@link CollectingSensorEventListener}.
+ */
+ public CollectingSensorEventListener() {
+ this(null);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ super.onSensorChanged(event);
+ mSensorEventsList.addLast(new TestSensorEvent(event, SystemClock.elapsedRealtimeNanos()));
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * Clears the event queue before starting.
+ * </p>
+ */
+ @Override
+ public void waitForEvents(int eventCount) {
+ clearEvents();
+ super.waitForEvents(eventCount);
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * Clears the event queue before starting.
+ * </p>
+ */
+ @Override
+ public void waitForEvents(long duration, TimeUnit timeUnit) {
+ clearEvents();
+ super.waitForEvents(duration, timeUnit);
+ }
+
+ /**
+ * Get the {@link TestSensorEvent} array from the event queue.
+ */
+ public TestSensorEvent[] getEvents() {
+ return mSensorEventsList.toArray(new TestSensorEvent[0]);
+ }
+
+ /**
+ * Clear the event queue.
+ */
+ public void clearEvents() {
+ mSensorEventsList.clear();
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/FrameworkUnitTests.java b/tests/tests/hardware/src/android/hardware/cts/helpers/FrameworkUnitTests.java
new file mode 100644
index 0000000..872bf28
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/FrameworkUnitTests.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.cts.helpers;
+
+import android.hardware.cts.helpers.sensoroperations.SensorOperationTest;
+import android.hardware.cts.helpers.sensorverification.EventOrderingVerificationTest;
+import android.hardware.cts.helpers.sensorverification.FrequencyVerificationTest;
+import android.hardware.cts.helpers.sensorverification.JitterVerificationTest;
+import android.hardware.cts.helpers.sensorverification.MagnitudeVerificationTest;
+import android.hardware.cts.helpers.sensorverification.MeanVerificationTest;
+import android.hardware.cts.helpers.sensorverification.SigNumVerificationTest;
+import android.hardware.cts.helpers.sensorverification.StandardDeviationVerificationTest;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Unit test suite for the CTS sensor framework.
+ */
+public class FrameworkUnitTests extends TestSuite {
+
+ public FrameworkUnitTests() {
+ super();
+
+ // helpers
+ addTestSuite(SensorCtsHelperTest.class);
+ addTestSuite(SensorStatsTest.class);
+
+ // sensorverification
+ addTestSuite(EventOrderingVerificationTest.class);
+ addTestSuite(FrequencyVerificationTest.class);
+ addTestSuite(JitterVerificationTest.class);
+ addTestSuite(MagnitudeVerificationTest.class);
+ addTestSuite(MeanVerificationTest.class);
+ addTestSuite(SigNumVerificationTest.class);
+ addTestSuite(StandardDeviationVerificationTest.class);
+
+ // sensorOperations
+ addTestSuite(SensorOperationTest.class);
+ }
+
+ public static Test suite() {
+ return new FrameworkUnitTests();
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/SensorCtsHelper.java b/tests/tests/hardware/src/android/hardware/cts/helpers/SensorCtsHelper.java
index fd221a9..09b5684 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/SensorCtsHelper.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/SensorCtsHelper.java
@@ -18,17 +18,10 @@
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorManager;
-import android.hardware.cts.helpers.sensoroperations.ISensorOperation;
-import android.os.Environment;
-import android.util.Log;
-import java.io.DataOutputStream;
-import java.io.IOException;
-import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
@@ -37,6 +30,8 @@
*/
public class SensorCtsHelper {
+ private static long NANOS_PER_MILLI = 1000000;
+
/**
* Private constructor for static class.
*/
@@ -61,72 +56,6 @@
}
/**
- * Calculates the mean for each of the values in the set of TestSensorEvents.
- *
- * @throws IllegalArgumentException if there are no events
- */
- public static Float[] getMeans(TestSensorEvent[] events) {
- if (events.length == 0) {
- throw new IllegalArgumentException("Events cannot be empty");
- }
-
- Float[] means = new Float[events[0].values.length];
- for (int i = 0; i < means.length; i++) {
- means[i] = 0.0f;
- }
- for (TestSensorEvent event : events) {
- for (int i = 0; i < means.length; i++) {
- means[i] += event.values[i];
- }
- }
- for (int i = 0; i < means.length; i++) {
- means[i] /= events.length;
- }
- return means;
- }
-
- /**
- * Calculates the bias-corrected variance for each of the values in the set of TestSensorEvents.
- *
- * @throws IllegalArgumentException if there are no events
- */
- public static Float[] getVariances(TestSensorEvent[] events) {
- Float[] means = getMeans(events);
- Float[] variances = new Float[means.length];
- for (int i = 0; i < variances.length; i++) {
- variances[i] = 0.0f;
- }
- for (int i = 0; i < means.length; i++) {
- Collection<Float> squaredDiffs = new ArrayList<Float>(events.length);
- for (TestSensorEvent event : events) {
- float diff = event.values[i] - means[i];
- squaredDiffs.add(diff * diff);
- }
- float sum = 0.0f;
- for (float value : squaredDiffs) {
- sum += value;
- }
- variances[i] = sum / (events.length - 1);
- }
- return variances;
- }
-
- /**
- * Calculates the bias-corrected standard deviation for each of the values in the set of
- * TestSensorEvents.
- *
- * @throws IllegalArgumentException if there are no events
- */
- public static Float[] getStandardDeviations(TestSensorEvent[] events) {
- Float[] variances = getVariances(events);
- Float[] stdDevs = new Float[variances.length];
- for (int i = 0; i < variances.length; i++) {
- stdDevs[i] = (float) Math.sqrt(variances[i]);
- }
- return stdDevs;
- }
-
- /**
* Calculate the mean of a collection.
*
* @throws IllegalArgumentException if the collection is null or empty
@@ -174,89 +103,6 @@
}
/**
- * Get a list containing the delay between sensor events.
- *
- * @param events The array of {@link TestSensorEvent}.
- * @return A list containing the delay between sensor events in nanoseconds.
- */
- public static List<Long> getTimestampDelayValues(TestSensorEvent[] events) {
- if (events.length < 2) {
- return new ArrayList<Long>();
- }
- List<Long> timestampDelayValues = new ArrayList<Long>(events.length - 1);
- for (int i = 1; i < events.length; i++) {
- timestampDelayValues.add(events[i].timestamp - events[i - 1].timestamp);
- }
- return timestampDelayValues;
- }
-
- /**
- * Get a list containing the jitter values for a collection of sensor events.
- *
- * @param events The array of {@link TestSensorEvent}.
- * @return A list containing the jitter values between each event.
- * @throws IllegalArgumentException if the number of events is less that 2.
- */
- public static List<Double> getJitterValues(TestSensorEvent[] events) {
- List<Long> timestampDelayValues = getTimestampDelayValues(events);
- double averageTimestampDelay = getMean(timestampDelayValues);
-
- List<Double> jitterValues = new ArrayList<Double>(timestampDelayValues.size());
- for (long timestampDelay : timestampDelayValues) {
- jitterValues.add(Math.abs(timestampDelay - averageTimestampDelay));
- }
- return jitterValues;
- }
-
- /**
- * NOTE:
- * - The bug report is usually written to /sdcard/Downloads
- * - In order for the test Instrumentation to gather useful data the following permissions are
- * required:
- * . android.permission.READ_LOGS
- * . android.permission.DUMP
- */
- public static String collectBugreport(String collectorId)
- throws IOException, InterruptedException {
- String commands[] = new String[] {
- "dumpstate",
- "dumpsys",
- "logcat -d -v threadtime",
- "exit"
- };
-
- SimpleDateFormat dateFormat = new SimpleDateFormat("M-d-y_H:m:s.S");
- String outputFile = String.format(
- "%s/%s_%s",
- Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
- collectorId,
- dateFormat.format(new Date()));
-
- DataOutputStream processOutput = null;
- try {
- Process process = Runtime.getRuntime().exec("/system/bin/sh -");
- processOutput = new DataOutputStream(process.getOutputStream());
-
- for(String command : commands) {
- processOutput.writeBytes(String.format("%s >> %s\n", command, outputFile));
- }
-
- processOutput.flush();
- process.waitFor();
-
- Log.d(collectorId, String.format("Bug-Report collected at: %s", outputFile));
- } finally {
- if(processOutput != null) {
- try {
- processOutput.close();
- } catch(IOException e) {}
- }
- }
-
- return outputFile;
- }
-
- /**
* Get the default sensor for a given type.
*/
public static Sensor getSensor(Context context, int sensorType) {
@@ -332,57 +178,58 @@
}
/**
- * Format an assertion message.
- *
- * @param verificationName The verification name
- * @param sensor The sensor under test
- * @param format The additional format string, use "" if blank
- * @param params The additional format params
- * @return The formatted string.
+ * Return true if the operation rate is not one of {@link SensorManager#SENSOR_DELAY_GAME},
+ * {@link SensorManager#SENSOR_DELAY_UI}, or {@link SensorManager#SENSOR_DELAY_NORMAL}.
*/
- public static String formatAssertionMessage(
- String verificationName,
- Sensor sensor,
- String format,
- Object ... params) {
- return formatAssertionMessage(verificationName, null, sensor, format, params);
+ public boolean isDelayRateTestable(int rateUs) {
+ return (rateUs != SensorManager.SENSOR_DELAY_GAME
+ && rateUs != SensorManager.SENSOR_DELAY_UI
+ && rateUs != SensorManager.SENSOR_DELAY_NORMAL);
+ }
+
+ /**
+ * Helper method to sleep for a given duration.
+ */
+ public static void sleep(long duration, TimeUnit timeUnit) {
+ long durationNs = TimeUnit.NANOSECONDS.convert(duration, timeUnit);
+ try {
+ Thread.sleep(durationNs / NANOS_PER_MILLI, (int) (durationNs % NANOS_PER_MILLI));
+ } catch (InterruptedException e) {
+ // Ignore
+ }
}
/**
* Format an assertion message.
*
- * @param verificationName The verification name
- * @param test The test, optional
- * @param sensor The sensor under test
- * @param format The additional format string, use "" if blank
- * @param params The additional format params
- * @return The formatted string.
+ * @param sensor the {@link Sensor}
+ * @param label the verification name
+ * @param rateUs the rate of the sensor
+ * @param maxBatchReportLatencyUs the max batch report latency of the sensor
+ * @return The formatted string
*/
- public static String formatAssertionMessage(
- String verificationName,
- ISensorOperation test,
- Sensor sensor,
- String format,
- Object ... params) {
- StringBuilder builder = new StringBuilder();
+ public static String formatAssertionMessage(Sensor sensor, String label, int rateUs,
+ int maxBatchReportLatencyUs) {
+ return String.format("%s | %s, handle: %d", label,
+ SensorTestInformation.getSensorName(sensor.getType()), sensor.getHandle());
+ }
- // identify the verification
- builder.append(verificationName);
- builder.append("| ");
- // add test context information
- if(test != null) {
- builder.append(test.toString());
- builder.append("| ");
- }
- // add context information
- builder.append(SensorTestInformation.getSensorName(sensor.getType()));
- builder.append(", handle:");
- builder.append(sensor.getHandle());
- builder.append("| ");
- // add the custom formatting
- builder.append(String.format(format, params));
-
- return builder.toString();
+ /**
+ * Format an assertion message with a custom message.
+ *
+ * @param sensor the {@link Sensor}
+ * @param label the verification name
+ * @param rateUs the rate of the sensor
+ * @param maxBatchReportLatencyUs the max batch report latency of the sensor
+ * @param format the additional format string
+ * @param params the additional format params
+ * @return The formatted string
+ */
+ public static String formatAssertionMessage(Sensor sensor, String label, int rateUs,
+ int maxBatchReportLatencyUs, String format, Object ... params) {
+ return String.format("%s | %s, handle: %d, rateUs: %d, maxBatchReportLatencyUs: %d | %s",
+ label, SensorTestInformation.getSensorName(sensor.getType()), sensor.getHandle(),
+ rateUs, maxBatchReportLatencyUs, String.format(format, params));
}
/**
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/SensorCtsHelperTest.java b/tests/tests/hardware/src/android/hardware/cts/helpers/SensorCtsHelperTest.java
index b8cb06f..6f99692 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/SensorCtsHelperTest.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/SensorCtsHelperTest.java
@@ -53,73 +53,6 @@
}
/**
- * Test {@link SensorCtsHelper#getMeans(TestSensorEvent[])}.
- */
- public void testGetMeans() {
- long[] timestamps = {0, 1, 2, 3, 4};
-
- float[] values = {0, 1, 2, 3, 4}; // 2.0
- Float[] means = SensorCtsHelper.getMeans(getSensorEvents(timestamps, values));
- assertEquals(1, means.length);
- assertEquals(2.0, means[0], 0.00001);
-
- float[] values1 = {0, 1, 2, 3, 4}; // 2.0
- float[] values2 = {1, 2, 3, 4, 5}; // 3.0
- float[] values3 = {0, 1, 4, 9, 16}; // 6.0
- means = SensorCtsHelper.getMeans(
- getSensorEvents(timestamps, values1, values2, values3));
- assertEquals(3, means.length);
- assertEquals(2.0, means[0], 0.00001);
- assertEquals(3.0, means[1], 0.00001);
- assertEquals(6.0, means[2], 0.00001);
- }
-
- /**
- * Test {@link SensorCtsHelper#getVariances(TestSensorEvent[])}.
- */
- public void testGetVariences() {
- long[] timestamps = {0, 1, 2, 3, 4};
-
- float[] values = {0, 1, 2, 3, 4}; // 2.5
- Float[] variances = SensorCtsHelper.getVariances(getSensorEvents(timestamps, values));
- assertEquals(1, variances.length);
- assertEquals(2.5, variances[0], 0.00001);
-
- float[] values1 = {0, 1, 2, 3, 4}; // 2.5
- float[] values2 = {1, 2, 3, 4, 5}; // 2.5
- float[] values3 = {0, 2, 4, 6, 8}; // 10.0
- variances = SensorCtsHelper.getVariances(
- getSensorEvents(timestamps, values1, values2, values3));
- assertEquals(3, variances.length);
- assertEquals(2.5, variances[0], 0.00001);
- assertEquals(2.5, variances[1], 0.00001);
- assertEquals(10.0, variances[2], 0.00001);
- }
-
- /**
- * Test {@link SensorCtsHelper#getStandardDeviations(TestSensorEvent[])}.
- */
- public void testGetStandardDeviations() {
- long[] timestamps = {0, 1, 2, 3, 4};
-
- float[] values = {0, 1, 2, 3, 4}; // sqrt(2.5)
- Float[] stddev = SensorCtsHelper.getStandardDeviations(
- getSensorEvents(timestamps, values));
- assertEquals(1, stddev.length);
- assertEquals(Math.sqrt(2.5), stddev[0], 0.00001);
-
- float[] values1 = {0, 1, 2, 3, 4}; // sqrt(2.5)
- float[] values2 = {1, 2, 3, 4, 5}; // sqrt(2.5)
- float[] values3 = {0, 2, 4, 6, 8}; // sqrt(10.0)
- stddev = SensorCtsHelper.getStandardDeviations(
- getSensorEvents(timestamps, values1, values2, values3));
- assertEquals(3, stddev.length);
- assertEquals(Math.sqrt(2.5), stddev[0], 0.00001);
- assertEquals(Math.sqrt(2.5), stddev[1], 0.00001);
- assertEquals(Math.sqrt(10.0), stddev[2], 0.00001);
- }
-
- /**
* Test {@link SensorCtsHelper#getMean(Collection)}.
*/
public void testGetMean() {
@@ -171,22 +104,6 @@
}
/**
- * Test {@link SensorCtsHelper#getTimestampDelayValues(TestSensorEvent[])}.
- */
- public void testGetTimestampDelayValues() {
- float[] values = {0, 1, 2, 3, 4};
-
- long[] timestamps = {0, 0, 1, 3, 100};
- List<Long> timestampDelayValues = SensorCtsHelper.getTimestampDelayValues(
- getSensorEvents(timestamps, values));
- assertEquals(4, timestampDelayValues.size());
- assertEquals(0, (long) timestampDelayValues.get(0));
- assertEquals(1, (long) timestampDelayValues.get(1));
- assertEquals(2, (long) timestampDelayValues.get(2));
- assertEquals(97, (long) timestampDelayValues.get(3));
- }
-
- /**
* Test {@link SensorCtsHelper#getFrequency(Number, TimeUnit)}.
*/
public void testGetFrequency() {
@@ -217,50 +134,4 @@
assertEquals(100, SensorCtsHelper.getPeriod(10000000, TimeUnit.NANOSECONDS), 0.001);
assertEquals(1, SensorCtsHelper.getPeriod(1000000000, TimeUnit.NANOSECONDS), 0.001);
}
-
- /**
- * Test {@link SensorCtsHelper#getJitterValues(TestSensorEvent[])}.
- */
- public void testGetJitterValues() {
- float[] values = {0, 1, 2, 3, 4};
-
- long[] timestamps1 = {0, 1, 2, 3, 4};
- List<Double> jitterValues = SensorCtsHelper.getJitterValues(
- getSensorEvents(timestamps1, values));
- assertEquals(4, jitterValues.size());
- assertEquals(0.0, (double) jitterValues.get(0));
- assertEquals(0.0, (double) jitterValues.get(1));
- assertEquals(0.0, (double) jitterValues.get(2));
- assertEquals(0.0, (double) jitterValues.get(3));
-
- long[] timestamps2 = {0, 0, 2, 4, 4};
- jitterValues = SensorCtsHelper.getJitterValues(
- getSensorEvents(timestamps2, values));
- assertEquals(4, jitterValues.size());
- assertEquals(1.0, (double) jitterValues.get(0));
- assertEquals(1.0, (double) jitterValues.get(1));
- assertEquals(1.0, (double) jitterValues.get(2));
- assertEquals(1.0, (double) jitterValues.get(3));
-
- long[] timestamps3 = {0, 1, 4, 9, 16};
- jitterValues = SensorCtsHelper.getJitterValues(
- getSensorEvents(timestamps3, values));
- assertEquals(4, jitterValues.size());
- assertEquals(3.0, (double) jitterValues.get(0));
- assertEquals(1.0, (double) jitterValues.get(1));
- assertEquals(1.0, (double) jitterValues.get(2));
- assertEquals(3.0, (double) jitterValues.get(3));
- }
-
- private TestSensorEvent[] getSensorEvents(long[] timestamps, float[] ... values) {
- TestSensorEvent[] events = new TestSensorEvent[timestamps.length];
- for (int i = 0; i < timestamps.length; i++) {
- float[] eventValues = new float[values.length];
- for (int j = 0; j < values.length; j++) {
- eventValues[j] = values[j][i];
- }
- events[i] = new TestSensorEvent(null, timestamps[i], 0, eventValues);
- }
- return events;
- }
}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/SensorManagerTestVerifier.java b/tests/tests/hardware/src/android/hardware/cts/helpers/SensorManagerTestVerifier.java
deleted file mode 100644
index e9e89d0..0000000
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/SensorManagerTestVerifier.java
+++ /dev/null
@@ -1,271 +0,0 @@
-/*
- * 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.hardware.cts.helpers;
-
-import android.content.Context;
-import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener2;
-import android.hardware.SensorManager;
-
-import junit.framework.Assert;
-
-import java.io.Closeable;
-import java.security.InvalidParameterException;
-import java.util.concurrent.ConcurrentLinkedDeque;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Test class to wrap SensorManager with verifications and test checks.
- * This class allows to perform operations in the Sensor Manager and performs all the expected test
- * verification on behalf of the owner.
- * An object can be used to quickly writing tests that focus on the scenario that needs to be
- * verified, and not in the implicit verifications that need to take place at any step.
- */
-public class SensorManagerTestVerifier implements Closeable, SensorEventListener2 {
- private final int WAIT_TIMEOUT_IN_SECONDS = 30;
-
- private final SensorManager mSensorManager;
- private final Sensor mSensorUnderTest;
- private final int mRateUs;
- private final int mMaxBatchReportLatencyUs;
-
- private TestSensorListener mEventListener;
-
- /**
- * Construction methods.
- */
- public SensorManagerTestVerifier(
- Context context,
- int sensorType,
- int rateUs,
- int maxBatchReportLatencyUs) {
- mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
- mSensorUnderTest = SensorCtsHelper.getSensor(context, sensorType);
- mRateUs = rateUs;
- mMaxBatchReportLatencyUs = maxBatchReportLatencyUs;
-
- mEventListener = new TestSensorListener(this);
- }
-
- /**
- * {@inheritDoc}
- * <p>
- * Available for subclasses to implement if they need access to the raw eventing model.
- * </p>
- */
- @Override
- public void onAccuracyChanged(Sensor sensor, int accuracy) {}
-
- /**
- * {@inheritDoc}
- * <p>
- * Available for subclasses to implement if they need access to the raw eventing model.
- * </p>
- */
- @Override
- public void onSensorChanged(SensorEvent event) {}
-
- /**
- * {@inheritDoc}
- * <p>
- * Available for subclasses to implement if they need access to the raw eventing model.
- * </p>
- */
- @Override
- public void onFlushCompleted(Sensor sensor) {}
-
- /**
- * Closes the {@link SensorManagerTestVerifier} and unregister the listener.
- */
- @Override
- public void close() {
- this.unregisterListener();
- mEventListener = null;
- }
-
- /**
- * Get the sensor under test.
- */
- public Sensor getUnderlyingSensor() {
- return mSensorUnderTest;
- }
-
- /**
- * Register the listener.
- */
- public void registerListener(String debugInfo) {
- boolean result = mSensorManager.registerListener(
- mEventListener,
- mSensorUnderTest,
- mRateUs,
- mMaxBatchReportLatencyUs);
- String message = SensorCtsHelper.formatAssertionMessage(
- "registerListener",
- mSensorUnderTest,
- debugInfo);
- Assert.assertTrue(message, result);
- }
-
- public void registerListener() {
- this.registerListener("");
- }
-
- public void unregisterListener() {
- mSensorManager.unregisterListener(mEventListener, mSensorUnderTest);
- }
-
- public TestSensorEvent[] getEvents(int count, String debugInfo) {
- mEventListener.waitForEvents(count, debugInfo);
- TestSensorEvent[] events = mEventListener.getAllEvents();
- mEventListener.clearEvents();
-
- return events;
- }
-
- public TestSensorEvent[] getEvents(int count) {
- return this.getEvents(count, "");
- }
-
- public TestSensorEvent[] getQueuedEvents() {
- return mEventListener.getAllEvents();
- }
-
- public TestSensorEvent[] collectEvents(int eventCount, String debugInfo) {
- this.registerListener(debugInfo);
- TestSensorEvent[] events = this.getEvents(eventCount, debugInfo);
- this.unregisterListener();
-
- return events;
- }
-
- public TestSensorEvent[] collectEvents(int eventCount) {
- return this.collectEvents(eventCount, "");
- }
-
- public TestSensorEvent[] collectEvents(long duration, TimeUnit timeUnit) {
- // TODO: Allow this class to support duration as well as event count
- throw new UnsupportedOperationException();
- }
-
- public void startFlush() {
- String message = SensorCtsHelper.formatAssertionMessage(
- "Flush",
- mSensorUnderTest,
- "" /* format */);
- Assert.assertTrue(message, mSensorManager.flush(mEventListener));
- }
-
- public void waitForFlush() throws InterruptedException {
- mEventListener.waitForFlushComplete();
- }
-
- public void flush() throws InterruptedException {
- this.startFlush();
- this.waitForFlush();
- }
-
- /**
- * Definition of support test classes.
- */
- private class TestSensorListener implements SensorEventListener2 {
- private final SensorEventListener2 mListener;
-
- private final ConcurrentLinkedDeque<TestSensorEvent> mSensorEventsList =
- new ConcurrentLinkedDeque<TestSensorEvent>();
-
- private volatile CountDownLatch mEventLatch;
- private volatile CountDownLatch mFlushLatch = new CountDownLatch(1);
-
- public TestSensorListener(SensorEventListener2 listener) {
- if(listener == null) {
- throw new InvalidParameterException("listener cannot be null");
- }
- mListener = listener;
- }
-
- @Override
- public void onSensorChanged(SensorEvent event) {
- // copy the event because there is no better way to do this in the platform
- mSensorEventsList.addLast(new TestSensorEvent(event));
- if(mEventLatch != null) {
- mEventLatch.countDown();
- }
- mListener.onSensorChanged(event);
- }
-
- @Override
- public void onAccuracyChanged(Sensor sensor, int accuracy) {
- mListener.onAccuracyChanged(sensor, accuracy);
- }
-
- @Override
- public void onFlushCompleted(Sensor sensor) {
- CountDownLatch latch = mFlushLatch;
- mFlushLatch = new CountDownLatch(1);
- if(latch != null) {
- latch.countDown();
- }
- mListener.onFlushCompleted(sensor);
- }
-
- public void waitForFlushComplete() throws InterruptedException {
- CountDownLatch latch = mFlushLatch;
- if(latch != null) {
- String message = SensorCtsHelper.formatAssertionMessage(
- "WaitForFlush",
- mSensorUnderTest,
- "" /* format */);
- Assert.assertTrue(message, latch.await(WAIT_TIMEOUT_IN_SECONDS, TimeUnit.SECONDS));
- }
- }
-
- public void waitForEvents(int eventCount, String timeoutInfo) {
- mEventLatch = new CountDownLatch(eventCount);
- this.clearEvents();
- try {
- int rateUs = SensorCtsHelper.getDelay(mSensorUnderTest, mRateUs);
- // Timeout is 2 * event count * expected period + default wait
- int timeoutUs = (int) ((eventCount * rateUs * 2)
- + TimeUnit.MICROSECONDS.convert(WAIT_TIMEOUT_IN_SECONDS, TimeUnit.SECONDS));
- boolean awaitCompleted = mEventLatch.await(timeoutUs, TimeUnit.MICROSECONDS);
- // TODO: can we collect bug reports on error based only if needed? env var?
-
- String message = SensorCtsHelper.formatAssertionMessage(
- "WaitForEvents",
- mSensorUnderTest,
- "count:%d, available:%d, %s",
- eventCount,
- mSensorEventsList.size(),
- timeoutInfo);
- Assert.assertTrue(message, awaitCompleted);
- } catch(InterruptedException e) {
- } finally {
- mEventLatch = null;
- }
- }
-
- public TestSensorEvent[] getAllEvents() {
- return mSensorEventsList.toArray(new TestSensorEvent[0]);
- }
-
- public void clearEvents() {
- mSensorEventsList.clear();
- }
- }
-}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/SensorStats.java b/tests/tests/hardware/src/android/hardware/cts/helpers/SensorStats.java
index 48f8136..6147e50 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/SensorStats.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/SensorStats.java
@@ -17,14 +17,21 @@
package android.hardware.cts.helpers;
import android.hardware.cts.helpers.sensoroperations.ISensorOperation;
+import android.os.Environment;
import android.util.Log;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.Set;
/**
* Class used to store stats related to {@link ISensorOperation}s. Sensor stats may be linked
@@ -33,6 +40,18 @@
public class SensorStats {
public static final String DELIMITER = "__";
+ public static final String FIRST_TIMESTAMP_KEY = "first_timestamp";
+ public static final String LAST_TIMESTAMP_KEY = "last_timestamp";
+ public static final String ERROR = "error";
+ public static final String EVENT_COUNT_KEY = "event_count";
+ public static final String EVENT_OUT_OF_ORDER_COUNT_KEY = "event_out_of_order_count";
+ public static final String EVENT_OUT_OF_ORDER_POSITIONS_KEY = "event_out_of_order_positions";
+ public static final String FREQUENCY_KEY = "frequency";
+ public static final String JITTER_95_PERCENTILE_KEY = "jitter_95_percentile";
+ public static final String MEAN_KEY = "mean";
+ public static final String STANDARD_DEVIATION_KEY = "standard_deviation";
+ public static final String MAGNITUDE_KEY = "magnitude";
+
private final Map<String, Object> mValues = new HashMap<String, Object>();
private final Map<String, SensorStats> mSensorStats = new HashMap<String, SensorStats>();
@@ -64,6 +83,22 @@
}
/**
+ * Get the keys from the values table. Will not get the keys from the nested
+ * {@link SensorStats}.
+ */
+ public synchronized Set<String> getKeys() {
+ return mValues.keySet();
+ }
+
+ /**
+ * Get a value from the values table. Will not attempt to get values from nested
+ * {@link SensorStats}.
+ */
+ public synchronized Object getValue(String key) {
+ return mValues.get(key);
+ }
+
+ /**
* Flattens the map and all sub {@link SensorStats} objects. Keys will be flattened using
* {@value #DELIMITER}. For example, if a sub {@link SensorStats} is added with key
* {@code "key1"} containing the key value pair {@code ("key2", "value")}, the flattened map
@@ -87,15 +122,59 @@
*/
public static void logStats(String tag, SensorStats stats) {
final Map<String, Object> flattened = stats.flatten();
- final List<String> keys = new ArrayList<String>(flattened.keySet());
- Collections.sort(keys);
- for (String key : keys) {
+ for (String key : getSortedKeys(flattened)) {
Object value = flattened.get(key);
- if (value instanceof Double || value instanceof Float) {
- Log.v(tag, String.format("%s: %.4f", key, value));
- } else {
- Log.v(tag, String.format("%s: %s", key, value.toString()));
+ Log.v(tag, String.format("%s: %s", key, getValueString(value)));
+ }
+ }
+
+ /**
+ * Utility method to log the stats to a file. Will overwrite the file if it already exists.
+ */
+ public static void logStatsToFile(String fileName, SensorStats stats) throws IOException {
+ final BufferedWriter writer = new BufferedWriter(new FileWriter(
+ new File(Environment.getExternalStorageDirectory(), fileName), false));
+ final Map<String, Object> flattened = stats.flatten();
+ try {
+ for (String key : getSortedKeys(flattened)) {
+ Object value = flattened.get(key);
+ writer.write(String.format("%s: %s\n", key, getValueString(value)));
}
+ } finally {
+ writer.flush();
+ writer.close();
+ }
+ }
+
+ private static List<String> getSortedKeys(Map<String, Object> flattenedStats) {
+ List<String> keys = new ArrayList<String>(flattenedStats.keySet());
+ Collections.sort(keys);
+ return keys;
+ }
+
+ private static String getValueString(Object value) {
+ if (value == null) {
+ return "";
+ } else if (value instanceof boolean[]) {
+ return Arrays.toString((boolean[]) value);
+ } else if (value instanceof byte[]) {
+ return Arrays.toString((byte[]) value);
+ } else if (value instanceof char[]) {
+ return Arrays.toString((char[]) value);
+ } else if (value instanceof double[]) {
+ return Arrays.toString((double[]) value);
+ } else if (value instanceof float[]) {
+ return Arrays.toString((float[]) value);
+ } else if (value instanceof int[]) {
+ return Arrays.toString((int[]) value);
+ } else if (value instanceof long[]) {
+ return Arrays.toString((long[]) value);
+ } else if (value instanceof short[]) {
+ return Arrays.toString((short[]) value);
+ } else if (value instanceof Object[]) {
+ return Arrays.toString((Object[]) value);
+ } else {
+ return value.toString();
}
}
}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/SensorVerificationHelper.java b/tests/tests/hardware/src/android/hardware/cts/helpers/SensorVerificationHelper.java
deleted file mode 100644
index 964c9d9..0000000
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/SensorVerificationHelper.java
+++ /dev/null
@@ -1,367 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.hardware.cts.helpers;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Set of static helper methods to verify sensor CTS tests.
- */
-public class SensorVerificationHelper {
-
- public static final String EVENT_ORDER_COUNT_KEY = "event_order_count";
- public static final String EVENT_ORDER_POSITIONS_KEY = "event_order_positions";
- public static final String FREQUENCY_KEY = "frequency";
- public static final String JITTER_95_PERCENTILE_KEY = "jitter_95_percentile";
- public static final String MEAN_KEY = "mean";
- public static final String MAGNITUDE_KEY = "magnitude";
- public static final String STANDARD_DEVIATION_KEY = "standard_deviation";
-
- private static final int MESSAGE_LENGTH = 3;
-
- /**
- * Class which holds results from the verification.
- */
- public static class VerificationResult {
- private boolean mFailed = false;
- private String mMessage = null;
- private Map<String, Object> mValueMap = new HashMap<String, Object>();
-
- public void fail(String messageFormat, Object ... args) {
- mFailed = true;
- mMessage = String.format(messageFormat, args);
- }
-
- public boolean isFailed() {
- return mFailed;
- }
-
- public String getFailureMessage() {
- return mMessage;
- }
-
- public void putValue(String key, Object value) {
- mValueMap.put(key, value);
- }
-
- public Set<String> getKeys() {
- return mValueMap.keySet();
- }
-
- public Object getValue(String key) {
- return mValueMap.get(key);
- }
- }
-
- /**
- * Private constructor for static class.
- */
- private SensorVerificationHelper() {}
-
- /**
- * Verify that the events are in the correct order.
- *
- * @param events The array of {@link TestSensorEvent}
- * @return a {@link VerificationResult} containing the verification info including the keys
- * "count" which is the number of events out of order and "positions" which contains an
- * array of indexes that were out of order.
- * @throws IllegalStateException if number of events less than 1.
- */
- public static VerificationResult verifyEventOrdering(TestSensorEvent[] events) {
- VerificationResult result = new VerificationResult();
- List<Integer> indices = new ArrayList<Integer>();
- long maxTimestamp = events[0].timestamp;
- for (int i = 1; i < events.length; i++) {
- long currentTimestamp = events[i].timestamp;
- if (currentTimestamp < maxTimestamp) {
- indices.add(i);
- } else if (currentTimestamp > maxTimestamp) {
- maxTimestamp = currentTimestamp;
- }
- }
-
- result.putValue(EVENT_ORDER_COUNT_KEY, indices.size());
- result.putValue(EVENT_ORDER_POSITIONS_KEY, indices);
-
- if (indices.size() > 0) {
- StringBuilder sb = new StringBuilder();
- sb.append(indices.size()).append(" events out of order: ");
- for (int i = 0; i < Math.min(indices.size(), MESSAGE_LENGTH); i++) {
- int index = indices.get(i);
- sb.append(String.format("position=%d, previous=%d, timestamp=%d; ", index,
- events[index - 1].timestamp, events[index].timestamp));
- }
- if (indices.size() > MESSAGE_LENGTH) {
- sb.append(indices.size() - MESSAGE_LENGTH).append(" more");
- } else {
- // Delete the "; "
- sb.delete(sb.length() - 2, sb.length());
- }
-
- result.fail(sb.toString());
- }
-
- return result;
- }
-
- /**
- * Verify that the sensor frequency matches the expected frequency.
- *
- * @param events The array of {@link TestSensorEvent}
- * @param expected The expected frequency in Hz
- * @param threshold The acceptable margin of error in Hz
- * @return a {@link VerificationResult} containing the verification info including the key
- * "frequency" which is the computed frequency of the events in Hz.
- * @throws IllegalStateException if number of events less than 1.
- */
- public static VerificationResult verifyFrequency(TestSensorEvent[] events, double expected,
- double threshold) {
- VerificationResult result = new VerificationResult();
- List<Long> timestampDelayValues = SensorCtsHelper.getTimestampDelayValues(events);
- double frequency = SensorCtsHelper.getFrequency(
- SensorCtsHelper.getMean(timestampDelayValues), TimeUnit.NANOSECONDS);
- result.putValue(FREQUENCY_KEY, frequency);
-
- if (Math.abs(frequency - expected) > threshold) {
- result.fail("Frequency out of range: frequency=%.2fHz, expected=%.2f+/-%.2fHz",
- frequency, expected, threshold);
- }
- return result;
- }
-
- /**
- * Verify that the jitter is in an acceptable range
- *
- * @param events The array of {@link TestSensorEvent}
- * @param expected the expected period in ns
- * @param threshold The acceptable margin of error as a percentage
- * @return a {@link VerificationResult} containing the verification info including the keys
- * "jitter" which is the list of computed jitter values and "jitter95Percentile" which is
- * 95th percentile of the jitter values.
- * @throws IllegalStateException if number of events less than 2.
- */
- public static VerificationResult verifyJitter(TestSensorEvent[] events, int expected,
- int threshold) {
- VerificationResult result = new VerificationResult();
- List<Double> jitterValues = SensorCtsHelper.getJitterValues(events);
- double jitter95Percentile = SensorCtsHelper.get95PercentileValue(jitterValues);
- result.putValue(JITTER_95_PERCENTILE_KEY, jitter95Percentile);
-
- if (jitter95Percentile > expected * (threshold / 100.0)) {
- result.fail("Jitter out of range: jitter at 95th percentile=%.0fns, expected=<%.0fns",
- jitter95Percentile, expected * (threshold / 100.0));
- }
- return result;
- }
-
- /**
- * Verify that the means matches the expected measurement.
- *
- * @param events The array of {@link TestSensorEvent}
- * @param expected The array of expected values
- * @param threshold The array of thresholds
- * @return a {@link VerificationResult} containing the verification info including the key
- * "mean" which is the computed means for each value of the sensor.
- * @throws IllegalStateException if number of events less than 1.
- */
- public static VerificationResult verifyMean(TestSensorEvent[] events, float[] expected,
- float[] threshold) {
- VerificationResult result = new VerificationResult();
- Float[] means = SensorCtsHelper.getMeans(events);
- result.putValue(MEAN_KEY, Arrays.asList(means));
-
- boolean failed = false;
- StringBuilder meanSb = new StringBuilder();
- StringBuilder expectedSb = new StringBuilder();
-
- if (means.length > 1) {
- meanSb.append("(");
- expectedSb.append("(");
- }
- for (int i = 0; i < means.length; i++) {
- if (Math.abs(means[i] - expected[i]) > threshold[i]) {
- failed = true;
- }
- meanSb.append(String.format("%.2f", means[i]));
- if (i != means.length - 1) meanSb.append(", ");
- expectedSb.append(String.format("%.2f+/-%.2f", expected[i], threshold[i]));
- if (i != means.length - 1) expectedSb.append(", ");
- }
- if (means.length > 1) {
- meanSb.append(")");
- expectedSb.append(")");
- }
-
- if (failed) {
- result.fail("Mean out of range: mean=%s, expected=%s",
- meanSb.toString(), expectedSb.toString());
- }
- return result;
- }
-
- /**
- * Verify that the mean of the magnitude of the sensors vector is within the expected range.
- *
- * @param events The array of {@link TestSensorEvent}
- * @param expected The expected value
- * @param threshold The threshold
- * @return a {@link VerificationResult} containing the verification info including the key
- * "magnitude" which is the mean of the computed magnitude of the sensor values.
- * @throws IllegalStateException if number of events less than 1.
- */
- public static VerificationResult verifyMagnitude(TestSensorEvent[] events, float expected,
- float threshold) {
- VerificationResult result = new VerificationResult();
- Collection<Float> magnitudes = new ArrayList<Float>(events.length);
-
- for (TestSensorEvent event : events) {
- float sumOfSquares = 0;
- for (int i = 0; i < event.values.length; i++) {
- sumOfSquares += event.values[i] * event.values[i];
- }
- magnitudes.add((float) Math.sqrt(sumOfSquares));
- }
-
- float mean = (float) SensorCtsHelper.getMean(magnitudes);
- result.putValue(MAGNITUDE_KEY, mean);
-
- if (Math.abs(mean - expected) > threshold) {
- result.fail(String.format("Magnitude mean out of range: mean=%s, expected=%s+/-%s",
- mean, expected, threshold));
- }
- return result;
- }
-
- /**
- * Verify that the sign of each of the sensor values is correct.
- * <p>
- * If the value of the measurement is in [-threshold, threshold], the sign is considered 0. If
- * it is less than -threshold, it is considered -1. If it is greater than threshold, it is
- * considered 1.
- * </p>
- *
- * @param events
- * @param threshold The threshold that needs to be crossed to consider a measurement nonzero
- * @return a {@link VerificationResult} containing the verification info including the key
- * "mean" which is the computed means for each value of the sensor.
- * @throws IllegalStateException if number of events less than 1.
- */
- public static VerificationResult verifySignum(TestSensorEvent[] events, int[] expected,
- float[] threshold) {
- VerificationResult result = new VerificationResult();
- for (int i = 0; i < expected.length; i++) {
- if (!(expected[i] == -1 || expected[i] == 0 || expected[i] == 1)) {
- throw new IllegalArgumentException("Expected value must be -1, 0, or 1");
- }
- }
- Float[] means = SensorCtsHelper.getMeans(events);
- result.putValue(MEAN_KEY, Arrays.asList(means));
-
- boolean failed = false;
- StringBuilder meanSb = new StringBuilder();
- StringBuilder expectedSb = new StringBuilder();
-
- if (means.length > 1) {
- meanSb.append("(");
- expectedSb.append("(");
- }
- for (int i = 0; i < means.length; i++) {
- meanSb.append(String.format("%.2f", means[i]));
- if (i != means.length - 1) meanSb.append(", ");
-
- if (expected[i] == 0) {
- if (Math.abs(means[i]) > threshold[i]) {
- failed = true;
- }
- expectedSb.append(String.format("[%.2f, %.2f]", -threshold[i], threshold[i]));
- } else {
- if (expected[i] > 0) {
- if (means[i] <= threshold[i]) {
- failed = true;
- }
- expectedSb.append(String.format("(%.2f, inf)", threshold[i]));
- } else {
- if (means[i] >= -1 * threshold[i]) {
- failed = true;
- }
- expectedSb.append(String.format("(-inf, %.2f)", -1 * threshold[i]));
- }
- }
- if (i != means.length - 1) expectedSb.append(", ");
- }
- if (means.length > 1) {
- meanSb.append(")");
- expectedSb.append(")");
- }
-
- if (failed) {
- result.fail("Signum out of range: mean=%s, expected=%s",
- meanSb.toString(), expectedSb.toString());
- }
- return result;
- }
-
- /**
- * Verify that the standard deviations is within the expected range.
- *
- * @param events The array of {@link TestSensorEvent}
- * @param threshold The array of thresholds
- * @return a {@link VerificationResult} containing the verification info including the key
- * "stddevs" which is the computed standard deviations for each value of the sensor.
- * @throws IllegalStateException if number of events less than 1.
- */
- public static VerificationResult verifyStandardDeviation(TestSensorEvent[] events,
- float[] threshold) {
- VerificationResult result = new VerificationResult();
- Float[] standardDeviations = SensorCtsHelper.getStandardDeviations(events);
- result.putValue(STANDARD_DEVIATION_KEY, Arrays.asList(standardDeviations));
-
- boolean failed = false;
- StringBuilder stddevSb = new StringBuilder();
- StringBuilder expectedSb = new StringBuilder();
-
- if (standardDeviations.length > 1) {
- stddevSb.append("(");
- expectedSb.append("(");
- }
- for (int i = 0; i < standardDeviations.length; i++) {
- if (standardDeviations[i] > threshold[i]) {
- failed = true;
- }
- stddevSb.append(String.format("%.2f", standardDeviations[i]));
- if (i != standardDeviations.length - 1) stddevSb.append(", ");
- expectedSb.append(String.format("<%.2f", threshold[i]));
- if (i != standardDeviations.length - 1) expectedSb.append(", ");
- }
- if (standardDeviations.length > 1) {
- stddevSb.append(")");
- expectedSb.append(")");
- }
-
- if (failed) {
- result.fail("Standard deviation out of range: stddev=%s, expected=%s",
- stddevSb.toString(), expectedSb.toString());
- }
- return result;
- }
-}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/SensorVerificationHelperTest.java b/tests/tests/hardware/src/android/hardware/cts/helpers/SensorVerificationHelperTest.java
deleted file mode 100644
index 5cd8dbc..0000000
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/SensorVerificationHelperTest.java
+++ /dev/null
@@ -1,290 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.hardware.cts.helpers;
-
-import android.hardware.cts.helpers.SensorVerificationHelper.VerificationResult;
-
-import junit.framework.TestCase;
-
-import java.util.List;
-
-/**
- * Unit tests for the {@link SensorVerificationHelper} class.
- */
-public class SensorVerificationHelperTest extends TestCase {
-
- /**
- * Test {@link SensorVerificationHelper#verifyEventOrdering(TestSensorEvent[])}.
- */
- @SuppressWarnings("unchecked")
- public void testVerifyEventOrdering() {
- float[] values = {0, 1, 2, 3, 4};
-
- long[] timestamps1 = {0, 0, 0, 0, 0};
- TestSensorEvent[] events1 = getSensorEvents(timestamps1, values);
- VerificationResult result = SensorVerificationHelper.verifyEventOrdering(events1);
- assertFalse(result.isFailed());
- assertEquals(0, result.getValue(SensorVerificationHelper.EVENT_ORDER_COUNT_KEY));
-
- long[] timestamps2 = {0, 1, 2, 3, 4};
- TestSensorEvent[] events2 = getSensorEvents(timestamps2, values);
- result = SensorVerificationHelper.verifyEventOrdering(events2);
- assertFalse(result.isFailed());
- assertEquals(0, result.getValue(SensorVerificationHelper.EVENT_ORDER_COUNT_KEY));
-
- long[] timestamps3 = {0, 2, 1, 3, 4};
- TestSensorEvent[] events3 = getSensorEvents(timestamps3, values);
- result = SensorVerificationHelper.verifyEventOrdering(events3);
- assertTrue(result.isFailed());
- assertEquals(1, result.getValue(SensorVerificationHelper.EVENT_ORDER_COUNT_KEY));
- List<Integer> indices = (List<Integer>) result.getValue(
- SensorVerificationHelper.EVENT_ORDER_POSITIONS_KEY);
- assertTrue(indices.contains(2));
-
- long[] timestamps4 = {4, 0, 1, 2, 3};
- TestSensorEvent[] events4 = getSensorEvents(timestamps4, values);
- result = SensorVerificationHelper.verifyEventOrdering(events4);
- assertTrue(result.isFailed());
- assertEquals(4, result.getValue(SensorVerificationHelper.EVENT_ORDER_COUNT_KEY));
- indices = (List<Integer>) result.getValue(
- SensorVerificationHelper.EVENT_ORDER_POSITIONS_KEY);
- assertTrue(indices.contains(1));
- assertTrue(indices.contains(2));
- assertTrue(indices.contains(3));
- assertTrue(indices.contains(4));
- }
-
- /**
- * Test {@link SensorVerificationHelper#verifyFrequency(TestSensorEvent[], double, double)}.
- */
- public void testVerifyFrequency() {
- float[] values = {0, 1, 2, 3, 4};
- long[] timestamps = {0, 1000000, 2000000, 3000000, 4000000}; // 1000Hz
- TestSensorEvent[] events = getSensorEvents(timestamps, values);
-
- VerificationResult result = SensorVerificationHelper.verifyFrequency(events, 1000.0, 1.0);
- assertFalse(result.isFailed());
- assertEquals(1000.0, (Double) result.getValue("frequency"), 0.01);
-
- result = SensorVerificationHelper.verifyFrequency(events, 950.0, 100.0);
- assertFalse(result.isFailed());
- assertEquals(1000.0, (Double) result.getValue("frequency"), 0.01);
-
- result = SensorVerificationHelper.verifyFrequency(events, 1050.0, 100.0);
- assertFalse(result.isFailed());
- assertEquals(1000.0, (Double) result.getValue("frequency"), 0.01);
-
- result = SensorVerificationHelper.verifyFrequency(events, 950.0, 25.0);
- assertTrue(result.isFailed());
- assertEquals(1000.0, (Double) result.getValue("frequency"), 0.01);
- }
-
- /**
- * Test {@link SensorVerificationHelper#verifyJitter(TestSensorEvent[], int, int)}.
- */
- public void testVerifyJitter() {
- final int SAMPLE_SIZE = 100;
- float[] values = new float[SAMPLE_SIZE];
- for (int i = 0; i < SAMPLE_SIZE; i++) {
- values[i] = i;
- }
-
- long[] timestamps1 = new long[SAMPLE_SIZE]; // 100 samples at 1000Hz
- for (int i = 0; i < SAMPLE_SIZE; i++) {
- timestamps1[i] = i * 100000;
- }
- TestSensorEvent[] events1 = getSensorEvents(timestamps1, values);
- VerificationResult result = SensorVerificationHelper.verifyJitter(events1, 1000, 10);
- assertFalse(result.isFailed());
- Double jitter95 = (Double) result.getValue(
- SensorVerificationHelper.JITTER_95_PERCENTILE_KEY);
- assertEquals(0.0, jitter95, 0.01);
-
- long[] timestamps2 = new long[SAMPLE_SIZE]; // 90 samples at 1000Hz, 10 samples at 2000Hz
- long timestamp = 0;
- for (int i = 0; i < SAMPLE_SIZE; i++) {
- timestamps2[i] = timestamp;
- timestamp += (i % 10 == 0) ? 500000 : 1000000;
- }
- TestSensorEvent[] events2 = getSensorEvents(timestamps2, values);
- result = SensorVerificationHelper.verifyJitter(events2, 1000, 10);
- assertTrue(result.isFailed());
- assertNotNull(result.getValue(SensorVerificationHelper.JITTER_95_PERCENTILE_KEY));
- }
-
- /**
- * Test {@link SensorVerificationHelper#verifyMean(TestSensorEvent[], float[], float[])}.
- */
- public void testVerifyMean() {
- long[] timestamps = {0, 1, 2, 3, 4};
- float[] values1 = {0, 1, 2, 3, 4};
- float[] values2 = {1, 2, 3, 4, 5};
- float[] values3 = {0, 1, 4, 9, 16};
- TestSensorEvent[] events = getSensorEvents(timestamps, values1, values2, values3);
-
- float[] expected1 = {2.0f, 3.0f, 6.0f};
- float[] threshold1 = {0.1f, 0.1f, 0.1f};
- VerificationResult result = SensorVerificationHelper.verifyMean(events, expected1,
- threshold1);
- assertFalse(result.isFailed());
- @SuppressWarnings("unchecked")
- List<Float> means = (List<Float>) result.getValue(SensorVerificationHelper.MEAN_KEY);
- assertEquals(2.0f, means.get(0), 0.01);
- assertEquals(3.0f, means.get(1), 0.01);
- assertEquals(6.0f, means.get(2), 0.01);
-
- float[] expected = {2.5f, 2.5f, 5.5f};
- float[] threshold = {0.6f, 0.6f, 0.6f};
- result = SensorVerificationHelper.verifyMean(events, expected, threshold);
- assertFalse(result.isFailed());
-
- expected = new float[]{2.5f, 2.5f, 5.5f};
- threshold = new float[]{0.1f, 0.6f, 0.6f};
- result = SensorVerificationHelper.verifyMean(events, expected, threshold);
- assertTrue(result.isFailed());
-
- expected = new float[]{2.5f, 2.5f, 5.5f};
- threshold = new float[]{0.6f, 0.1f, 0.6f};
- result = SensorVerificationHelper.verifyMean(events, expected, threshold);
- assertTrue(result.isFailed());
-
- threshold = new float[]{2.5f, 2.5f, 5.5f};
- threshold = new float[]{0.6f, 0.6f, 0.1f};
- result = SensorVerificationHelper.verifyMean(events, expected, threshold);
- assertTrue(result.isFailed());
- }
-
- /**
- * Test {@link SensorVerificationHelper#verifyMagnitude(TestSensorEvent[], float, float)}.
- */
- public void testVerifyMagnitude() {
- long[] timestamps = {0, 1, 2, 3, 4};
- float[] values1 = {0, 4, 3, 0, 6};
- float[] values2 = {3, 0, 4, 0, 0};
- float[] values3 = {4, 3, 0, 4, 0};
- TestSensorEvent[] events = getSensorEvents(timestamps, values1, values2, values3);
-
- float expected = 5.0f;
- float threshold = 0.1f;
- VerificationResult result = SensorVerificationHelper.verifyMagnitude(events, expected,
- threshold);
- assertFalse(result.isFailed());
- assertEquals(5.0f, (Float) result.getValue(SensorVerificationHelper.MAGNITUDE_KEY), 0.01);
-
- expected = 4.5f;
- threshold = 0.6f;
- result = SensorVerificationHelper.verifyMagnitude(events, expected, threshold);
- assertFalse(result.isFailed());
-
- expected = 5.5f;
- threshold = 0.6f;
- result = SensorVerificationHelper.verifyMagnitude(events, expected, threshold);
- assertFalse(result.isFailed());
-
- expected = 4.5f;
- threshold = 0.1f;
- result = SensorVerificationHelper.verifyMagnitude(events, expected, threshold);
- assertTrue(result.isFailed());
-
- expected = 5.5f;
- threshold = 0.1f;
- result = SensorVerificationHelper.verifyMagnitude(events, expected, threshold);
- assertTrue(result.isFailed());
- }
-
- /**
- * Test {@link SensorVerificationHelper#verifySignum(TestSensorEvent[], int[], float[])}.
- */
- public void testVerifySignum() {
- long[] timestamps = {0};
- float[][] values = {{1}, {0.2f}, {0}, {-0.2f}, {-1}};
- TestSensorEvent[] events = getSensorEvents(timestamps, values);
-
- int[] expected = {1, 1, 0, -1, -1};
- float[] threshold = {0.1f, 0.1f, 0.1f, 0.1f, 0.1f};
- VerificationResult result = SensorVerificationHelper.verifySignum(events, expected,
- threshold);
- assertFalse(result.isFailed());
- assertNotNull(result.getValue(SensorVerificationHelper.MEAN_KEY));
-
- expected = new int[]{1, 0, 0, 0, -1};
- threshold = new float[]{0.5f, 0.5f, 0.5f, 0.5f, 0.5f};
- result = SensorVerificationHelper.verifySignum(events, expected, threshold);
- assertFalse(result.isFailed());
-
- expected = new int[]{0, 1, 0, -1, 0};
- threshold = new float[]{1.5f, 0.1f, 0.1f, 0.1f, 1.5f};
- result = SensorVerificationHelper.verifySignum(events, expected, threshold);
- assertFalse(result.isFailed());
-
- expected = new int[]{1, 0, 0, 0, 1};
- threshold = new float[]{0.5f, 0.5f, 0.5f, 0.5f, 0.5f};
- result = SensorVerificationHelper.verifySignum(events, expected, threshold);
- assertTrue(result.isFailed());
-
- expected = new int[]{-1, 0, 0, 0, -1};
- threshold = new float[]{0.5f, 0.5f, 0.5f, 0.5f, 0.5f};
- result = SensorVerificationHelper.verifySignum(events, expected, threshold);
- assertTrue(result.isFailed());
- }
-
- /**
- * Test {@link SensorVerificationHelper#verifyStandardDeviation(TestSensorEvent[], float[])}.
- */
- public void testVerifyStandardDeviation() {
- long[] timestamps = {0, 1, 2, 3, 4};
- float[] values1 = {0, 1, 2, 3, 4}; // sqrt(2.5)
- float[] values2 = {1, 2, 3, 4, 5}; // sqrt(2.5)
- float[] values3 = {0, 2, 4, 6, 8}; // sqrt(10.0)
- TestSensorEvent[] events = getSensorEvents(timestamps, values1, values2, values3);
-
- float[] threshold = {2, 2, 4};
- VerificationResult result = SensorVerificationHelper.verifyStandardDeviation(events,
- threshold);
- assertFalse(result.isFailed());
- @SuppressWarnings("unchecked")
- List<Float> stddevs = (List<Float>) result.getValue(
- SensorVerificationHelper.STANDARD_DEVIATION_KEY);
- assertEquals(Math.sqrt(2.5), stddevs.get(0), 0.01);
- assertEquals(Math.sqrt(2.5), stddevs.get(1), 0.01);
- assertEquals(Math.sqrt(10.0), stddevs.get(2), 0.01);
-
- threshold = new float[]{1, 2, 4};
- result = SensorVerificationHelper.verifyStandardDeviation(events, threshold);
- assertTrue(result.isFailed());
-
- threshold = new float[]{2, 1, 4};
- result = SensorVerificationHelper.verifyStandardDeviation(events, threshold);
- assertTrue(result.isFailed());
-
- threshold = new float[]{2, 2, 3};
- result = SensorVerificationHelper.verifyStandardDeviation(events, threshold);
- assertTrue(result.isFailed());
- }
-
- private TestSensorEvent[] getSensorEvents(long[] timestamps, float[] ... values) {
- TestSensorEvent[] events = new TestSensorEvent[timestamps.length];
- for (int i = 0; i < timestamps.length; i++) {
- float[] eventValues = new float[values.length];
- for (int j = 0; j < values.length; j++) {
- eventValues[j] = values[j][i];
- }
- events[i] = new TestSensorEvent(null, timestamps[i], 0, eventValues);
- }
- return events;
- }
-}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/TestSensorEvent.java b/tests/tests/hardware/src/android/hardware/cts/helpers/TestSensorEvent.java
index 48bc1d3..b349e1b 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/TestSensorEvent.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/TestSensorEvent.java
@@ -18,33 +18,51 @@
import android.hardware.Sensor;
import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener2;
/**
- * Test class to wrap SensorEvent.
- * It currently only provides a way to clone SensorEvent data, but in the future it can contain
- * verifications and test checks.
+ * Class for holding information about individual {@link SensorEvent}s.
*/
public class TestSensorEvent {
public final Sensor sensor;
public final long timestamp;
+ public final long receivedTimestamp;
public final int accuracy;
public final float values[];
- public TestSensorEvent(SensorEvent event) {
+ /**
+ * Construct a TestSensorEvent from {@link SensorEvent} data and a received timestamp.
+ *
+ * @param event the {@link SensorEvent} to be cloned
+ * @param receivedTimestamp the timestamp when
+ * {@link SensorEventListener2#onSensorChanged(SensorEvent)} was called, in nanoseconds.
+ */
+ public TestSensorEvent(SensorEvent event, long receivedTimestamp) {
values = new float[event.values.length];
- System.arraycopy(event.values, 0, values, 0, event.values.length);
+ System.arraycopy(event.values, 0, values, 0, values.length);
sensor = event.sensor;
timestamp = event.timestamp;
accuracy = event.accuracy;
+
+ this.receivedTimestamp = receivedTimestamp;
}
/**
* Constructor for TestSensorEvent. Exposed for unit testing.
*/
- protected TestSensorEvent(Sensor sensor, long timestamp, int accuracy, float[] values) {
+ public TestSensorEvent(Sensor sensor, long timestamp, int accuracy, float[] values) {
+ this(sensor, timestamp, timestamp, accuracy, values);
+ }
+
+ /**
+ * Constructor for TestSensorEvent. Exposed for unit testing.
+ */
+ public TestSensorEvent(Sensor sensor, long timestamp, long receivedTimestamp, int accuracy,
+ float[] values) {
this.sensor = sensor;
this.timestamp = timestamp;
+ this.receivedTimestamp = receivedTimestamp;
this.accuracy = accuracy;
this.values = values;
}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/TestSensorEventListener.java b/tests/tests/hardware/src/android/hardware/cts/helpers/TestSensorEventListener.java
new file mode 100644
index 0000000..ddbc8c2
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/TestSensorEventListener.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.cts.helpers;
+
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener2;
+import android.util.Log;
+
+import junit.framework.Assert;
+
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A {@link SensorEventListener2} which performs operations such as waiting for a specific number of
+ * events or for a specific time, or waiting for a flush to complete. This class performs
+ * verifications and will throw {@link AssertionError}s if there are any errors. It may also wrap
+ * another {@link SensorEventListener2}.
+ */
+public class TestSensorEventListener implements SensorEventListener2 {
+ public static final String LOG_TAG = "TestSensorEventListener";
+ private static final long EVENT_TIMEOUT_US = TimeUnit.MICROSECONDS.convert(5, TimeUnit.SECONDS);
+ private static final long FLUSH_TIMEOUT_US = TimeUnit.MICROSECONDS.convert(5, TimeUnit.SECONDS);
+
+ private final SensorEventListener2 mListener;
+
+ private volatile CountDownLatch mEventLatch = null;
+ private volatile CountDownLatch mFlushLatch = new CountDownLatch(1);
+
+ private Sensor mSensor = null;
+ private int mRateUs = 0;
+ private int mMaxBatchReportLatencyUs = 0;
+ private boolean mLogEvents = false;
+
+ /**
+ * Construct a {@link TestSensorEventListener}.
+ */
+ public TestSensorEventListener() {
+ this(null);
+ }
+
+ /**
+ * Construct a {@link TestSensorEventListener} that wraps a {@link SensorEventListener2}.
+ */
+ public TestSensorEventListener(SensorEventListener2 listener) {
+ mListener = listener;
+ }
+
+ /**
+ * Set the sensor, rate, and batch report latency used for the assertions.
+ */
+ public void setSensorInfo(Sensor sensor, int rateUs, int maxBatchReportLatencyUs) {
+ mSensor = sensor;
+ mRateUs = rateUs;
+ mMaxBatchReportLatencyUs = maxBatchReportLatencyUs;
+ }
+
+ /**
+ * Set whether or not to log events
+ */
+ public void setLogEvents(boolean log) {
+ mLogEvents = log;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ if(mEventLatch != null) {
+ mEventLatch.countDown();
+ }
+ if (mListener != null) {
+ mListener.onSensorChanged(event);
+ }
+ if (mLogEvents) {
+ StringBuilder valuesSb = new StringBuilder();
+ if (event.values.length == 1) {
+ valuesSb.append(String.format("%.2f", event.values[0]));
+ } else {
+ valuesSb.append("[").append(String.format("%.2f", event.values[0]));
+ for (int i = 1; i < event.values.length; i++) {
+ valuesSb.append(String.format(", %.2f", event.values[i]));
+ }
+ valuesSb.append("]");
+ }
+
+ Log.v(LOG_TAG, String.format(
+ "Sensor %d: sensor_timestamp=%d, received_timestamp=%d, values=%s",
+ mSensor.getType(), event.timestamp, System.nanoTime(),
+ Arrays.toString(event.values)));
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ if (mListener != null) {
+ mListener.onAccuracyChanged(sensor, accuracy);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onFlushCompleted(Sensor sensor) {
+ CountDownLatch latch = mFlushLatch;
+ mFlushLatch = new CountDownLatch(1);
+ if(latch != null) {
+ latch.countDown();
+ }
+ if (mListener != null) {
+ mListener.onFlushCompleted(sensor);
+ }
+ }
+
+ /**
+ * Wait for {@link #onFlushCompleted(Sensor)} to be called.
+ *
+ * @throws AssertionError if there was a timeout after {@value #FLUSH_TIMEOUT_US} µs
+ */
+ public void waitForFlushComplete() {
+ CountDownLatch latch = mFlushLatch;
+ try {
+ if(latch != null) {
+ String message = SensorCtsHelper.formatAssertionMessage(mSensor, "WaitForFlush",
+ mRateUs, mMaxBatchReportLatencyUs);
+ Assert.assertTrue(message, latch.await(FLUSH_TIMEOUT_US, TimeUnit.MICROSECONDS));
+ }
+ } catch(InterruptedException e) {
+ // Ignore
+ }
+ }
+
+ /**
+ * Collect a specific number of {@link TestSensorEvent}s.
+ *
+ * @throws AssertionError if there was a timeout after {@value #FLUSH_TIMEOUT_US} µs
+ */
+ public void waitForEvents(int eventCount) {
+ mEventLatch = new CountDownLatch(eventCount);
+ try {
+ int rateUs = SensorCtsHelper.getDelay(mSensor, mRateUs);
+ // Timeout is 2 * event count * expected period + batch timeout + default wait
+ long timeoutUs = ((2 * eventCount * rateUs)
+ + mMaxBatchReportLatencyUs + EVENT_TIMEOUT_US);
+
+ String message = SensorCtsHelper.formatAssertionMessage(mSensor, "WaitForEvents",
+ mRateUs, mMaxBatchReportLatencyUs, "count:%d, available:%d", eventCount,
+ mEventLatch.getCount());
+ Assert.assertTrue(message, mEventLatch.await(timeoutUs, TimeUnit.MICROSECONDS));
+ } catch(InterruptedException e) {
+ // Ignore
+ } finally {
+ mEventLatch = null;
+ }
+ }
+
+ /**
+ * Collect {@link TestSensorEvent} for a specific duration.
+ */
+ public void waitForEvents(long duration, TimeUnit timeUnit) {
+ SensorCtsHelper.sleep(duration, timeUnit);
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/TestSensorManager.java b/tests/tests/hardware/src/android/hardware/cts/helpers/TestSensorManager.java
new file mode 100644
index 0000000..a45ad70
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/TestSensorManager.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.cts.helpers;
+
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorEventListener2;
+import android.hardware.SensorManager;
+import android.util.Log;
+
+import junit.framework.Assert;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A test class that performs the actions of {@link SensorManager} on a single sensor. This
+ * class allows for a single sensor to be registered and unregistered as well as performing
+ * operations such as flushing the sensor events and gathering events. This class also manages
+ * performing the test verifications for the sensor manager.
+ * <p>
+ * This class requires that operations are performed in the following order:
+ * <p><ul>
+ * <li>{@link #registerListener(TestSensorEventListener)}</li>
+ * <li>{@link #startFlush()}, {@link #waitForFlushCompleted()}, or {@link #flush()}.
+ * <li>{@link #unregisterListener()}</li>
+ * </ul><p>Or:</p><ul>
+ * <li>{@link #runSensor(TestSensorEventListener, int)}</li>
+ * </ul><p>Or:</p><ul>
+ * <li>{@link #runSensor(TestSensorEventListener, long, TimeUnit)}</li>
+ * </ul><p>
+ * If methods are called outside of this order, they will print a warning to the log and then
+ * return. Both {@link #runSensor(TestSensorEventListener, int)}} and
+ * {@link #runSensor(TestSensorEventListener, long, TimeUnit)} will perform the appropriate
+ * set up and tear down.
+ * <p>
+ */
+public class TestSensorManager {
+ private static final String LOG_TAG = "TestSensorManager";
+
+ private final SensorManager mSensorManager;
+ private final Sensor mSensor;
+ private final int mRateUs;
+ private final int mMaxBatchReportLatencyUs;
+
+ private TestSensorEventListener mTestSensorEventListener = null;
+
+ /**
+ * Construct a {@link TestSensorManager}.
+ */
+ public TestSensorManager(Context context, int sensorType, int rateUs,
+ int maxBatchReportLatencyUs) {
+ mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
+ mSensor = SensorCtsHelper.getSensor(context, sensorType);
+ mRateUs = rateUs;
+ mMaxBatchReportLatencyUs = maxBatchReportLatencyUs;
+ }
+
+ /**
+ * Register the listener. This method will perform a no-op if the sensor is already registered.
+ *
+ * @throws AssertionError if there was an error registering the listener with the
+ * {@link SensorManager}
+ */
+ public void registerListener(TestSensorEventListener listener) {
+ if (mTestSensorEventListener != null) {
+ Log.w(LOG_TAG, "Listener already registered, returning.");
+ return;
+ }
+
+ mTestSensorEventListener = listener != null ? listener : new TestSensorEventListener();
+ mTestSensorEventListener.setSensorInfo(mSensor, mRateUs, mMaxBatchReportLatencyUs);
+
+ String message = SensorCtsHelper.formatAssertionMessage(mSensor, "registerListener",
+ mRateUs, mMaxBatchReportLatencyUs);
+ boolean result = mSensorManager.registerListener(mTestSensorEventListener, mSensor, mRateUs,
+ mMaxBatchReportLatencyUs);
+ Assert.assertTrue(message, result);
+ }
+
+ /**
+ * Unregister the listener. This method will perform a no-op if the sensor is not registered.
+ */
+ public void unregisterListener() {
+ if (mTestSensorEventListener == null) {
+ Log.w(LOG_TAG, "No listener registered, returning.");
+ return;
+ }
+
+ mSensorManager.unregisterListener(mTestSensorEventListener, mSensor);
+ mTestSensorEventListener = null;
+ }
+
+ /**
+ * Wait for a specific number of events.
+ */
+ public void waitForEvents(int eventCount) {
+ if (mTestSensorEventListener == null) {
+ Log.w(LOG_TAG, "No listener registered, returning.");
+ return;
+ }
+
+ mTestSensorEventListener.waitForEvents(eventCount);
+ }
+
+ /**
+ * Wait for a specific duration.
+ */
+ public void waitForEvents(long duration, TimeUnit timeUnit) {
+ if (mTestSensorEventListener == null) {
+ Log.w(LOG_TAG, "No listener registered, returning.");
+ return;
+ }
+
+ mTestSensorEventListener.waitForEvents(duration, timeUnit);
+ }
+
+ /**
+ * Call {@link SensorManager#flush(SensorEventListener)}. This method will perform a no-op if
+ * the sensor is not registered.
+ *
+ * @throws AssertionError if {@link SensorManager#flush(SensorEventListener)} returns false
+ */
+ public void startFlush() {
+ if (mTestSensorEventListener == null) {
+ return;
+ }
+
+ String message = SensorCtsHelper.formatAssertionMessage(mSensor, "Flush", mRateUs,
+ mMaxBatchReportLatencyUs);
+ Assert.assertTrue(message, mSensorManager.flush(mTestSensorEventListener));
+ }
+
+ /**
+ * Wait for {@link SensorEventListener2#onFlushCompleted(Sensor)} to be called. This method will
+ * perform a no-op if the sensor is not registered.
+ *
+ * @throws AssertionError if there is a time out
+ * @throws InterruptedException if the thread was interrupted
+ */
+ public void waitForFlushCompleted() throws InterruptedException {
+ if (mTestSensorEventListener == null) {
+ return;
+ }
+
+ mTestSensorEventListener.waitForFlushComplete();
+ }
+
+ /**
+ * Call {@link SensorManager#flush(SensorEventListener)} and wait for
+ * {@link SensorEventListener2#onFlushCompleted(Sensor)} to be called. This method will perform
+ * a no-op if the sensor is not registered.
+ *
+ * @throws AssertionError if {@link SensorManager#flush(SensorEventListener)} returns false or
+ * if there is a time out
+ * @throws InterruptedException if the thread was interrupted
+ */
+ public void flush() throws InterruptedException {
+ if (mTestSensorEventListener == null) {
+ return;
+ }
+
+ startFlush();
+ waitForFlushCompleted();
+ }
+
+ /**
+ * Register a listener, wait for a specific number of events, and then unregister the listener.
+ */
+ public void runSensor(TestSensorEventListener listener, int eventCount) {
+ if (mTestSensorEventListener != null) {
+ Log.w(LOG_TAG, "Listener already registered, returning.");
+ return;
+ }
+
+ try {
+ registerListener(listener);
+ waitForEvents(eventCount);
+ } finally {
+ unregisterListener();
+ }
+ }
+
+ /**
+ * Register a listener, wait for a specific duration, and then unregister the listener.
+ */
+ public void runSensor(TestSensorEventListener listener, long duration, TimeUnit timeUnit) {
+ if (mTestSensorEventListener != null) {
+ Log.w(LOG_TAG, "Listener already registered, returning.");
+ return;
+ }
+
+ try {
+ registerListener(listener);
+ waitForEvents(duration, timeUnit);
+ } finally {
+ unregisterListener();
+ }
+ }
+
+ /**
+ * Get the sensor under test.
+ */
+ public Sensor getSensor() {
+ return mSensor;
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/ValidatingSensorEventListener.java b/tests/tests/hardware/src/android/hardware/cts/helpers/ValidatingSensorEventListener.java
new file mode 100644
index 0000000..ae7ea04
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/ValidatingSensorEventListener.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.cts.helpers;
+
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener2;
+import android.hardware.cts.helpers.sensorverification.ISensorVerification;
+import android.os.SystemClock;
+
+import java.util.Collection;
+import java.util.LinkedList;
+
+/**
+ * A {@link TestSensorEventListener} which performs validations on the received events on the fly.
+ * This class is useful for long running tests where it is not practical to store all the events to
+ * be processed after.
+ */
+public class ValidatingSensorEventListener extends TestSensorEventListener {
+
+ private final Collection<ISensorVerification> mVerifications =
+ new LinkedList<ISensorVerification>();
+
+ /**
+ * Construct a {@link ValidatingSensorEventListener} with an additional
+ * {@link SensorEventListener2}.
+ */
+ public ValidatingSensorEventListener(SensorEventListener2 listener,
+ ISensorVerification ... verifications) {
+ super(listener);
+ for (ISensorVerification verification : verifications) {
+ mVerifications.add(verification);
+ }
+ }
+
+ /**
+ * Construct a {@link ValidatingSensorEventListener} with an additional
+ * {@link SensorEventListener2}.
+ */
+ public ValidatingSensorEventListener(SensorEventListener2 listener,
+ Collection<ISensorVerification> verifications) {
+ this(listener, verifications.toArray(new ISensorVerification[0]));
+ }
+
+ /**
+ * Construct a {@link ValidatingSensorEventListener}.
+ */
+ public ValidatingSensorEventListener(ISensorVerification ... verifications) {
+ this(null, verifications);
+ }
+
+ /**
+ * Construct a {@link ValidatingSensorEventListener}.
+ */
+ public ValidatingSensorEventListener(Collection<ISensorVerification> verifications) {
+ this(null, verifications);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ TestSensorEvent testEvent = new TestSensorEvent(event, SystemClock.elapsedRealtimeNanos());
+ for (ISensorVerification verification : mVerifications) {
+ verification.addSensorEvent(testEvent);
+ }
+ super.onSensorChanged(event);
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/AbstractSensorOperation.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/AbstractSensorOperation.java
index 5f6e558..5b969f2 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/AbstractSensorOperation.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/AbstractSensorOperation.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package android.hardware.cts.helpers.sensoroperations;
import android.hardware.cts.helpers.SensorStats;
@@ -11,13 +27,6 @@
private final SensorStats mStats = new SensorStats();
/**
- * Wrapper around {@link SensorStats#addValue(String, Object)}
- */
- protected void addValue(String key, Object value) {
- mStats.addValue(key, value);
- }
-
- /**
* Wrapper around {@link SensorStats#addSensorStats(String, SensorStats)}
*/
protected void addSensorStats(String key, SensorStats stats) {
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/DelaySensorOperation.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/DelaySensorOperation.java
index ad0ce94..bf43189 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/DelaySensorOperation.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/DelaySensorOperation.java
@@ -16,6 +16,7 @@
package android.hardware.cts.helpers.sensoroperations;
+import android.hardware.cts.helpers.SensorCtsHelper;
import android.hardware.cts.helpers.SensorStats;
import java.util.concurrent.TimeUnit;
@@ -25,8 +26,6 @@
* {@link ISensorOperation}.
*/
public class DelaySensorOperation implements ISensorOperation {
- private static final int NANOS_PER_MILLI = 1000000;
-
private final ISensorOperation mOperation;
private final long mDelay;
private final TimeUnit mTimeUnit;
@@ -52,7 +51,7 @@
*/
@Override
public void execute() {
- sleep(TimeUnit.NANOSECONDS.convert(mDelay, mTimeUnit));
+ sleep(mDelay, mTimeUnit);
mOperation.execute();
}
@@ -75,11 +74,7 @@
/**
* Helper method to sleep for a given number of ns. Exposed for unit testing.
*/
- void sleep(long delayNs) {
- try {
- Thread.sleep(delayNs / NANOS_PER_MILLI, (int) (delayNs % NANOS_PER_MILLI));
- } catch (InterruptedException e) {
- // Ignore
- }
+ void sleep(long delay, TimeUnit timeUnit) {
+ SensorCtsHelper.sleep(delay, timeUnit);
}
}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/FakeSensorOperation.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/FakeSensorOperation.java
index 0c7e771..bb64dfa 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/FakeSensorOperation.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/FakeSensorOperation.java
@@ -16,6 +16,8 @@
package android.hardware.cts.helpers.sensoroperations;
+import android.hardware.cts.helpers.SensorStats;
+
import junit.framework.Assert;
import java.util.concurrent.TimeUnit;
@@ -58,9 +60,9 @@
long delayNs = TimeUnit.NANOSECONDS.convert(mDelay, mTimeUnit);
try {
Thread.sleep(delayNs / NANOS_PER_MILLI, (int) (delayNs % NANOS_PER_MILLI));
- addValue("executed", new Boolean(true));
+ getStats().addValue("executed", true);
if (mFail) {
- Assert.fail("FakeSensorOperation failed");
+ doFail();
}
}catch (InterruptedException e) {
// Ignore
@@ -74,4 +76,13 @@
public FakeSensorOperation clone() {
return new FakeSensorOperation(mFail, mDelay, mTimeUnit);
}
+
+ /**
+ * Fails the operation.
+ */
+ protected void doFail() {
+ String msg = "FakeSensorOperation failed";
+ getStats().addValue(SensorStats.ERROR, msg);
+ Assert.fail(msg);
+ }
}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/ParallelSensorOperation.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/ParallelSensorOperation.java
index 9dda510..4cca428 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/ParallelSensorOperation.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/ParallelSensorOperation.java
@@ -16,6 +16,7 @@
package android.hardware.cts.helpers.sensoroperations;
+import android.hardware.cts.helpers.SensorStats;
import android.util.Log;
import junit.framework.Assert;
@@ -123,6 +124,7 @@
}
} else if (earliestException instanceof AssertionError) {
String msg = getExceptionMessage(exceptions, timeoutIndices);
+ getStats().addValue(SensorStats.ERROR, msg);
throw new AssertionError(msg, earliestException);
} else if (earliestException instanceof RuntimeException) {
throw (RuntimeException) earliestException;
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/RepeatingSensorOperation.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/RepeatingSensorOperation.java
index 28884cd..5e023e5 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/RepeatingSensorOperation.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/RepeatingSensorOperation.java
@@ -16,6 +16,8 @@
package android.hardware.cts.helpers.sensoroperations;
+import android.hardware.cts.helpers.SensorStats;
+
/**
* A {@link ISensorOperation} that executes a single {@link ISensorOperation} a given number of
* times. This class can be combined to compose complex {@link ISensorOperation}s.
@@ -53,6 +55,7 @@
operation.execute();
} catch (AssertionError e) {
String msg = String.format("Iteration %d failed: \"%s\"", i, e.getMessage());
+ getStats().addValue(SensorStats.ERROR, msg);
throw new AssertionError(msg, e);
} finally {
addSensorStats(STATS_TAG, i, operation.getStats());
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/SensorOperationTest.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/SensorOperationTest.java
index 244a974..7148454 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/SensorOperationTest.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/SensorOperationTest.java
@@ -54,6 +54,7 @@
} catch (AssertionError e) {
// Expected
}
+ assertTrue(op.getStats().flatten().keySet().contains(SensorStats.ERROR));
}
/**
@@ -129,11 +130,18 @@
}
statsKeys = op.getStats().flatten().keySet();
- assertEquals(subOpCount, statsKeys.size());
+ assertEquals(subOpCount + 3, statsKeys.size());
for (int i = 0; i < subOpCount; i++) {
assertTrue(statsKeys.contains(String.format("%s_%03d%sexecuted",
ParallelSensorOperation.STATS_TAG, i, SensorStats.DELIMITER)));
+ if (i % 50 == 5) {
+ assertTrue(statsKeys.contains(String.format("%s_%03d%s%s",
+ ParallelSensorOperation.STATS_TAG, i, SensorStats.DELIMITER,
+ SensorStats.ERROR)));
+ }
+
}
+ assertTrue(statsKeys.contains(SensorStats.ERROR));
}
/**
@@ -208,6 +216,7 @@
ISensorOperation subOp = new FakeSensorOperation(0, TimeUnit.MILLISECONDS) {
private int mExecutedCount = 0;
+ private SensorStats mFakeStats = new SensorStats();
@Override
public void execute() {
@@ -215,15 +224,21 @@
mExecutedCount++;
if (failCount == mExecutedCount) {
- fail("FakeSensorOperation failed");
+ doFail();
}
}
@Override
public FakeSensorOperation clone() {
// Don't clone
+ mFakeStats = new SensorStats();
return this;
}
+
+ @Override
+ public SensorStats getStats() {
+ return mFakeStats;
+ }
};
ISensorOperation op = new RepeatingSensorOperation(subOp, iterations);
@@ -239,11 +254,15 @@
}
statsKeys = op.getStats().flatten().keySet();
- assertEquals(failCount, statsKeys.size());
+ assertEquals(failCount + 2, statsKeys.size());
for (int i = 0; i < failCount; i++) {
assertTrue(statsKeys.contains(String.format("%s_%03d%sexecuted",
RepeatingSensorOperation.STATS_TAG, i, SensorStats.DELIMITER)));
}
+ assertTrue(statsKeys.contains(String.format("%s_%03d%s%s",
+ RepeatingSensorOperation.STATS_TAG, failCount - 1, SensorStats.DELIMITER,
+ SensorStats.ERROR)));
+ assertTrue(statsKeys.contains(SensorStats.ERROR));
}
/**
@@ -304,10 +323,14 @@
}
statsKeys = op.getStats().flatten().keySet();
- assertEquals(failCount, statsKeys.size());
+ assertEquals(failCount + 2, statsKeys.size());
for (int i = 0; i < failCount; i++) {
assertTrue(statsKeys.contains(String.format("%s_%03d%sexecuted",
SequentialSensorOperation.STATS_TAG, i, SensorStats.DELIMITER)));
}
+ assertTrue(statsKeys.contains(String.format("%s_%03d%s%s",
+ SequentialSensorOperation.STATS_TAG, failCount - 1, SensorStats.DELIMITER,
+ SensorStats.ERROR)));
+ assertTrue(statsKeys.contains(SensorStats.ERROR));
}
}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/SequentialSensorOperation.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/SequentialSensorOperation.java
index b62b867..050a8f6 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/SequentialSensorOperation.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/SequentialSensorOperation.java
@@ -16,6 +16,8 @@
package android.hardware.cts.helpers.sensoroperations;
+import android.hardware.cts.helpers.SensorStats;
+
import java.util.LinkedList;
import java.util.List;
@@ -53,6 +55,7 @@
operation.execute();
} catch (AssertionError e) {
String msg = String.format("Operation %d failed: \"%s\"", i, e.getMessage());
+ getStats().addValue(SensorStats.ERROR, msg);
throw new AssertionError(msg, e);
} finally {
addSensorStats(STATS_TAG, i, operation.getStats());
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/TestSensorOperation.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/TestSensorOperation.java
new file mode 100644
index 0000000..b632515
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/TestSensorOperation.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.cts.helpers.sensoroperations;
+
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.cts.helpers.SensorCtsHelper;
+import android.hardware.cts.helpers.SensorStats;
+import android.hardware.cts.helpers.SensorTestInformation;
+import android.hardware.cts.helpers.TestSensorManager;
+import android.hardware.cts.helpers.ValidatingSensorEventListener;
+import android.hardware.cts.helpers.sensorverification.EventOrderingVerification;
+import android.hardware.cts.helpers.sensorverification.FrequencyVerification;
+import android.hardware.cts.helpers.sensorverification.ISensorVerification;
+import android.hardware.cts.helpers.sensorverification.JitterVerification;
+import android.hardware.cts.helpers.sensorverification.MagnitudeVerification;
+import android.hardware.cts.helpers.sensorverification.MeanVerification;
+import android.hardware.cts.helpers.sensorverification.StandardDeviationVerification;
+
+import junit.framework.Assert;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A {@link ISensorOperation} used to verify that sensor events and sensor values are correct.
+ * <p>
+ * Provides methods to set test expectations as well as providing a set of default expectations
+ * depending on sensor type. When {{@link #execute()} is called, the sensor will collect the
+ * events and then run all the tests.
+ * </p>
+ */
+public class TestSensorOperation extends AbstractSensorOperation {
+ private final TestSensorManager mSensorManager;
+ private final Context mContext;
+ private final int mSensorType;
+ private final int mRateUs;
+ private final int mMaxBatchReportLatencyUs;
+ private final Integer mEventCount;
+ private final Long mDuration;
+ private final TimeUnit mTimeUnit;
+
+ private final Collection<ISensorVerification> mVerifications =
+ new HashSet<ISensorVerification>();
+
+ private boolean mLogEvents = false;
+
+ /**
+ * Create a {@link TestSensorOperation}.
+ *
+ * @param context the {@link Context}.
+ * @param sensorType the sensor type
+ * @param rateUs the rate that
+ * @param maxBatchReportLatencyUs the max batch report latency
+ * @param eventCount the number of events to gather
+ */
+ public TestSensorOperation(Context context, int sensorType, int rateUs,
+ int maxBatchReportLatencyUs, int eventCount) {
+ this(context, sensorType, rateUs, maxBatchReportLatencyUs, eventCount, null, null);
+ }
+
+ /**
+ * Create a {@link TestSensorOperation}.
+ *
+ * @param context the {@link Context}.
+ * @param sensorType the sensor type
+ * @param rateUs the rate that
+ * @param maxBatchReportLatencyUs the max batch report latency
+ * @param duration the duration to gather events for
+ * @param timeUnit the time unit of the duration
+ */
+ public TestSensorOperation(Context context, int sensorType, int rateUs,
+ int maxBatchReportLatencyUs, long duration, TimeUnit timeUnit) {
+ this(context, sensorType, rateUs, maxBatchReportLatencyUs, null, duration, timeUnit);
+ }
+
+ /**
+ * Private helper constructor.
+ */
+ private TestSensorOperation(Context context, int sensorType, int rateUs,
+ int maxBatchReportLatencyUs, Integer eventCount, Long duration, TimeUnit timeUnit) {
+ mContext = context;
+ mSensorType = sensorType;
+ mRateUs = rateUs;
+ mMaxBatchReportLatencyUs = maxBatchReportLatencyUs;
+ mEventCount = eventCount;
+ mDuration = duration;
+ mTimeUnit = timeUnit;
+ mSensorManager = new TestSensorManager(mContext, mSensorType, mRateUs,
+ mMaxBatchReportLatencyUs);
+ }
+
+ /**
+ * Set whether to log events.
+ */
+ public void setLogEvents(boolean logEvents) {
+ mLogEvents = logEvents;
+ }
+
+ /**
+ * Set all of the default test expectations.
+ */
+ public void setDefaultVerifications() {
+ Sensor sensor = mSensorManager.getSensor();
+ addVerification(EventOrderingVerification.getDefault(sensor));
+ addVerification(FrequencyVerification.getDefault(sensor, mRateUs));
+ addVerification(JitterVerification.getDefault(sensor, mRateUs));
+ addVerification(MagnitudeVerification.getDefault(sensor));
+ addVerification(MeanVerification.getDefault(sensor));
+ // Skip SigNumVerification since it has no default
+ addVerification(StandardDeviationVerification.getDefault(sensor));
+ }
+
+ public void addVerification(ISensorVerification verification) {
+ if (verification != null) {
+ mVerifications.add(verification);
+ }
+ }
+
+ /**
+ * Collect the specified number of events from the sensor and run all enabled verifications.
+ */
+ @Override
+ public void execute() {
+ getStats().addValue("sensor_name", SensorTestInformation.getSensorName(mSensorType));
+ getStats().addValue("sensor_handle", mSensorManager.getSensor().getHandle());
+
+ ValidatingSensorEventListener listener = new ValidatingSensorEventListener(mVerifications);
+ listener.setLogEvents(mLogEvents);
+
+ if (mEventCount != null) {
+ mSensorManager.runSensor(listener, mEventCount);
+ } else {
+ mSensorManager.runSensor(listener, mDuration, mTimeUnit);
+ }
+
+ boolean failed = false;
+ StringBuilder sb = new StringBuilder();
+
+ for (ISensorVerification verification : mVerifications) {
+ failed |= evaluateResults(verification, sb);
+ }
+
+ if (failed) {
+ String msg = SensorCtsHelper.formatAssertionMessage(mSensorManager.getSensor(),
+ "VerifySensorOperation", mRateUs, mMaxBatchReportLatencyUs, sb.toString());
+ getStats().addValue(SensorStats.ERROR, msg);
+ Assert.fail(msg);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public TestSensorOperation clone() {
+ TestSensorOperation operation;
+ if (mEventCount != null) {
+ operation = new TestSensorOperation(mContext, mSensorType, mRateUs,
+ mMaxBatchReportLatencyUs, mEventCount);
+ } else {
+ operation = new TestSensorOperation(mContext, mSensorType, mRateUs,
+ mMaxBatchReportLatencyUs, mDuration, mTimeUnit);
+ }
+
+ for (ISensorVerification verification : mVerifications) {
+ operation.addVerification(verification.clone());
+ }
+ return operation;
+ }
+
+ /**
+ * Evaluate the results of a test, aggregate the stats, and build the error message.
+ */
+ private boolean evaluateResults(ISensorVerification verification, StringBuilder sb) {
+ try {
+ verification.verify(getStats());
+ } catch (AssertionError e) {
+ if (sb.length() > 0) {
+ sb.append(", ");
+ }
+ sb.append(e.getMessage());
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/VerifySensorOperation.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/VerifySensorOperation.java
deleted file mode 100644
index de83a94..0000000
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/VerifySensorOperation.java
+++ /dev/null
@@ -1,635 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.hardware.cts.helpers.sensoroperations;
-
-import android.content.Context;
-import android.hardware.Sensor;
-import android.hardware.SensorManager;
-import android.hardware.cts.helpers.SensorCtsHelper;
-import android.hardware.cts.helpers.SensorManagerTestVerifier;
-import android.hardware.cts.helpers.SensorTestInformation;
-import android.hardware.cts.helpers.SensorVerificationHelper;
-import android.hardware.cts.helpers.SensorVerificationHelper.VerificationResult;
-import android.hardware.cts.helpers.TestSensorEvent;
-import android.util.Log;
-
-import junit.framework.Assert;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-
-/**
- * A {@link ISensorOperation} used to verify that sensor events and sensor values are correct.
- * <p>
- * Provides methods to set test expectations as well as providing a set of default expectations
- * depending on sensor type. When {{@link #execute()} is called, the sensor will collect the
- * events and then run all the tests.
- * </p>
- */
-public class VerifySensorOperation extends AbstractSensorOperation {
- private static final String TAG = "VerifySensorOperation";
-
- private static final boolean DEBUG = false;
-
- private SensorManagerTestVerifier mSensor;
- private Context mContext = null;
- private int mSensorType = 0;
- private int mRateUs = 0;
- private int mMaxBatchReportLatencyUs = 0;
- private Integer mEventCount = null;
- private Long mDuration = null;
- private TimeUnit mTimeUnit = null;
-
- private boolean mVerifyEventOrdering = false;
-
- private boolean mVerifyFrequency = false;
- private double mFrequencyExpected = 0.0;
- private double mFrequencyThreshold = 0.0;
-
- private boolean mVerifyJitter = false;
- private int mJitterExpected = 0;
- private int mJitterThreshold = 0;
-
- private boolean mVerifyMean = false;
- private float[] mMeanExpected = null;
- private float[] mMeanThreshold = null;
-
- private boolean mVerifyMagnitude = false;
- private float mMagnitudeExpected = 0.0f;
- private float mMagnitudeThreshold = 0.0f;
-
- private boolean mVerifySignum = false;
- private int[] mSignumExpected = null;
- private float[] mSignumThreshold = null;
-
- private boolean mVerifyStandardDeviation = false;
- private float[] mStandardDeviationThreshold = null;
-
- /**
- * Create a {@link VerifySensorOperation}.
- *
- * @param context the {@link Context}.
- * @param sensorType the sensor type
- * @param rateUs the rate that
- * @param maxBatchReportLatencyUs the max batch report latency
- * @param eventCount the number of events to gather
- */
- public VerifySensorOperation(Context context, int sensorType, int rateUs,
- int maxBatchReportLatencyUs, int eventCount) {
- mContext = context;
- mSensorType = sensorType;
- mRateUs = rateUs;
- mMaxBatchReportLatencyUs = maxBatchReportLatencyUs;
- mEventCount = eventCount;
- mSensor = new SensorManagerTestVerifier(mContext, mSensorType, mRateUs,
- mMaxBatchReportLatencyUs);
- }
-
- /**
- * Create a {@link VerifySensorOperation}.
- *
- * @param context the {@link Context}.
- * @param sensorType the sensor type
- * @param rateUs the rate that
- * @param maxBatchReportLatencyUs the max batch report latency
- * @param duration the duration to gather events for
- * @param timeUnit the time unit of the duration
- */
- public VerifySensorOperation(Context context, int sensorType, int rateUs,
- int maxBatchReportLatencyUs, long duration, TimeUnit timeUnit) {
- mContext = context;
- mSensorType = sensorType;
- mRateUs = rateUs;
- mMaxBatchReportLatencyUs = maxBatchReportLatencyUs;
- mDuration = duration;
- mTimeUnit = timeUnit;
- mSensor = new SensorManagerTestVerifier(mContext, mSensorType, mRateUs,
- mMaxBatchReportLatencyUs);
- }
-
- /**
- * Set all of the default test expectations.
- */
- public void setDefaultVerifications() {
- setDefaultVerifyEventOrdering();
- setDefaultVerifyFrequency();
- setDefaultVerifyJitter();
- setDefaultVerifyMean();
- setDefaultVerifyMagnitude();
- setDefaultVerifySignum();
- setDefaultVerifyStandardDeviation();
- }
-
- /**
- * Enable the event ordering verification.
- */
- public void verifyEventOrdering() {
- mVerifyEventOrdering = true;
- }
-
- /**
- * Set the default event ordering verification.
- */
- @SuppressWarnings("deprecation")
- public void setDefaultVerifyEventOrdering() {
- switch (mSensorType) {
- case Sensor.TYPE_ACCELEROMETER:
- case Sensor.TYPE_MAGNETIC_FIELD:
- case Sensor.TYPE_ORIENTATION:
- case Sensor.TYPE_GYROSCOPE:
- case Sensor.TYPE_PRESSURE:
- case Sensor.TYPE_GRAVITY:
- case Sensor.TYPE_LINEAR_ACCELERATION:
- case Sensor.TYPE_ROTATION_VECTOR:
- case Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED:
- case Sensor.TYPE_GAME_ROTATION_VECTOR:
- case Sensor.TYPE_GYROSCOPE_UNCALIBRATED:
- case Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR:
- verifyEventOrdering();
- break;
- }
- }
-
- /**
- * Enable the frequency verification.
- *
- * @param expected the expected frequency in ns.
- * @param threshold the threshold in ns.
- */
- public void verifyFrequency(double expected, double threshold) {
- mVerifyFrequency = true;
- mFrequencyExpected = expected;
- mFrequencyThreshold = threshold;
- }
-
- /**
- * Set the default frequency verification depending on the sensor.
- * <p>
- * The expected frequency is based on {@link Sensor#getMinDelay()} and the threshold is
- * calculated based on a percentage of the expected frequency. The verification will not be run
- * if the rate is set to {@link SensorManager#SENSOR_DELAY_GAME},
- * {@link SensorManager#SENSOR_DELAY_UI}, or {@link SensorManager#SENSOR_DELAY_NORMAL} because
- * these rates are not specified in the CDD.
- */
- @SuppressWarnings("deprecation")
- public void setDefaultVerifyFrequency() {
- if (!isRateValid()) {
- return;
- }
-
- // sensorType: threshold (% of expected frequency)
- Map<Integer, Integer> defaults = new HashMap<Integer, Integer>(12);
- // Sensors that we don't want to test at this time but still want to record the values.
- defaults.put(Sensor.TYPE_ACCELEROMETER, Integer.MAX_VALUE);
- defaults.put(Sensor.TYPE_MAGNETIC_FIELD, Integer.MAX_VALUE);
- defaults.put(Sensor.TYPE_GYROSCOPE, Integer.MAX_VALUE);
- defaults.put(Sensor.TYPE_ORIENTATION, Integer.MAX_VALUE);
- defaults.put(Sensor.TYPE_PRESSURE, Integer.MAX_VALUE);
- defaults.put(Sensor.TYPE_GRAVITY, Integer.MAX_VALUE);
- defaults.put(Sensor.TYPE_LINEAR_ACCELERATION, Integer.MAX_VALUE);
- defaults.put(Sensor.TYPE_ROTATION_VECTOR, Integer.MAX_VALUE);
- defaults.put(Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED, Integer.MAX_VALUE);
- defaults.put(Sensor.TYPE_GAME_ROTATION_VECTOR, Integer.MAX_VALUE);
- defaults.put(Sensor.TYPE_GYROSCOPE_UNCALIBRATED, Integer.MAX_VALUE);
- defaults.put(Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR, Integer.MAX_VALUE);
-
- if (defaults.containsKey(mSensorType)) {
- // Expected frequency in Hz
- double expected = SensorCtsHelper.getFrequency(
- SensorCtsHelper.getDelay(mSensor.getUnderlyingSensor(), mRateUs),
- TimeUnit.MICROSECONDS);
- // Expected frequency * threshold percentage
- double threshold = expected * defaults.get(mSensorType) / 100;
- verifyFrequency(expected, threshold);
- }
- }
-
- /**
- * Enable the jitter verification.
- * <p>
- * This test looks at the 95th percentile of the jitter and makes sure it is less than the
- * threshold percentage of the expected period.
- * </p>
- *
- * @param expected the expected period in ns.
- * @param threshold the theshold as a percentage of the expected period.
- */
- public void verifyJitter(int expected, int threshold) {
- mVerifyJitter = true;
- mJitterExpected = expected;
- mJitterThreshold = threshold;
- }
-
- /**
- * Set the default jitter verification based on the sensor type.
- * <p>
- * The verification will not be run if the rate is set to
- * {@link SensorManager#SENSOR_DELAY_GAME}, {@link SensorManager#SENSOR_DELAY_UI}, or
- * {@link SensorManager#SENSOR_DELAY_NORMAL} because these rates are not specified in the CDD.
- * </p>
- */
- @SuppressWarnings("deprecation")
- public void setDefaultVerifyJitter() {
- if (!isRateValid()) {
- return;
- }
-
- // sensorType: threshold (% of expected period)
- Map<Integer, Integer> defaults = new HashMap<Integer, Integer>(12);
- // Sensors that we don't want to test at this time but still want to record the values.
- defaults.put(Sensor.TYPE_ACCELEROMETER, Integer.MAX_VALUE);
- defaults.put(Sensor.TYPE_MAGNETIC_FIELD, Integer.MAX_VALUE);
- defaults.put(Sensor.TYPE_GYROSCOPE, Integer.MAX_VALUE);
- defaults.put(Sensor.TYPE_ORIENTATION, Integer.MAX_VALUE);
- defaults.put(Sensor.TYPE_PRESSURE, Integer.MAX_VALUE);
- defaults.put(Sensor.TYPE_GRAVITY, Integer.MAX_VALUE);
- defaults.put(Sensor.TYPE_LINEAR_ACCELERATION, Integer.MAX_VALUE);
- defaults.put(Sensor.TYPE_ROTATION_VECTOR, Integer.MAX_VALUE);
- defaults.put(Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED, Integer.MAX_VALUE);
- defaults.put(Sensor.TYPE_GAME_ROTATION_VECTOR, Integer.MAX_VALUE);
- defaults.put(Sensor.TYPE_GYROSCOPE_UNCALIBRATED, Integer.MAX_VALUE);
- defaults.put(Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR, Integer.MAX_VALUE);
-
- if (defaults.containsKey(mSensorType)) {
- int expected = (int) TimeUnit.NANOSECONDS.convert(
- SensorCtsHelper.getDelay(mSensor.getUnderlyingSensor(), mRateUs),
- TimeUnit.MICROSECONDS);
- verifyJitter(expected, defaults.get(mSensorType));
- }
- }
-
- /**
- * Enable the mean verification.
- *
- * @param expected the expected means
- * @param threshold the threshold
- */
- public void verifyMean(float[] expected, float[] threshold) {
- mVerifyMean = true;
- mMeanExpected = expected;
- mMeanThreshold = threshold;
- }
-
- /**
- * Set the default mean verification based on sensor type.
- * <p>
- * This sets the mean expectations for a device at rest in a standard environment. For sensors
- * whose values vary depending on the orientation or environment, the expectations will not be
- * set.
- * </p><p>
- * The following expectations are set for these sensors:
- * </p><ul>
- * <li>Gyroscope: all values should be 0.</li>
- * <li>Pressure: values[0] should be close to the standard pressure.</li>
- * <li>Linear acceleration: all values should be 0.</li>
- * <li>Game rotation vector: all values should be 0 except values[3] which should be 1.</li>
- * <li>Uncalibrated gyroscope: all values should be 0.</li>
- * </ul>
- */
- @SuppressWarnings("deprecation")
- public void setDefaultVerifyMean() {
- // sensorType: {expected, threshold}
- Map<Integer, Object[]> defaults = new HashMap<Integer, Object[]>(5);
- // Sensors that we don't want to test at this time but still want to record the values.
- // Gyroscope should be 0 for a static device
- defaults.put(Sensor.TYPE_GYROSCOPE, new Object[]{
- new float[]{0.0f, 0.0f, 0.0f},
- new float[]{Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE}});
- // Pressure will not be exact in a controlled environment but should be relatively close to
- // sea level. Second values should always be 0.
- defaults.put(Sensor.TYPE_PRESSURE, new Object[]{
- new float[]{SensorManager.PRESSURE_STANDARD_ATMOSPHERE, 0.0f, 0.0f},
- new float[]{Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE}});
- // Linear acceleration should be 0 in all directions for a static device
- defaults.put(Sensor.TYPE_LINEAR_ACCELERATION, new Object[]{
- new float[]{0.0f, 0.0f, 0.0f},
- new float[]{Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE}});
- // Game rotation vector should be (0, 0, 0, 1, 0) for a static device
- defaults.put(Sensor.TYPE_GAME_ROTATION_VECTOR, new Object[]{
- new float[]{0.0f, 0.0f, 0.0f, 1.0f, 0.0f},
- new float[]{Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE,
- Float.MAX_VALUE}});
- // Uncalibrated gyroscope should be 0 for a static device but allow a bigger threshold
- defaults.put(Sensor.TYPE_GYROSCOPE_UNCALIBRATED, new Object[]{
- new float[]{0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f},
- new float[]{Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE,
- Float.MAX_VALUE, Float.MAX_VALUE}});
-
- if (defaults.containsKey(mSensorType)) {
- float[] expected = (float[]) defaults.get(mSensorType)[0];
- float[] threshold = (float[]) defaults.get(mSensorType)[1];
- verifyMean(expected, threshold);
- }
- }
-
- /**
- * Enable the magnitude verification.
- *
- * @param expected the expected magnitude of the vector
- * @param threshold the threshold
- */
- public void verifyMagnitude(float expected, float threshold) {
- mVerifyMagnitude = true;
- mMagnitudeExpected = expected;
- mMagnitudeThreshold = threshold;
- }
-
- /**
- * Set the default magnitude verification base on the sensor type.
- * <p>
- * This sets the magnitude expectations for a device at rest in a standard environment. For
- * sensors whose values vary depending on the orientation or environment, the expectations will
- * not be set.
- * </p>
- */
- @SuppressWarnings("deprecation")
- public void setDefaultVerifyMagnitude() {
- // sensorType: {expected, threshold}
- Map<Integer, Float[]> defaults = new HashMap<Integer, Float[]>(3);
- defaults.put(Sensor.TYPE_ACCELEROMETER, new Float[]{SensorManager.STANDARD_GRAVITY, 1.5f});
- defaults.put(Sensor.TYPE_GYROSCOPE, new Float[]{0.0f, 1.5f});
- // Sensors that we don't want to test at this time but still want to record the values.
- defaults.put(Sensor.TYPE_GRAVITY,
- new Float[]{SensorManager.STANDARD_GRAVITY, Float.MAX_VALUE});
-
- if (defaults.containsKey(mSensorType)) {
- Float expected = defaults.get(mSensorType)[0];
- Float threshold = defaults.get(mSensorType)[1];
- verifyMagnitude(expected, threshold);
- }
- }
-
- /**
- * Enable the signum verification.
- *
- * @param expected the expected signs, an array of either -1s, 0s, or 1s.
- * @param threshold the threshold
- */
- public void verifySignum(int[] expected, float[] threshold) {
- mVerifySignum = true;
- mSignumExpected = expected;
- mSignumThreshold = threshold;
- }
-
- /**
- * Set the default signum verification base on the sensor type.
- * <p>
- * This is a no-op since currently all sensors which can specify a default sign can also specify
- * a default mean which is a more precise test.
- * </p>
- */
- public void setDefaultVerifySignum() {
- // No-op: All sensors that have an expected sign when static are already tested in
- // setDefaultVerifyMean().
- }
-
- /**
- * Enable the standard deviation verification.
- *
- * @param threshold the threshold.
- */
- public void verifyStandardDeviation(float[] threshold) {
- mVerifyStandardDeviation = true;
- mStandardDeviationThreshold = threshold;
- }
-
- /**
- * Set the default standard deviation verification based on the sensor type.
- */
- @SuppressWarnings("deprecation")
- public void setDefaultVerifyStandardDeviation() {
- // sensorType: threshold
- Map<Integer, float[]> defaults = new HashMap<Integer, float[]>(12);
- defaults.put(Sensor.TYPE_ACCELEROMETER, new float[]{1.0f, 1.0f, 1.0f});
- defaults.put(Sensor.TYPE_GYROSCOPE, new float[]{0.5f, 0.5f, 0.5f});
- // Sensors that we don't want to test at this time but still want to record the values.
- defaults.put(Sensor.TYPE_MAGNETIC_FIELD,
- new float[]{Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE});
- defaults.put(Sensor.TYPE_ORIENTATION,
- new float[]{Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE});
- defaults.put(Sensor.TYPE_PRESSURE,
- new float[]{Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE});
- defaults.put(Sensor.TYPE_GRAVITY,
- new float[]{Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE});
- defaults.put(Sensor.TYPE_LINEAR_ACCELERATION,
- new float[]{Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE});
- defaults.put(Sensor.TYPE_ROTATION_VECTOR,
- new float[]{Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE,
- Float.MAX_VALUE});
- defaults.put(Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED,
- new float[]{Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE,
- Float.MAX_VALUE, Float.MAX_VALUE});
- defaults.put(Sensor.TYPE_GAME_ROTATION_VECTOR,
- new float[]{Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE,
- Float.MAX_VALUE});
- defaults.put(Sensor.TYPE_GYROSCOPE_UNCALIBRATED,
- new float[]{Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE,
- Float.MAX_VALUE, Float.MAX_VALUE});
- defaults.put(Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR,
- new float[]{Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE,
- Float.MAX_VALUE});
-
- if (defaults.containsKey(mSensorType)) {
- float[] threshold = defaults.get(mSensorType);
- verifyStandardDeviation(threshold);
- }
- }
-
- /**
- * Collect the specified number of events from the sensor and run all enabled verifications.
- */
- @Override
- public void execute() {
- addValue("sensor_name", SensorTestInformation.getSensorName(mSensorType));
- addValue("sensor_handle", mSensor.getUnderlyingSensor().getHandle());
-
- TestSensorEvent[] events;
- if (mEventCount != null) {
- events = mSensor.collectEvents(mEventCount);
- } else {
- events = mSensor.collectEvents(mDuration, mTimeUnit);
- }
-
- boolean failed = false;
- StringBuilder sb = new StringBuilder();
- VerificationResult result = null;
-
- if (mVerifyEventOrdering) {
- result = SensorVerificationHelper.verifyEventOrdering(events);
- // evaluateResults first so it is always called.
- failed |= evaluateResults(result, sb);
- }
-
- if (mVerifyFrequency) {
- result = SensorVerificationHelper.verifyFrequency(events, mFrequencyExpected,
- mFrequencyThreshold);
- failed |= evaluateResults(result, sb);
- }
-
- if (mVerifyJitter) {
- result = SensorVerificationHelper.verifyJitter(events, mJitterExpected,
- mJitterThreshold);
- failed |= evaluateResults(result, sb);
- }
-
- if (mVerifyMean) {
- result = SensorVerificationHelper.verifyMean(events, mMeanExpected, mMeanThreshold);
- failed |= evaluateResults(result, sb);
- }
-
- if (mVerifyMagnitude) {
- result = SensorVerificationHelper.verifyMagnitude(events, mMagnitudeExpected,
- mMagnitudeThreshold);
- failed |= evaluateResults(result, sb);
- }
-
- if (mVerifySignum) {
- result = SensorVerificationHelper.verifySignum(events, mSignumExpected,
- mSignumThreshold);
- failed |= evaluateResults(result, sb);
- }
-
- if (mVerifyStandardDeviation) {
- result = SensorVerificationHelper.verifyStandardDeviation(events,
- mStandardDeviationThreshold);
- failed |= evaluateResults(result, sb);
- }
-
- if (DEBUG) {
- logStats(events);
- }
-
- if (failed) {
- Assert.fail(String.format("%s, handle %d: %s",
- SensorTestInformation.getSensorName(mSensorType),
- mSensor.getUnderlyingSensor().getHandle(), sb.toString()));
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public VerifySensorOperation clone() {
- VerifySensorOperation operation;
- if (mEventCount != null) {
- operation = new VerifySensorOperation(mContext, mSensorType, mRateUs,
- mMaxBatchReportLatencyUs, mEventCount);
- } else {
- operation = new VerifySensorOperation(mContext, mSensorType, mRateUs,
- mMaxBatchReportLatencyUs, mDuration, mTimeUnit);
- }
- if (mVerifyEventOrdering) {
- operation.verifyEventOrdering();
- }
- if (mVerifyFrequency) {
- operation.verifyFrequency(mFrequencyExpected, mFrequencyThreshold);
- }
- if (mVerifyJitter) {
- operation.verifyJitter(mJitterExpected, mJitterThreshold);
- }
- if (mVerifyMean) {
- operation.verifyMean(mMeanExpected, mMeanThreshold);
- }
- if (mVerifyMagnitude) {
- operation.verifyMagnitude(mMagnitudeExpected, mMagnitudeThreshold);
- }
- if (mVerifySignum) {
- operation.verifySignum(mSignumExpected, mSignumThreshold);
- }
- if (mVerifyStandardDeviation) {
- operation.verifyStandardDeviation(mStandardDeviationThreshold);
- }
- return operation;
- }
-
- /**
- * Return true if the operation rate is not one of {@link SensorManager#SENSOR_DELAY_GAME},
- * {@link SensorManager#SENSOR_DELAY_UI}, or {@link SensorManager#SENSOR_DELAY_NORMAL}.
- */
- private boolean isRateValid() {
- return (mRateUs != SensorManager.SENSOR_DELAY_GAME
- && mRateUs != SensorManager.SENSOR_DELAY_UI
- && mRateUs != SensorManager.SENSOR_DELAY_NORMAL);
- }
-
- /**
- * Evaluate the results of a test, aggregate the stats, and build the error message.
- */
- private boolean evaluateResults(VerificationResult result, StringBuilder sb) {
- for (String key : result.getKeys()) {
- addValue(key, result.getValue(key));
- }
-
- if (result.isFailed()) {
- if (sb.length() > 0) {
- sb.append(", ");
- }
- sb.append(result.getFailureMessage());
- return true;
- }
- return false;
- }
-
- /**
- * Log the events to the logcat.
- */
- private void logStats(TestSensorEvent[] events) {
- if (events.length <= 0) {
- return;
- }
-
- List<Double> jitterValues = null;
- if (events.length > 1) {
- jitterValues = SensorCtsHelper.getJitterValues(events);
- }
-
- logTestSensorEvent(0, events[0], null, null);
- for (int i = 1; i < events.length; i++) {
- Double jitter = jitterValues == null ? null : jitterValues.get(i - 1);
- logTestSensorEvent(i, events[i], events[i - 1], jitter);
- }
- }
-
- /**
- * Log a single sensor event to the logcat.
- */
- private void logTestSensorEvent(int index, TestSensorEvent event, TestSensorEvent prevEvent,
- Double jitter) {
- String deltaStr = prevEvent == null ? null : String.format("%d",
- event.timestamp - prevEvent.timestamp);
- String jitterStr = jitter == null ? null : String.format("%.2f", jitter);
-
- StringBuilder valuesSb = new StringBuilder();
- if (event.values.length == 1) {
- valuesSb.append(String.format("%.2f", event.values[0]));
- } else {
- valuesSb.append("[").append(String.format("%.2f", event.values[0]));
- for (int i = 1; i < event.values.length; i++) {
- valuesSb.append(String.format(", %.2f", event.values[i]));
- }
- valuesSb.append("]");
- }
-
- Log.v(TAG, String.format(
- "Sensor %d: Event %d: device_timestamp=%d, delta_timestamp=%s, jitter=%s, "
- + "values=%s", mSensor.getUnderlyingSensor().getType(), index, event.timestamp,
- deltaStr, jitterStr, valuesSb.toString()));
- }
-}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/AbstractMeanVerification.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/AbstractMeanVerification.java
new file mode 100644
index 0000000..8d132a3
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/AbstractMeanVerification.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.cts.helpers.sensorverification;
+
+import android.hardware.cts.helpers.TestSensorEvent;
+
+import junit.framework.Assert;
+
+/**
+ * Abstract class that calculates of the mean event values.
+ */
+public abstract class AbstractMeanVerification extends AbstractSensorVerification {
+ private float[] mSums = null;
+ private int mCount = 0;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void addSensorEventInternal(TestSensorEvent event) {
+ if (mSums == null) {
+ mSums = new float[event.values.length];
+ }
+ Assert.assertEquals(mSums.length, event.values.length);
+ for (int i = 0; i < mSums.length; i++) {
+ mSums[i] += event.values[i];
+ }
+ mCount++;
+ }
+
+ /**
+ * Return the number of events.
+ */
+ protected int getCount() {
+ return mCount;
+ }
+
+ /**
+ * Return the means of the event values.
+ */
+ protected float[] getMeans() {
+ if (mCount < 0) {
+ return null;
+ }
+
+ float[] means = new float[mSums.length];
+ for (int i = 0; i < mSums.length; i++) {
+ means[i] = mSums[i] / mCount;
+ }
+ return means;
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/AbstractSensorVerification.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/AbstractSensorVerification.java
new file mode 100644
index 0000000..f11f7a4
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/AbstractSensorVerification.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.cts.helpers.sensorverification;
+
+import android.hardware.cts.helpers.TestSensorEvent;
+
+/**
+ * Abstract class that deals with the synchronization of the sensor verifications.
+ */
+public abstract class AbstractSensorVerification implements ISensorVerification {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public synchronized void addSensorEvents(TestSensorEvent ... events) {
+ for (TestSensorEvent event : events) {
+ addSensorEventInternal(event);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public synchronized void addSensorEvent(TestSensorEvent event) {
+ addSensorEventInternal(event);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public abstract ISensorVerification clone();
+
+ /**
+ * Used by implementing classes to add a sensor event.
+ */
+ protected abstract void addSensorEventInternal(TestSensorEvent event);
+}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/EventOrderingVerification.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/EventOrderingVerification.java
new file mode 100644
index 0000000..fadf8b0
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/EventOrderingVerification.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.cts.helpers.sensorverification;
+
+import android.hardware.Sensor;
+import android.hardware.cts.helpers.SensorStats;
+import android.hardware.cts.helpers.TestSensorEvent;
+
+import junit.framework.Assert;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * A {@link ISensorVerification} which verifies that all events are received in the correct order.
+ */
+public class EventOrderingVerification extends AbstractSensorVerification {
+ public static final String PASSED_KEY = "event_out_of_order_passed";
+
+ private static final int MESSAGE_LENGTH = 3;
+
+ private Long mMaxTimestamp = null;
+ private TestSensorEvent mPreviousEvent = null;
+ private final List<EventInfo> mOutOfOrderEvents = new LinkedList<EventInfo>();
+ private int mCount = 0;
+ private int mIndex = 0;
+
+ /**
+ * Get the default {@link EventOrderingVerification} for a sensor.
+ *
+ * @param sensor a {@link Sensor}
+ * @return the verification or null if the verification does not apply to the sensor.
+ */
+ @SuppressWarnings("deprecation")
+ public static EventOrderingVerification getDefault(Sensor sensor) {
+ switch (sensor.getType()) {
+ case Sensor.TYPE_ACCELEROMETER:
+ case Sensor.TYPE_MAGNETIC_FIELD:
+ case Sensor.TYPE_ORIENTATION:
+ case Sensor.TYPE_GYROSCOPE:
+ case Sensor.TYPE_PRESSURE:
+ case Sensor.TYPE_GRAVITY:
+ case Sensor.TYPE_LINEAR_ACCELERATION:
+ case Sensor.TYPE_ROTATION_VECTOR:
+ case Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED:
+ case Sensor.TYPE_GAME_ROTATION_VECTOR:
+ case Sensor.TYPE_GYROSCOPE_UNCALIBRATED:
+ case Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR:
+ return new EventOrderingVerification();
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Verify that the events are in the correct order. Add {@value #PASSED_KEY},
+ * {@value SensorStats#EVENT_OUT_OF_ORDER_COUNT_KEY}, and
+ * {@value SensorStats#EVENT_OUT_OF_ORDER_POSITIONS_KEY} keys to {@link SensorStats}.
+ *
+ * @throws AssertionError if the verification failed.
+ */
+ @Override
+ public void verify(SensorStats stats) {
+ stats.addValue(PASSED_KEY, mCount == 0);
+ stats.addValue(SensorStats.EVENT_OUT_OF_ORDER_COUNT_KEY, mCount);
+
+ int[] indices = new int[mOutOfOrderEvents.size()];
+ for (int i = 0; i < indices.length; i++) {
+ indices[i] = mOutOfOrderEvents.get(i).index;
+ }
+ stats.addValue(SensorStats.EVENT_OUT_OF_ORDER_POSITIONS_KEY, indices);
+
+ if (mOutOfOrderEvents.size() > 0) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(mCount).append(" events out of order: ");
+ for (int i = 0; i < Math.min(mOutOfOrderEvents.size(), MESSAGE_LENGTH); i++) {
+ EventInfo info = mOutOfOrderEvents.get(i);
+ sb.append(String.format("position=%d, previous=%d, timestamp=%d; ", info.index,
+ info.previousEvent.timestamp, info.event.timestamp));
+ }
+ if (mOutOfOrderEvents.size() > MESSAGE_LENGTH) {
+ sb.append(mOutOfOrderEvents.size() - MESSAGE_LENGTH).append(" more");
+ } else {
+ // Delete the trailing "; "
+ sb.delete(sb.length() - 2, sb.length());
+ }
+
+ Assert.fail(sb.toString());
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public EventOrderingVerification clone() {
+ return new EventOrderingVerification();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void addSensorEventInternal(TestSensorEvent event) {
+ if (mPreviousEvent == null) {
+ mMaxTimestamp = event.timestamp;
+ } else {
+ if (event.timestamp < mMaxTimestamp) {
+ mOutOfOrderEvents.add(new EventInfo(mIndex, event, mPreviousEvent));
+ mCount++;
+ } else if (event.timestamp > mMaxTimestamp) {
+ mMaxTimestamp = event.timestamp;
+ }
+ }
+
+ mPreviousEvent = event;
+ mIndex++;
+ }
+
+ private class EventInfo {
+ public final int index;
+ public final TestSensorEvent event;
+ public final TestSensorEvent previousEvent;
+
+ public EventInfo(int index, TestSensorEvent event, TestSensorEvent previousEvent) {
+ this.index = index;
+ this.event = event;
+ this.previousEvent = previousEvent;
+ }
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/EventOrderingVerificationTest.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/EventOrderingVerificationTest.java
new file mode 100644
index 0000000..28cbd01
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/EventOrderingVerificationTest.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.cts.helpers.sensorverification;
+
+import android.hardware.cts.helpers.SensorStats;
+import android.hardware.cts.helpers.TestSensorEvent;
+
+import junit.framework.TestCase;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for {@link EventOrderingVerification}.
+ */
+public class EventOrderingVerificationTest extends TestCase {
+
+ /**
+ * Test that the verification passes when there are no results.
+ */
+ public void testNoEvents() {
+ SensorStats stats = new SensorStats();
+ ISensorVerification verification = getVerification();
+ verification.verify(stats);
+ verifyStats(stats, true, 0);
+ }
+
+ /**
+ * Test that the verification passes when the timestamps are the same.
+ */
+ public void testSameTimestamp() {
+ SensorStats stats = new SensorStats();
+ ISensorVerification verification = getVerification(0, 0, 0, 0, 0);
+ verification.verify(stats);
+ verifyStats(stats, true, 0);
+ }
+
+ /**
+ * Test that the verification passes when the timestamps are increasing.
+ */
+ public void testSequentialTimestamp() {
+ SensorStats stats = new SensorStats();
+ ISensorVerification verification = getVerification(0, 1, 2, 3, 4);
+ verification.verify(stats);
+ verifyStats(stats, true, 0);
+ }
+
+ /**
+ * Test that the verification fails when there is one event out of order.
+ */
+ public void testSingleOutofOrder() {
+ SensorStats stats = new SensorStats();
+ ISensorVerification verification = getVerification(0, 2, 1, 3, 4);
+ try {
+ verification.verify(stats);
+ fail("Expected an AssertionError");
+ } catch (AssertionError e) {
+ // Expected;
+ }
+ verifyStats(stats, false, 1);
+ List<Integer> indices = getIndices(stats);
+ assertTrue(indices.contains(2));
+ }
+
+ /**
+ * Test that the verification fails when there are multiple events out of order.
+ */
+ public void testMultipleOutOfOrder() {
+ SensorStats stats = new SensorStats();
+ ISensorVerification verification = getVerification(4, 0, 1, 2, 3);
+ try {
+ verification.verify(stats);
+ fail("Expected an AssertionError");
+ } catch (AssertionError e) {
+ // Expected;
+ }
+ verifyStats(stats, false, 4);
+ List<Integer> indices = getIndices(stats);
+ assertTrue(indices.contains(1));
+ assertTrue(indices.contains(2));
+ assertTrue(indices.contains(3));
+ assertTrue(indices.contains(4));
+ }
+
+ private ISensorVerification getVerification(long ... timestamps) {
+ ISensorVerification verification = new EventOrderingVerification();
+ for (long timestamp : timestamps) {
+ verification.addSensorEvent(new TestSensorEvent(null, timestamp, 0, null));
+ }
+ return verification;
+ }
+
+ private void verifyStats(SensorStats stats, boolean passed, int count) {
+ assertEquals(passed, stats.getValue(EventOrderingVerification.PASSED_KEY));
+ assertEquals(count, stats.getValue(SensorStats.EVENT_OUT_OF_ORDER_COUNT_KEY));
+ assertNotNull(stats.getValue(SensorStats.EVENT_OUT_OF_ORDER_POSITIONS_KEY));
+ }
+
+ private List<Integer> getIndices(SensorStats stats) {
+ int[] primitiveIndices = (int[]) stats.getValue(
+ SensorStats.EVENT_OUT_OF_ORDER_POSITIONS_KEY);
+ List<Integer> indices = new ArrayList<Integer>(primitiveIndices.length);
+ for (int index : primitiveIndices) {
+ indices.add(index);
+ }
+ return indices;
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/FrequencyVerification.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/FrequencyVerification.java
new file mode 100644
index 0000000..030bfeb
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/FrequencyVerification.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.cts.helpers.sensorverification;
+
+import android.hardware.Sensor;
+import android.hardware.cts.helpers.SensorCtsHelper;
+import android.hardware.cts.helpers.SensorStats;
+import android.hardware.cts.helpers.TestSensorEvent;
+
+import junit.framework.Assert;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A {@link ISensorVerification} which verifies that the sensor frequency are within the expected
+ * range.
+ */
+public class FrequencyVerification extends AbstractSensorVerification {
+ public static final String PASSED_KEY = "frequency_passed";
+
+ // threshold is (100 - 10)% expected to (100 + 110)% expected
+ private static final int[] DEFAULT_THRESHOLDS = {10, 110};
+
+ // sensorType: lowerThreshold, upperThreshold (% of expected frequency)
+ private final static Map<Integer, int[]> DEFAULTS = new HashMap<Integer, int[]>(12);
+ static {
+ // Use a method so that the @deprecation warning can be set for that method only
+ setDefaults();
+ }
+
+ private final double mExpected;
+ private final double mLowerThreshold;
+ private final double mUpperThreshold;
+
+ private long mMinTimestamp = 0;
+ private long mMaxTimestamp = 0;
+ private int mCount = 0;
+
+ /**
+ * Construct a {@link FrequencyVerification}.
+ *
+ * @param expected the expected frequency in Hz.
+ * @param lowerTheshold the lower threshold in Hz. {@code expected - lower} should be the
+ * slowest acceptable frequency of the sensor.
+ * @param upperThreshold the upper threshold in Hz. {@code expected + upper} should be the
+ * fastest acceptable frequency of the sensor.
+ */
+ public FrequencyVerification(double expected, double lowerTheshold, double upperThreshold) {
+ mExpected = expected;
+ mLowerThreshold = lowerTheshold;
+ mUpperThreshold = upperThreshold;
+ }
+
+ /**
+ * Get the default {@link FrequencyVerification} for a sensor.
+ *
+ * @param sensor a {@link Sensor}
+ * @param rateUs the desired rate of the sensor
+ * @return the verification or null if the verification does not apply to the sensor.
+ */
+ public static FrequencyVerification getDefault(Sensor sensor, int rateUs) {
+ if (!DEFAULTS.containsKey(sensor.getType())) {
+ return null;
+ }
+
+ // Expected frequency in Hz
+ double expected = SensorCtsHelper.getFrequency(SensorCtsHelper.getDelay(sensor, rateUs),
+ TimeUnit.MICROSECONDS);
+ // Expected frequency * threshold percentage
+ double lowerThreshold = expected * DEFAULTS.get(sensor.getType())[0] / 100;
+ double upperThreshold = expected * DEFAULTS.get(sensor.getType())[1] / 100;
+ return new FrequencyVerification(expected, lowerThreshold, upperThreshold);
+ }
+
+ /**
+ * Verify that the frequency is correct. Add {@value #PASSED_KEY} and
+ * {@value SensorStats#FREQUENCY_KEY} keys to {@link SensorStats}.
+ *
+ * @throws AssertionError if the verification failed.
+ */
+ @Override
+ public void verify(SensorStats stats) {
+ if (mCount < 2) {
+ stats.addValue(PASSED_KEY, true);
+ return;
+ }
+
+ double frequency = SensorCtsHelper.getFrequency(
+ ((double) (mMaxTimestamp - mMinTimestamp)) / (mCount - 1), TimeUnit.NANOSECONDS);
+ boolean failed = (frequency <= mExpected - mLowerThreshold
+ || frequency >= mExpected + mUpperThreshold);
+
+ stats.addValue(SensorStats.FREQUENCY_KEY, frequency);
+ stats.addValue(PASSED_KEY, !failed);
+
+ if (failed) {
+ Assert.fail(String.format("Frequency out of range: frequency=%.2fHz, "
+ + "expected=(%.2f-%.2fHz, %.2f+%.2fHz)", frequency, mExpected, mLowerThreshold,
+ mExpected, mUpperThreshold));
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public FrequencyVerification clone() {
+ return new FrequencyVerification(mExpected, mLowerThreshold, mUpperThreshold);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void addSensorEventInternal(TestSensorEvent event) {
+ if (mCount == 0) {
+ mMinTimestamp = event.timestamp;
+ mMaxTimestamp = event.timestamp;
+ } else {
+ if (mMinTimestamp > event.timestamp) {
+ mMinTimestamp = event.timestamp;
+ }
+ if (mMaxTimestamp < event.timestamp) {
+ mMaxTimestamp = event.timestamp;
+ }
+ }
+ mCount++;
+ }
+
+ @SuppressWarnings("deprecation")
+ private static void setDefaults() {
+ DEFAULTS.put(Sensor.TYPE_ACCELEROMETER, DEFAULT_THRESHOLDS);
+ DEFAULTS.put(Sensor.TYPE_MAGNETIC_FIELD, DEFAULT_THRESHOLDS);
+ DEFAULTS.put(Sensor.TYPE_GYROSCOPE, DEFAULT_THRESHOLDS);
+ DEFAULTS.put(Sensor.TYPE_ORIENTATION, DEFAULT_THRESHOLDS);
+ DEFAULTS.put(Sensor.TYPE_PRESSURE, DEFAULT_THRESHOLDS);
+ DEFAULTS.put(Sensor.TYPE_GRAVITY, DEFAULT_THRESHOLDS);
+ DEFAULTS.put(Sensor.TYPE_LINEAR_ACCELERATION, DEFAULT_THRESHOLDS);
+ DEFAULTS.put(Sensor.TYPE_ROTATION_VECTOR, DEFAULT_THRESHOLDS);
+ DEFAULTS.put(Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED, DEFAULT_THRESHOLDS);
+ DEFAULTS.put(Sensor.TYPE_GAME_ROTATION_VECTOR, DEFAULT_THRESHOLDS);
+ DEFAULTS.put(Sensor.TYPE_GYROSCOPE_UNCALIBRATED, DEFAULT_THRESHOLDS);
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/FrequencyVerificationTest.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/FrequencyVerificationTest.java
new file mode 100644
index 0000000..cec09a5
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/FrequencyVerificationTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.cts.helpers.sensorverification;
+
+import android.hardware.cts.helpers.SensorStats;
+import android.hardware.cts.helpers.TestSensorEvent;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for {@link EventOrderingVerification}.
+ */
+public class FrequencyVerificationTest extends TestCase {
+
+ /**
+ * Test that the verifications passes/fails based on threshold given.
+ */
+ public void testVerifification() {
+ long[] timestamps = {0, 1000000, 2000000, 3000000, 4000000}; // 1000Hz
+
+ SensorStats stats = new SensorStats();
+ ISensorVerification verification = getVerification(1000.0, 1.0, 1.0, timestamps);
+ verification.verify(stats);
+ verifyStats(stats, true, 1000.0);
+
+ stats = new SensorStats();
+ verification = getVerification(950.0, 100.0, 100.0, timestamps);
+ verification.verify(stats);
+ verifyStats(stats, true, 1000.0);
+
+ stats = new SensorStats();
+ verification = getVerification(1050.0, 100.0, 100.0, timestamps);
+ verification.verify(stats);
+ verifyStats(stats, true, 1000.0);
+
+ stats = new SensorStats();
+ verification = getVerification(950.0, 100.0, 25.0, timestamps);
+ try {
+ verification.verify(stats);
+ fail("Expected an AssertionError");
+ } catch (AssertionError e) {
+ // Expected;
+ }
+ verifyStats(stats, false, 1000.0);
+
+ stats = new SensorStats();
+ verification = getVerification(1050.0, 25.0, 100.0, timestamps);
+ try {
+ verification.verify(stats);
+ fail("Expected an AssertionError");
+ } catch (AssertionError e) {
+ // Expected;
+ }
+ verifyStats(stats, false, 1000.0);
+ }
+
+ private ISensorVerification getVerification(double expected, double lowerThreshold,
+ double upperThreshold, long ... timestamps) {
+ ISensorVerification verification = new FrequencyVerification(expected, lowerThreshold,
+ upperThreshold);
+ for (long timestamp : timestamps) {
+ verification.addSensorEvent(new TestSensorEvent(null, timestamp, 0, null));
+ }
+ return verification;
+ }
+
+ private void verifyStats(SensorStats stats, boolean passed, double frequency) {
+ assertEquals(passed, stats.getValue(FrequencyVerification.PASSED_KEY));
+ assertEquals(frequency, stats.getValue(SensorStats.FREQUENCY_KEY));
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/ISensorVerification.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/ISensorVerification.java
new file mode 100644
index 0000000..07af392
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/ISensorVerification.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.cts.helpers.sensorverification;
+
+import android.hardware.cts.helpers.SensorStats;
+import android.hardware.cts.helpers.TestSensorEvent;
+
+/**
+ * Interface describing the sensor verification. This class was designed for to handle streaming
+ * events. The methods {@link #addSensorEvent(TestSensorEvent)} and
+ * {@link #addSensorEvents(TestSensorEvent...)} should be called in the order that the events are
+ * received. The method {@link #verify(SensorStats)} should be called after all events are added.
+ */
+public interface ISensorVerification {
+
+ /**
+ * Add a single {@link TestSensorEvent} to be evaluated.
+ */
+ public void addSensorEvent(TestSensorEvent event);
+
+ /**
+ * Add multiple {@link TestSensorEvent}s to be evaluated.
+ */
+ public void addSensorEvents(TestSensorEvent ... events);
+
+ /**
+ * Evaluate all added {@link TestSensorEvent}s and update stats.
+ *
+ * @param stats a {@link SensorStats} object used to keep track of the stats.
+ * @throws AssertionError if the verification fails.
+ */
+ public void verify(SensorStats stats);
+
+ /**
+ * Clones the {@link ISensorVerification}
+ */
+ public ISensorVerification clone();
+}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/JitterVerification.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/JitterVerification.java
new file mode 100644
index 0000000..7929ee9
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/JitterVerification.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.cts.helpers.sensorverification;
+
+import android.hardware.Sensor;
+import android.hardware.cts.helpers.SensorCtsHelper;
+import android.hardware.cts.helpers.SensorStats;
+import android.hardware.cts.helpers.TestSensorEvent;
+
+import junit.framework.Assert;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A {@link ISensorVerification} which verifies that the sensor jitter is in an acceptable range.
+ */
+public class JitterVerification extends AbstractSensorVerification {
+ public static final String PASSED_KEY = "jitter_passed";
+
+ // sensorType: threshold (% of expected period)
+ private static final Map<Integer, Integer> DEFAULTS = new HashMap<Integer, Integer>(12);
+ static {
+ // Use a method so that the @deprecation warning can be set for that method only
+ setDefaults();
+ }
+
+ private final int mExpected;
+ private final int mThreshold;
+
+ private List<Long> mTimestamps = new LinkedList<Long>();
+
+ /**
+ * Construct a {@link JitterVerification}
+ *
+ * @param expected the expected period in ns
+ * @param threshold the acceptable margin of error as a percentage
+ */
+ public JitterVerification(int expected, int threshold) {
+ mExpected = expected;
+ mThreshold = threshold;
+ }
+
+ /**
+ * Get the default {@link JitterVerification} for a sensor.
+ *
+ * @param sensor a {@link Sensor}
+ * @param rateUs the desired rate of the sensor
+ * @return the verification or null if the verification does not apply to the sensor.
+ */
+ public static JitterVerification getDefault(Sensor sensor, int rateUs) {
+ if (!DEFAULTS.containsKey(sensor.getType())) {
+ return null;
+ }
+
+ int expected = (int) TimeUnit.NANOSECONDS.convert(SensorCtsHelper.getDelay(sensor, rateUs),
+ TimeUnit.MICROSECONDS);
+ return new JitterVerification(expected, DEFAULTS.get(sensor.getType()));
+ }
+
+ /**
+ * Verify that the 95th percentile of the jitter is in the acceptable range. Add
+ * {@value #PASSED_KEY} and {@value SensorStats#JITTER_95_PERCENTILE_KEY} keys to
+ * {@link SensorStats}.
+ *
+ * @throws AssertionError if the verification failed.
+ */
+ @Override
+ public void verify(SensorStats stats) {
+ if (mTimestamps.size() < 2) {
+ stats.addValue(PASSED_KEY, true);
+ return;
+ }
+
+ List<Double> jitters = getJitterValues();
+ double jitter95Percentile = SensorCtsHelper.get95PercentileValue(jitters);
+ boolean failed = (jitter95Percentile > mExpected * (mThreshold / 100.0));
+
+ stats.addValue(PASSED_KEY, !failed);
+ stats.addValue(SensorStats.JITTER_95_PERCENTILE_KEY, jitter95Percentile);
+
+ if (failed) {
+ Assert.fail(String.format("Jitter out of range: jitter at 95th percentile=%.0fns, "
+ + "expected=<%.0fns", jitter95Percentile, mExpected * (mThreshold / 100.0)));
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public JitterVerification clone() {
+ return new JitterVerification(mExpected, mThreshold);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void addSensorEventInternal(TestSensorEvent event) {
+ mTimestamps.add(event.timestamp);
+ }
+
+ /**
+ * Get the list of all jitter values. Exposed for unit testing.
+ */
+ List<Double> getJitterValues() {
+ List<Long> deltas = new ArrayList<Long>(mTimestamps.size() - 1);
+ for (int i = 1; i < mTimestamps.size(); i++) {
+ deltas.add(mTimestamps.get(i) - mTimestamps.get(i -1));
+ }
+ double deltaMean = SensorCtsHelper.getMean(deltas);
+ List<Double> jitters = new ArrayList<Double>(deltas.size());
+ for (long delta : deltas) {
+ jitters.add(Math.abs(delta - deltaMean));
+ }
+ return jitters;
+ }
+
+ @SuppressWarnings("deprecation")
+ private static void setDefaults() {
+ // Sensors that we don't want to test at this time but still want to record the values.
+ DEFAULTS.put(Sensor.TYPE_ACCELEROMETER, Integer.MAX_VALUE);
+ DEFAULTS.put(Sensor.TYPE_MAGNETIC_FIELD, Integer.MAX_VALUE);
+ DEFAULTS.put(Sensor.TYPE_GYROSCOPE, Integer.MAX_VALUE);
+ DEFAULTS.put(Sensor.TYPE_ORIENTATION, Integer.MAX_VALUE);
+ DEFAULTS.put(Sensor.TYPE_PRESSURE, Integer.MAX_VALUE);
+ DEFAULTS.put(Sensor.TYPE_GRAVITY, Integer.MAX_VALUE);
+ DEFAULTS.put(Sensor.TYPE_LINEAR_ACCELERATION, Integer.MAX_VALUE);
+ DEFAULTS.put(Sensor.TYPE_ROTATION_VECTOR, Integer.MAX_VALUE);
+ DEFAULTS.put(Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED, Integer.MAX_VALUE);
+ DEFAULTS.put(Sensor.TYPE_GAME_ROTATION_VECTOR, Integer.MAX_VALUE);
+ DEFAULTS.put(Sensor.TYPE_GYROSCOPE_UNCALIBRATED, Integer.MAX_VALUE);
+ DEFAULTS.put(Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR, Integer.MAX_VALUE);
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/JitterVerificationTest.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/JitterVerificationTest.java
new file mode 100644
index 0000000..a9e872a
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/JitterVerificationTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.cts.helpers.sensorverification;
+
+import android.hardware.cts.helpers.SensorStats;
+import android.hardware.cts.helpers.TestSensorEvent;
+
+import junit.framework.TestCase;
+
+import java.util.List;
+
+/**
+ * Tests for {@link EventOrderingVerification}.
+ */
+public class JitterVerificationTest extends TestCase {
+
+
+ public void testVerify() {
+ final int SAMPLE_SIZE = 100;
+
+ // 100 samples at 1000Hz
+ long[] timestamps = new long[SAMPLE_SIZE];
+ for (int i = 0; i < SAMPLE_SIZE; i++) {
+ timestamps[i] = i * 100000;
+ }
+ SensorStats stats = new SensorStats();
+ ISensorVerification verification = getVerification(1000, 1, timestamps);
+ verification.verify(stats);
+ verifyStats(stats, true, 0.0);
+
+ // 90 samples at 1000Hz, 10 samples at 2000Hz
+ long timestamp = 0;
+ for (int i = 0; i < SAMPLE_SIZE; i++) {
+ timestamps[i] = timestamp;
+ timestamp += (i % 10 == 0) ? 500000 : 1000000;
+ }
+ stats = new SensorStats();
+ verification = getVerification(1000, 1, timestamps);
+ try {
+ verification.verify(stats);
+ fail("Expected an AssertionError");
+ } catch (AssertionError e) {
+ // Expected;
+ }
+ verifyStats(stats, false, 449494.9494);
+ }
+
+ public void testCalculateJitter() {
+ long[] timestamps = new long[]{0, 1, 2, 3, 4};
+ JitterVerification verification = getVerification(1000, 1, timestamps);
+ List<Double> jitterValues = verification.getJitterValues();
+ assertEquals(4, jitterValues.size());
+ assertEquals(0.0, (double) jitterValues.get(0));
+ assertEquals(0.0, (double) jitterValues.get(1));
+ assertEquals(0.0, (double) jitterValues.get(2));
+ assertEquals(0.0, (double) jitterValues.get(3));
+
+ timestamps = new long[]{0, 0, 2, 4, 4};
+ verification = getVerification(1000, 1, timestamps);
+ jitterValues = verification.getJitterValues();
+ assertEquals(4, jitterValues.size());
+ assertEquals(1.0, (double) jitterValues.get(0));
+ assertEquals(1.0, (double) jitterValues.get(1));
+ assertEquals(1.0, (double) jitterValues.get(2));
+ assertEquals(1.0, (double) jitterValues.get(3));
+
+ timestamps = new long[]{0, 1, 4, 9, 16};
+ verification = getVerification(1000, 1, timestamps);
+ jitterValues = verification.getJitterValues();
+ assertEquals(4, jitterValues.size());
+ assertEquals(4, jitterValues.size());
+ assertEquals(3.0, (double) jitterValues.get(0));
+ assertEquals(1.0, (double) jitterValues.get(1));
+ assertEquals(1.0, (double) jitterValues.get(2));
+ assertEquals(3.0, (double) jitterValues.get(3));
+ }
+
+ private JitterVerification getVerification(int expected, int threshold, long ... timestamps) {
+ JitterVerification verification = new JitterVerification(expected, threshold);
+ for (long timestamp : timestamps) {
+ verification.addSensorEvent(new TestSensorEvent(null, timestamp, 0, null));
+ }
+ return verification;
+ }
+
+ private void verifyStats(SensorStats stats, boolean passed, double jitter95) {
+ assertEquals(passed, stats.getValue(JitterVerification.PASSED_KEY));
+ assertEquals(jitter95, (Double) stats.getValue(SensorStats.JITTER_95_PERCENTILE_KEY), 0.1);
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/MagnitudeVerification.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/MagnitudeVerification.java
new file mode 100644
index 0000000..d987eef
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/MagnitudeVerification.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.cts.helpers.sensorverification;
+
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
+import android.hardware.cts.helpers.SensorStats;
+import android.hardware.cts.helpers.TestSensorEvent;
+
+import junit.framework.Assert;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A {@link ISensorVerification} which verifies that the mean of the magnitude of the sensors vector
+ * is within the expected range.
+ */
+public class MagnitudeVerification extends AbstractSensorVerification {
+ public static final String PASSED_KEY = "magnitude_passed";
+
+ // sensorType: {expected, threshold}
+ private static Map<Integer, Float[]> DEFAULTS = new HashMap<Integer, Float[]>(3);
+ static {
+ // Use a method so that the @deprecation warning can be set for that method only
+ setDefaults();
+ }
+
+ private final float mExpected;
+ private final float mThreshold;
+
+ private float mSum = 0.0f;
+ private int mCount = 0;
+
+ /**
+ * Construct a {@link MagnitudeVerification}
+ *
+ * @param expected the expected value
+ * @param threshold the threshold
+ */
+ public MagnitudeVerification(float expected, float threshold) {
+ mExpected = expected;
+ mThreshold = threshold;
+ }
+
+ /**
+ * Get the default {@link MagnitudeVerification} for a sensor.
+ *
+ * @param sensor a {@link Sensor}
+ * @return the verification or null if the verification does not apply to the sensor.
+ */
+ public static MagnitudeVerification getDefault(Sensor sensor) {
+ if (!DEFAULTS.containsKey(sensor.getType())) {
+ return null;
+ }
+ Float expected = DEFAULTS.get(sensor.getType())[0];
+ Float threshold = DEFAULTS.get(sensor.getType())[1];
+ return new MagnitudeVerification(expected, threshold);
+ }
+
+ /**
+ * Verify that the magnitude is in the acceptable range. Add {@value #PASSED_KEY} and
+ * {@value SensorStats#MAGNITUDE_KEY} keys to {@link SensorStats}.
+ *
+ * @throws AssertionError if the verification failed.
+ */
+ @Override
+ public void verify(SensorStats stats) {
+ if (mCount < 1) {
+ stats.addValue(PASSED_KEY, true);
+ return;
+ }
+
+ float mean = mSum / mCount;
+ boolean failed = Math.abs(mean - mExpected) > mThreshold;
+
+ stats.addValue(PASSED_KEY, !failed);
+ stats.addValue(SensorStats.MAGNITUDE_KEY, mean);
+
+ if (failed) {
+ Assert.fail(String.format("Magnitude mean out of range: mean=%s, expected=%s+/-%s",
+ mean, mExpected, mThreshold));
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public MagnitudeVerification clone() {
+ return new MagnitudeVerification(mExpected, mThreshold);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void addSensorEventInternal(TestSensorEvent event) {
+ float sumOfSquares = 0.0f;
+ for (float value : event.values) {
+ sumOfSquares += value * value;
+ }
+ mSum += (float) Math.sqrt(sumOfSquares);
+ mCount++;
+ }
+
+ @SuppressWarnings("deprecation")
+ private static void setDefaults() {
+ DEFAULTS.put(Sensor.TYPE_ACCELEROMETER, new Float[]{SensorManager.STANDARD_GRAVITY, 1.5f});
+ DEFAULTS.put(Sensor.TYPE_GYROSCOPE, new Float[]{0.0f, 1.5f});
+ // Sensors that we don't want to test at this time but still want to record the values.
+ DEFAULTS.put(Sensor.TYPE_GRAVITY,
+ new Float[]{SensorManager.STANDARD_GRAVITY, Float.MAX_VALUE});
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/MagnitudeVerificationTest.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/MagnitudeVerificationTest.java
new file mode 100644
index 0000000..9a50753
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/MagnitudeVerificationTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.cts.helpers.sensorverification;
+
+import android.hardware.cts.helpers.SensorStats;
+import android.hardware.cts.helpers.TestSensorEvent;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for {@link MagnitudeVerification}.
+ */
+public class MagnitudeVerificationTest extends TestCase {
+
+ /**
+ * Test {@link MagnitudeVerification#verify(SensorStats)}.
+ */
+ public void testVerify() {
+ float[][] values = {
+ {0, 3, 4},
+ {4, 0, 3},
+ {3, 4, 0},
+ {0, 0, 4},
+ {6, 0, 0},
+ };
+
+ runStats(5.0f, 0.1f, values, true, 5.0f);
+ runStats(4.5f, 0.6f, values, true, 5.0f);
+ runStats(5.5f, 0.6f, values, true, 5.0f);
+ runStats(4.5f, 0.1f, values, false, 5.0f);
+ runStats(5.5f, 0.1f, values, false, 5.0f);
+ }
+
+ private void runStats(float expected, float threshold, float[][] values, boolean pass, float magnitude) {
+ SensorStats stats = new SensorStats();
+ ISensorVerification verification = getVerification(expected, threshold, values);
+ if (pass) {
+ verification.verify(stats);
+ } else {
+ try {
+ verification.verify(stats);
+ fail("Expected an AssertionError");
+ } catch (AssertionError e) {
+ // Expected;
+ }
+ }
+ assertEquals(pass, stats.getValue(MagnitudeVerification.PASSED_KEY));
+ assertEquals(magnitude, (Float) stats.getValue(SensorStats.MAGNITUDE_KEY), 0.01);
+ }
+
+ private ISensorVerification getVerification(float expected, float threshold,
+ float[] ... values) {
+ ISensorVerification verification = new MagnitudeVerification(expected, threshold);
+ for (float[] value : values) {
+ verification.addSensorEvent(new TestSensorEvent(null, 0, 0, value));
+ }
+ return verification;
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/MeanVerification.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/MeanVerification.java
new file mode 100644
index 0000000..315b3aa
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/MeanVerification.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.cts.helpers.sensorverification;
+
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
+import android.hardware.cts.helpers.SensorStats;
+
+import junit.framework.Assert;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A {@link ISensorVerification} which verifies that the means matches the expected measurement.
+ */
+public class MeanVerification extends AbstractMeanVerification {
+ public static final String PASSED_KEY = "mean_passed";
+
+ // sensorType: {expected, threshold}
+ private static final Map<Integer, Object[]> DEFAULTS = new HashMap<Integer, Object[]>(5);
+ static {
+ // Use a method so that the @deprecation warning can be set for that method only
+ setDefaults();
+ }
+
+ private final float[] mExpected;
+ private final float[] mThreshold;
+
+ /**
+ * Construct a {@link MeanVerification}
+ *
+ * @param expected the expected values
+ * @param threshold the thresholds
+ */
+ public MeanVerification(float[] expected, float[] threshold) {
+ mExpected = expected;
+ mThreshold = threshold;
+ }
+
+ /**
+ * Get the default {@link MeanVerification} for a sensor.
+ *
+ * @param sensor a {@link Sensor}
+ * @return the verification or null if the verification does not apply to the sensor.
+ */
+ public static MeanVerification getDefault(Sensor sensor) {
+ if (!DEFAULTS.containsKey(sensor.getType())) {
+ return null;
+ }
+ float[] expected = (float[]) DEFAULTS.get(sensor.getType())[0];
+ float[] threshold = (float[]) DEFAULTS.get(sensor.getType())[1];
+ return new MeanVerification(expected, threshold);
+ }
+
+ /**
+ * Verify that the mean is in the acceptable range. Add {@value #PASSED_KEY} and
+ * {@value SensorStats#MEAN_KEY} keys to {@link SensorStats}.
+ *
+ * @throws AssertionError if the verification failed.
+ */
+ @Override
+ public void verify(SensorStats stats) {
+ if (getCount() < 1) {
+ stats.addValue(PASSED_KEY, true);
+ return;
+ }
+
+ float[] means = getMeans();
+
+ boolean failed = false;
+ StringBuilder meanSb = new StringBuilder();
+ StringBuilder expectedSb = new StringBuilder();
+
+ if (means.length > 1) {
+ meanSb.append("(");
+ expectedSb.append("(");
+ }
+ for (int i = 0; i < means.length; i++) {
+ if (Math.abs(means[i] - mExpected[i]) > mThreshold[i]) {
+ failed = true;
+ }
+ meanSb.append(String.format("%.2f", means[i]));
+ if (i != means.length - 1) meanSb.append(", ");
+ expectedSb.append(String.format("%.2f+/-%.2f", mExpected[i], mThreshold[i]));
+ if (i != means.length - 1) expectedSb.append(", ");
+ }
+ if (means.length > 1) {
+ meanSb.append(")");
+ expectedSb.append(")");
+ }
+
+ stats.addValue(PASSED_KEY, !failed);
+ stats.addValue(SensorStats.MEAN_KEY, means);
+
+ if (failed) {
+ Assert.fail(String.format("Mean out of range: mean=%s, expected=%s", meanSb.toString(),
+ expectedSb.toString()));
+ }
+ }
+
+ @Override
+ public MeanVerification clone() {
+ return new MeanVerification(mExpected, mThreshold);
+ }
+
+ @SuppressWarnings("deprecation")
+ private static void setDefaults() {
+ // Sensors that we don't want to test at this time but still want to record the values.
+ // Gyroscope should be 0 for a static device
+ DEFAULTS.put(Sensor.TYPE_GYROSCOPE, new Object[]{
+ new float[]{0.0f, 0.0f, 0.0f},
+ new float[]{Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE}});
+ // Pressure will not be exact in a controlled environment but should be relatively close to
+ // sea level. Second values should always be 0.
+ DEFAULTS.put(Sensor.TYPE_PRESSURE, new Object[]{
+ new float[]{SensorManager.PRESSURE_STANDARD_ATMOSPHERE, 0.0f, 0.0f},
+ new float[]{Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE}});
+ // Linear acceleration should be 0 in all directions for a static device
+ DEFAULTS.put(Sensor.TYPE_LINEAR_ACCELERATION, new Object[]{
+ new float[]{0.0f, 0.0f, 0.0f},
+ new float[]{Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE}});
+ // Game rotation vector should be (0, 0, 0, 1, 0) for a static device
+ DEFAULTS.put(Sensor.TYPE_GAME_ROTATION_VECTOR, new Object[]{
+ new float[]{0.0f, 0.0f, 0.0f, 1.0f, 0.0f},
+ new float[]{Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE,
+ Float.MAX_VALUE}});
+ // Uncalibrated gyroscope should be 0 for a static device but allow a bigger threshold
+ DEFAULTS.put(Sensor.TYPE_GYROSCOPE_UNCALIBRATED, new Object[]{
+ new float[]{0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f},
+ new float[]{Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE,
+ Float.MAX_VALUE, Float.MAX_VALUE}});
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/MeanVerificationTest.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/MeanVerificationTest.java
new file mode 100644
index 0000000..94b6362
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/MeanVerificationTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.cts.helpers.sensorverification;
+
+import android.hardware.cts.helpers.SensorStats;
+import android.hardware.cts.helpers.TestSensorEvent;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for {@link MeanVerification}.
+ */
+public class MeanVerificationTest extends TestCase {
+
+ /**
+ * Test {@link MeanVerification#verify(SensorStats)}.
+ */
+ public void testVerify() {
+ float[][] values = {
+ {0, 1, 0},
+ {1, 2, 1},
+ {2, 3, 4},
+ {3, 4, 9},
+ {4, 5, 16},
+ };
+
+ float[] expected = {2.0f, 3.0f, 6.0f};
+ float[] threshold = {0.1f, 0.1f, 0.1f};
+ SensorStats stats = new SensorStats();
+ ISensorVerification verification = getVerification(expected, threshold, values);
+ verification.verify(stats);
+ verifyStats(stats, true, new float[]{2.0f, 3.0f, 6.0f});
+
+ expected = new float[]{2.5f, 2.5f, 5.5f};
+ threshold = new float[]{0.6f, 0.6f, 0.6f};
+ stats = new SensorStats();
+ verification = getVerification(expected, threshold, values);
+ verification.verify(stats);
+ verifyStats(stats, true, new float[]{2.0f, 3.0f, 6.0f});
+
+ expected = new float[]{2.5f, 2.5f, 5.5f};
+ threshold = new float[]{0.1f, 0.6f, 0.6f};
+ stats = new SensorStats();
+ verification = getVerification(expected, threshold, values);
+ try {
+ verification.verify(stats);
+ fail("Expected an AssertionError");
+ } catch (AssertionError e) {
+ // Expected;
+ }
+ verifyStats(stats, false, new float[]{2.0f, 3.0f, 6.0f});
+
+ expected = new float[]{2.5f, 2.5f, 5.5f};
+ threshold = new float[]{0.6f, 0.1f, 0.6f};
+ stats = new SensorStats();
+ verification = getVerification(expected, threshold, values);
+ try {
+ verification.verify(stats);
+ fail("Expected an AssertionError");
+ } catch (AssertionError e) {
+ // Expected;
+ }
+ verifyStats(stats, false, new float[]{2.0f, 3.0f, 6.0f});
+
+ threshold = new float[]{2.5f, 2.5f, 5.5f};
+ threshold = new float[]{0.6f, 0.6f, 0.1f};
+ stats = new SensorStats();
+ verification = getVerification(expected, threshold, values);
+ try {
+ verification.verify(stats);
+ fail("Expected an AssertionError");
+ } catch (AssertionError e) {
+ // Expected;
+ }
+ verifyStats(stats, false, new float[]{2.0f, 3.0f, 6.0f});
+ }
+
+ private ISensorVerification getVerification(float[] expected, float[] threshold,
+ float[] ... values) {
+ ISensorVerification verification = new MeanVerification(expected, threshold);
+ for (float[] value : values) {
+ verification.addSensorEvent(new TestSensorEvent(null, 0, 0, value));
+ }
+ return verification;
+ }
+
+ private void verifyStats(SensorStats stats, boolean passed, float[] means) {
+ assertEquals(passed, stats.getValue(MeanVerification.PASSED_KEY));
+ float[] actual = (float[]) stats.getValue(SensorStats.MEAN_KEY);
+ for (int i = 0; i < means.length; i++) {
+ assertEquals(means[i], actual[i], 0.1);
+ }
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/SigNumVerification.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/SigNumVerification.java
new file mode 100644
index 0000000..9417cd3
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/SigNumVerification.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.cts.helpers.sensorverification;
+
+import android.hardware.cts.helpers.SensorStats;
+
+import junit.framework.Assert;
+
+/**
+ * A {@link ISensorVerification} which verifies that the sign of each of the sensor values is
+ * correct.
+ * <p>
+ * If the value of the measurement is in [-threshold, threshold], the sign is considered 0. If
+ * it is less than -threshold, it is considered -1. If it is greater than threshold, it is
+ * considered 1.
+ * </p>
+ */
+public class SigNumVerification extends AbstractMeanVerification {
+ public static final String PASSED_KEY = "sig_num_passed";
+
+ private final int[] mExpected;
+ private final float[] mThreshold;
+
+ /**
+ * Construct a {@link SigNumVerification}
+ *
+ * @param expected the expected values
+ * @param threshold the threshold that needs to be crossed to consider a measurement nonzero
+ * @throws IllegalStateException if the expected values are not 0, -1, or 1.
+ */
+ public SigNumVerification(int[] expected, float[] threshold) {
+ for (int i = 0; i < expected.length; i++) {
+ if (!(expected[i] == -1 || expected[i] == 0 || expected[i] == 1)) {
+ throw new IllegalArgumentException("Expected value must be -1, 0, or 1");
+ }
+ }
+
+ mExpected = expected;
+ mThreshold = threshold;
+ }
+
+ /**
+ * Verify that the sign of each of the sensor values is correct. Add {@value #PASSED_KEY} and
+ * {@value SensorStats#MEAN_KEY} keys to {@link SensorStats}.
+ *
+ * @throws AssertionError if the verification failed.
+ */
+ @Override
+ public void verify(SensorStats stats) {
+ if (getCount() < 1) {
+ stats.addValue(PASSED_KEY, true);
+ return;
+ }
+
+ float[] means = getMeans();
+
+ boolean failed = false;
+ StringBuilder meanSb = new StringBuilder();
+ StringBuilder expectedSb = new StringBuilder();
+
+ if (means.length > 1) {
+ meanSb.append("(");
+ expectedSb.append("(");
+ }
+ for (int i = 0; i < means.length; i++) {
+ meanSb.append(String.format("%.2f", means[i]));
+ if (i != means.length - 1) meanSb.append(", ");
+
+ if (mExpected[i] == 0) {
+ if (Math.abs(means[i]) > mThreshold[i]) {
+ failed = true;
+ }
+ expectedSb.append(String.format("[%.2f, %.2f]", -mThreshold[i], mThreshold[i]));
+ } else {
+ if (mExpected[i] > 0) {
+ if (means[i] <= mThreshold[i]) {
+ failed = true;
+ }
+ expectedSb.append(String.format("(%.2f, inf)", mThreshold[i]));
+ } else {
+ if (means[i] >= -1 * mThreshold[i]) {
+ failed = true;
+ }
+ expectedSb.append(String.format("(-inf, %.2f)", -1 * mThreshold[i]));
+ }
+ }
+ if (i != means.length - 1) expectedSb.append(", ");
+ }
+ if (means.length > 1) {
+ meanSb.append(")");
+ expectedSb.append(")");
+ }
+
+ stats.addValue(PASSED_KEY, !failed);
+ stats.addValue(SensorStats.MEAN_KEY, means);
+
+ if (failed) {
+ Assert.fail(String.format("Signum out of range: mean=%s, expected=%s",
+ meanSb.toString(), expectedSb.toString()));
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public SigNumVerification clone() {
+ return new SigNumVerification(mExpected, mThreshold);
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/SigNumVerificationTest.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/SigNumVerificationTest.java
new file mode 100644
index 0000000..009ab65
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/SigNumVerificationTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.cts.helpers.sensorverification;
+
+import android.hardware.cts.helpers.SensorStats;
+import android.hardware.cts.helpers.TestSensorEvent;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for {@link SigNumVerification}.
+ */
+public class SigNumVerificationTest extends TestCase {
+
+ /**
+ * Test {@link SigNumVerification#verify(SensorStats)}.
+ */
+ public void testVerify() {
+ float[][] values = {{1.0f, 0.2f, 0.0f, -0.2f, -1.0f}};
+
+ int[] expected = {1, 1, 0, -1, -1};
+ float[] threshold = {0.1f, 0.1f, 0.1f, 0.1f, 0.1f};
+ runVerification(true, expected, threshold, values);
+
+ expected = new int[]{1, 0, 0, 0, -1};
+ threshold = new float[]{0.5f, 0.5f, 0.5f, 0.5f, 0.5f};
+ runVerification(true, expected, threshold, values);
+
+ expected = new int[]{0, 1, 0, -1, 0};
+ threshold = new float[]{1.5f, 0.1f, 0.1f, 0.1f, 1.5f};
+ runVerification(true, expected, threshold, values);
+
+ expected = new int[]{1, 0, 0, 0, 1};
+ threshold = new float[]{0.5f, 0.5f, 0.5f, 0.5f, 0.5f};
+ runVerification(false, expected, threshold, values);
+
+ expected = new int[]{-1, 0, 0, 0, -1};
+ threshold = new float[]{0.5f, 0.5f, 0.5f, 0.5f, 0.5f};
+ runVerification(false, expected, threshold, values);
+ }
+
+ private SigNumVerification getVerification(int[] expected, float[] threshold,
+ float[] ... values) {
+ SigNumVerification verification = new SigNumVerification(expected, threshold);
+ for (float[] value : values) {
+ verification.addSensorEvent(new TestSensorEvent(null, 0, 0, value));
+ }
+ return verification;
+ }
+
+ private void runVerification(boolean passed, int[] expected, float[] threshold,
+ float[][] values) {
+ SensorStats stats = new SensorStats();
+ ISensorVerification verification = getVerification(expected, threshold, values);
+ if (passed) {
+ verification.verify(stats);
+ } else {
+ try {
+ verification.verify(stats);
+ fail("Expected an AssertionError");
+ } catch (AssertionError e) {
+ // Expected;
+ }
+ }
+ assertEquals(passed, stats.getValue(SigNumVerification.PASSED_KEY));
+ assertNotNull(stats.getValue(SensorStats.MEAN_KEY));
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/StandardDeviationVerification.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/StandardDeviationVerification.java
new file mode 100644
index 0000000..03db067
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/StandardDeviationVerification.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.cts.helpers.sensorverification;
+
+import android.hardware.Sensor;
+import android.hardware.cts.helpers.SensorStats;
+import android.hardware.cts.helpers.TestSensorEvent;
+
+import junit.framework.Assert;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A {@link ISensorVerification} which verifies that the standard deviations is within the expected
+ * range.
+ */
+public class StandardDeviationVerification extends AbstractSensorVerification {
+ public static final String PASSED_KEY = "standard_deviation_passed";
+
+ // sensorType: threshold
+ private static final Map<Integer, float[]> DEFAULTS = new HashMap<Integer, float[]>(12);
+ static {
+ // Use a method so that the @deprecation warning can be set for that method only
+ setDefaults();
+ }
+
+ private final float[] mThreshold;
+
+ private float[] mMeans = null;
+ private float[] mM2s = null;
+ private int mCount = 0;
+
+ /**
+ * Construct a {@link StandardDeviationVerification}
+ *
+ * @param threshold the thresholds
+ */
+ public StandardDeviationVerification(float[] threshold) {
+ mThreshold = threshold;
+ }
+
+ /**
+ * Get the default {@link StandardDeviationVerification} for a sensor.
+ *
+ * @param sensor a {@link Sensor}
+ * @return the verification or null if the verification does not apply to the sensor.
+ */
+ public static StandardDeviationVerification getDefault(Sensor sensor) {
+ if (!DEFAULTS.containsKey(sensor.getType())) {
+ return null;
+ }
+
+ return new StandardDeviationVerification(DEFAULTS.get(sensor.getType()));
+ }
+
+ /**
+ * Verify that the standard deviation is in the acceptable range. Add {@value #PASSED_KEY} and
+ * {@value SensorStats#STANDARD_DEVIATION_KEY} keys to {@link SensorStats}.
+ *
+ * @throws AssertionError if the verification failed.
+ */
+ @Override
+ public void verify(SensorStats stats) {
+ if (mCount < 2) {
+ stats.addValue(PASSED_KEY, true);
+ return;
+ }
+
+ float[] stdDevs = new float[mM2s.length];
+ for (int i = 0; i < mM2s.length; i++) {
+ stdDevs[i] = (float) Math.sqrt(mM2s[i] / (mCount - 1));
+ }
+
+ boolean failed = false;
+ StringBuilder stddevSb = new StringBuilder();
+ StringBuilder expectedSb = new StringBuilder();
+
+ if (stdDevs.length > 1) {
+ stddevSb.append("(");
+ expectedSb.append("(");
+ }
+ for (int i = 0; i < stdDevs.length; i++) {
+ if (stdDevs[i] > mThreshold[i]) {
+ failed = true;
+ }
+ stddevSb.append(String.format("%.2f", stdDevs[i]));
+ if (i != stdDevs.length - 1) stddevSb.append(", ");
+ expectedSb.append(String.format("<%.2f", mThreshold[i]));
+ if (i != stdDevs.length - 1) expectedSb.append(", ");
+ }
+ if (stdDevs.length > 1) {
+ stddevSb.append(")");
+ expectedSb.append(")");
+ }
+
+ stats.addValue(PASSED_KEY, !failed);
+ stats.addValue(SensorStats.STANDARD_DEVIATION_KEY, stdDevs);
+
+ if (failed) {
+ Assert.fail(String.format("Standard deviation out of range: stddev=%s, expected=%s",
+ stddevSb.toString(), expectedSb.toString()));
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public StandardDeviationVerification clone() {
+ return new StandardDeviationVerification(mThreshold);
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * Computes the standard deviation using
+ * <a href="http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#On-line_algorithm">
+ * Welford's algorith</a>.
+ * </p>
+ */
+ @Override
+ protected void addSensorEventInternal(TestSensorEvent event) {
+ if (mMeans == null || mM2s == null) {
+ mMeans = new float[event.values.length];
+ mM2s = new float[event.values.length];
+ }
+
+ Assert.assertEquals(mMeans.length, event.values.length);
+ Assert.assertEquals(mM2s.length, event.values.length);
+
+ mCount++;
+
+ for (int i = 0; i < event.values.length; i++) {
+ float delta = event.values[i] - mMeans[i];
+ mMeans[i] += delta / mCount;
+ mM2s[i] += delta * (event.values[i] - mMeans[i]);
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ private static void setDefaults() {
+ DEFAULTS.put(Sensor.TYPE_ACCELEROMETER, new float[]{1.0f, 1.0f, 1.0f});
+ DEFAULTS.put(Sensor.TYPE_GYROSCOPE, new float[]{0.5f, 0.5f, 0.5f});
+ // Sensors that we don't want to test at this time but still want to record the values.
+ DEFAULTS.put(Sensor.TYPE_MAGNETIC_FIELD,
+ new float[]{Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE});
+ DEFAULTS.put(Sensor.TYPE_ORIENTATION,
+ new float[]{Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE});
+ DEFAULTS.put(Sensor.TYPE_PRESSURE,
+ new float[]{Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE});
+ DEFAULTS.put(Sensor.TYPE_GRAVITY,
+ new float[]{Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE});
+ DEFAULTS.put(Sensor.TYPE_LINEAR_ACCELERATION,
+ new float[]{Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE});
+ DEFAULTS.put(Sensor.TYPE_ROTATION_VECTOR,
+ new float[]{Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE,
+ Float.MAX_VALUE});
+ DEFAULTS.put(Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED,
+ new float[]{Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE,
+ Float.MAX_VALUE, Float.MAX_VALUE});
+ DEFAULTS.put(Sensor.TYPE_GAME_ROTATION_VECTOR,
+ new float[]{Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE,
+ Float.MAX_VALUE});
+ DEFAULTS.put(Sensor.TYPE_GYROSCOPE_UNCALIBRATED,
+ new float[]{Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE,
+ Float.MAX_VALUE, Float.MAX_VALUE});
+ DEFAULTS.put(Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR,
+ new float[]{Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE,
+ Float.MAX_VALUE});
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/StandardDeviationVerificationTest.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/StandardDeviationVerificationTest.java
new file mode 100644
index 0000000..0e5fb90
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/StandardDeviationVerificationTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.cts.helpers.sensorverification;
+
+import android.hardware.cts.helpers.SensorStats;
+import android.hardware.cts.helpers.TestSensorEvent;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for {@link StandardDeviationVerification}.
+ */
+public class StandardDeviationVerificationTest extends TestCase {
+
+ /**
+ * Test {@link StandardDeviationVerification#verify(SensorStats)}.
+ */
+ public void testVerify() {
+ // Stddev should be {sqrt(2.5), sqrt(2.5), sqrt(2.5)}
+ float[][] values = {
+ {0, 1, 0},
+ {1, 2, 2},
+ {2, 3, 4},
+ {3, 4, 6},
+ {4, 5, 8},
+ };
+ float[] standardDeviations = {
+ (float) Math.sqrt(2.5), (float) Math.sqrt(2.5), (float) Math.sqrt(10.0)
+ };
+
+ float[] threshold = {2, 2, 4};
+ runVerification(threshold, values, true, standardDeviations);
+
+ threshold = new float[]{1, 2, 4};
+ runVerification(threshold, values, false, standardDeviations);
+
+ threshold = new float[]{2, 1, 4};
+ runVerification(threshold, values, false, standardDeviations);
+
+ threshold = new float[]{2, 2, 3};
+ runVerification(threshold, values, false, standardDeviations);
+ }
+
+ private void runVerification(float[] threshold, float[][] values, boolean pass,
+ float[] standardDeviations) {
+ SensorStats stats = new SensorStats();
+ ISensorVerification verification = getVerification(threshold, values);
+ if (pass) {
+ verification.verify(stats);
+ } else {
+ try {
+ verification.verify(stats);
+ fail("Expected an AssertionError");
+ } catch (AssertionError e) {
+ // Expected;
+ }
+ }
+ assertEquals(pass, stats.getValue(StandardDeviationVerification.PASSED_KEY));
+ float[] actual = (float[]) stats.getValue(SensorStats.STANDARD_DEVIATION_KEY);
+ for (int i = 0; i < standardDeviations.length; i++) {
+ assertEquals(standardDeviations[i], actual[i], 0.1);
+ }
+ }
+
+ private ISensorVerification getVerification(float[] threshold, float[] ... values) {
+ ISensorVerification verification = new StandardDeviationVerification(threshold);
+ for (float[] value : values) {
+ verification.addSensorEvent(new TestSensorEvent(null, 0, 0, value));
+ }
+ return verification;
+ }
+}
diff --git a/tests/tests/media/AndroidManifest.xml b/tests/tests/media/AndroidManifest.xml
index 0c7b7b6..d53b2c6 100644
--- a/tests/tests/media/AndroidManifest.xml
+++ b/tests/tests/media/AndroidManifest.xml
@@ -58,6 +58,12 @@
<category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
</intent-filter>
</activity>
+ <service android:name="android.media.cts.RemoteVirtualDisplayService"
+ android:process=":remoteService" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ </intent-filter>
+ </service>
</application>
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/media/res/layout/composition_layout.xml b/tests/tests/media/res/layout/composition_layout.xml
new file mode 100644
index 0000000..6aa9ea8
--- /dev/null
+++ b/tests/tests/media/res/layout/composition_layout.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal" >
+ <FrameLayout
+ android:id="@+id/window0"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:visibility="visible"
+ android:layout_weight="0.33" />
+ <FrameLayout
+ android:id="@+id/window1"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:visibility="gone"
+ android:layout_weight="0.33" />
+ <FrameLayout
+ android:id="@+id/window2"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:visibility="gone"
+ android:layout_weight="0.33" />
+</LinearLayout>
diff --git a/tests/tests/media/res/raw/bug13652927.ogg b/tests/tests/media/res/raw/bug13652927.ogg
new file mode 100644
index 0000000..065d9e5
--- /dev/null
+++ b/tests/tests/media/res/raw/bug13652927.ogg
Binary files differ
diff --git a/tests/tests/media/res/raw/cp1251_7.mp3 b/tests/tests/media/res/raw/cp1251_7.mp3
new file mode 100644
index 0000000..d1c492b
--- /dev/null
+++ b/tests/tests/media/res/raw/cp1251_7.mp3
Binary files differ
diff --git a/tests/tests/media/res/raw/cp1251_8.mp3 b/tests/tests/media/res/raw/cp1251_8.mp3
new file mode 100644
index 0000000..17f7e31
--- /dev/null
+++ b/tests/tests/media/res/raw/cp1251_8.mp3
Binary files differ
diff --git a/tests/tests/media/res/raw/iso88591_2.mp3 b/tests/tests/media/res/raw/iso88591_2.mp3
new file mode 100644
index 0000000..bcfdaad
--- /dev/null
+++ b/tests/tests/media/res/raw/iso88591_2.mp3
Binary files differ
diff --git a/tests/tests/media/src/android/media/cts/CompositionTextureView.java b/tests/tests/media/src/android/media/cts/CompositionTextureView.java
new file mode 100644
index 0000000..5d84a11
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/CompositionTextureView.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.cts;
+
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+import android.content.Context;
+import android.graphics.SurfaceTexture;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Surface;
+import android.view.TextureView;
+
+public class CompositionTextureView extends TextureView
+ implements TextureView.SurfaceTextureListener {
+ private static final String TAG = "CompositionTextureView";
+ private static final boolean DBG = true;
+ private static final boolean DBG_VERBOSE = false;
+
+ private final Semaphore mInitWaitSemaphore = new Semaphore(0);
+ private Surface mSurface;
+
+ public CompositionTextureView(Context context) {
+ super(context);
+ if (DBG) {
+ Log.i(TAG, "CompositionTextureView");
+ }
+ }
+
+ public CompositionTextureView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ if (DBG) {
+ Log.i(TAG, "CompositionTextureView");
+ }
+ }
+
+ public CompositionTextureView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ if (DBG) {
+ Log.i(TAG, "CompositionTextureView");
+ }
+ }
+
+ public void startListening() {
+ if (DBG) {
+ Log.i(TAG, "startListening");
+ }
+ setSurfaceTextureListener(this);
+ }
+
+ @Override
+ public void onSurfaceTextureAvailable(SurfaceTexture arg0, int arg1, int arg2) {
+ if (DBG) {
+ Log.i(TAG, "onSurfaceTextureAvailable");
+ }
+ recreateSurface(arg0);
+ mInitWaitSemaphore.release();
+ }
+
+ @Override
+ public boolean onSurfaceTextureDestroyed(SurfaceTexture arg0) {
+ if (DBG) {
+ Log.i(TAG, "onSurfaceTextureDestroyed");
+ }
+ return true;
+ }
+
+ @Override
+ public void onSurfaceTextureSizeChanged(SurfaceTexture arg0, int arg1, int arg2) {
+ if (DBG) {
+ Log.i(TAG, "onSurfaceTextureSizeChanged");
+ }
+ recreateSurface(arg0);
+ mInitWaitSemaphore.release();
+ }
+
+ @Override
+ public void onSurfaceTextureUpdated(SurfaceTexture arg0) {
+ if (DBG_VERBOSE) {
+ Log.i(TAG, "onSurfaceTextureUpdated");
+ }
+ //ignore
+ }
+
+ public boolean waitForSurfaceReady(long timeoutMs) throws Exception {
+ if (isSurfaceTextureAvailable()) {
+ return true;
+ }
+ if (mInitWaitSemaphore.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) {
+ return true;
+ }
+ return isSurfaceTextureAvailable();
+ }
+
+ private boolean isSurfaceTextureAvailable() {
+ SurfaceTexture surfaceTexture = getSurfaceTexture();
+ if (mSurface == null && surfaceTexture != null) {
+ recreateSurface(surfaceTexture);
+ }
+ return (mSurface != null);
+ }
+
+ private synchronized void recreateSurface(SurfaceTexture surfaceTexture) {
+ if (mSurface != null) {
+ mSurface.release();
+ }
+ mSurface = new Surface(surfaceTexture);
+ }
+
+ public Surface getSurface() {
+ return mSurface;
+ }
+
+}
diff --git a/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayTest.java b/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayTest.java
index eb06eda..c622cfe 100644
--- a/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayTest.java
+++ b/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayTest.java
@@ -71,7 +71,7 @@
private static final int HEIGHT = 720;
private static final int DENSITY = DisplayMetrics.DENSITY_HIGH;
private static final int UI_TIMEOUT_MS = 2000;
- private static final int UI_RENDER_PAUSE_MS = 200;
+ private static final int UI_RENDER_PAUSE_MS = 400;
// Encoder parameters. We use the same width/height as the virtual display.
private static final String MIME_TYPE = "video/avc";
diff --git a/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTest.java b/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTest.java
index 79afc12..dc767c9 100644
--- a/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTest.java
+++ b/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTest.java
@@ -18,12 +18,17 @@
import android.app.Presentation;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
import android.graphics.SurfaceTexture;
import android.graphics.Typeface;
+import android.graphics.drawable.ColorDrawable;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.media.MediaCodec;
+import android.media.MediaCodec.BufferInfo;
import android.media.MediaCodecInfo;
import android.media.MediaCodecInfo.CodecCapabilities;
import android.media.MediaCodecInfo.CodecProfileLevel;
@@ -34,22 +39,35 @@
import android.opengl.Matrix;
import android.os.Bundle;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.Parcel;
import android.test.AndroidTestCase;
+import android.util.AttributeSet;
import android.util.Log;
+import android.util.Pair;
import android.view.Display;
import android.view.Surface;
+import android.view.TextureView;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
import android.view.WindowManager;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
import android.widget.TextView;
+import com.android.cts.media.R;
+
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.Semaphore;
-import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* Tests to check if MediaCodec encoding works with composition of multiple virtual displays
@@ -61,11 +79,38 @@
private static final String TAG = "EncodeVirtualDisplayWithCompositionTest";
private static final boolean DBG = false;
private static final String MIME_TYPE = "video/avc";
- private Handler mHandler;
- private Surface mSurface;
- private CodecInfo mCodecInfo;
+
+ private static final long DEFAULT_WAIT_TIMEOUT_MS = 5000;
+ private static final long DEFAULT_WAIT_TIMEOUT_US = 5000000;
+
+ private static final int COLOR_RED = makeColor(100, 0, 0);
+ private static final int COLOR_BLUE = makeColor(0, 100, 0);
+ private static final int COLOR_GREEN = makeColor(0, 0, 100);
+ private static final int COLOR_GREY = makeColor(100, 100, 100);
+
+ private static final int BITRATE_1080p = 20000000;
+ private static final int BITRATE_720p = 14000000;
+ private static final int BITRATE_800x480 = 14000000;
+ private static final int IFRAME_INTERVAL = 10;
+
+ private static final int MAX_NUM_WINDOWS = 3;
+
+ private static Handler sHandlerForRunOnMain = new Handler(Looper.getMainLooper());
+
+ private Surface mEncodingSurface;
+ private OutputSurface mDecodingSurface;
private volatile boolean mCodecConfigReceived = false;
private volatile boolean mCodecBufferReceived = false;
+ private EncodingHelper mEncodingHelper;
+ private MediaCodec mDecoder;
+ private final ByteBuffer mPixelBuf = ByteBuffer.allocateDirect(4);
+ private volatile boolean mIsQuitting = false;
+ private Throwable mTestException;
+ private VirtualDisplayPresentation mLocalPresentation;
+ private RemoteVirtualDisplayPresentation mRemotePresentation;
+ private ByteBuffer[] mDecoderInputBuffers;
+
+ /** event listener for test without verifying output */
private EncoderEventListener mEncoderEventListener = new EncoderEventListener() {
@Override
public void onCodecConfig(ByteBuffer data, MediaCodec.BufferInfo info) {
@@ -81,24 +126,280 @@
}
};
- @Override
- protected void setUp() {
- mHandler = new Handler(Looper.getMainLooper());
- mCodecInfo = getAvcSupportedFormatInfo();
+ /* TEST_COLORS static initialization; need ARGB for ColorDrawable */
+ private static int makeColor(int red, int green, int blue) {
+ return 0xff << 24 | (red & 0xff) << 16 | (green & 0xff) << 8 | (blue & 0xff);
}
- public void testSingleVirtualDisplay() throws Exception {
- doTestVirtualDisplays(1);
+ public void testVirtualDisplayRecycles() throws Exception {
+ doTestVirtualDisplayRecycles(3);
}
- public void testMultipleVirtualDisplays() throws Exception {
- doTestVirtualDisplays(3);
+ public void testRendering800x480Locally() throws Throwable {
+ Log.i(TAG, "testRendering800x480Locally");
+ Pair<Integer, Integer> maxRes = checkMaxConcurrentEncodingDecodingResolution();
+ if (maxRes.first >= 800 && maxRes.second >= 480) {
+ runTestRenderingInSeparateThread(800, 480, false, false);
+ } else {
+ Log.w(TAG, "This H/W does not support 800x480");
+ }
}
- void doTestVirtualDisplays(int numDisplays) throws Exception {
- final int NUM_CODEC_CREATION = 10;
- final int NUM_DISPLAY_CREATION = 20;
- final int NUM_RENDERING = 10;
+ public void testRenderingMaxResolutionLocally() throws Throwable {
+ Log.i(TAG, "testRenderingMaxResolutionLocally");
+ Pair<Integer, Integer> maxRes = checkMaxConcurrentEncodingDecodingResolution();
+ Log.w(TAG, "Trying resolution w:" + maxRes.first + " h:" + maxRes.second);
+ runTestRenderingInSeparateThread(maxRes.first, maxRes.second, false, false);
+ }
+
+ public void testRendering800x480Remotely() throws Throwable {
+ Log.i(TAG, "testRendering800x480Remotely");
+ Pair<Integer, Integer> maxRes = checkMaxConcurrentEncodingDecodingResolution();
+ if (maxRes.first >= 800 && maxRes.second >= 480) {
+ runTestRenderingInSeparateThread(800, 480, true, false);
+ } else {
+ Log.w(TAG, "This H/W does not support 800x480");
+ }
+ }
+
+ public void testRenderingMaxResolutionRemotely() throws Throwable {
+ Log.i(TAG, "testRenderingMaxResolutionRemotely");
+ Pair<Integer, Integer> maxRes = checkMaxConcurrentEncodingDecodingResolution();
+ Log.w(TAG, "Trying resolution w:" + maxRes.first + " h:" + maxRes.second);
+ runTestRenderingInSeparateThread(maxRes.first, maxRes.second, true, false);
+ }
+
+ public void testRendering800x480RemotelyWith3Windows() throws Throwable {
+ Log.i(TAG, "testRendering800x480RemotelyWith3Windows");
+ Pair<Integer, Integer> maxRes = checkMaxConcurrentEncodingDecodingResolution();
+ if (maxRes.first >= 800 && maxRes.second >= 480) {
+ runTestRenderingInSeparateThread(800, 480, true, true);
+ } else {
+ Log.w(TAG, "This H/W does not support 800x480");
+ }
+ }
+
+ public void testRendering800x480LocallyWith3Windows() throws Throwable {
+ Log.i(TAG, "testRendering800x480LocallyWith3Windows");
+ Pair<Integer, Integer> maxRes = checkMaxConcurrentEncodingDecodingResolution();
+ if (maxRes.first >= 800 && maxRes.second >= 480) {
+ runTestRenderingInSeparateThread(800, 480, false, true);
+ } else {
+ Log.w(TAG, "This H/W does not support 800x480");
+ }
+ }
+
+ /**
+ * Run rendering test in a separate thread. This is necessary as {@link OutputSurface} requires
+ * constructing it in a non-test thread.
+ * @param w
+ * @param h
+ * @throws Exception
+ */
+ private void runTestRenderingInSeparateThread(final int w, final int h,
+ final boolean runRemotely, final boolean multipleWindows) throws Throwable {
+ mTestException = null;
+ Thread renderingThread = new Thread(new Runnable() {
+ public void run() {
+ try {
+ doTestRenderingOutput(w, h, runRemotely, multipleWindows);
+ } catch (Throwable t) {
+ t.printStackTrace();
+ mTestException = t;
+ }
+ }
+ });
+ renderingThread.start();
+ renderingThread.join(20000);
+ assertTrue(!renderingThread.isAlive());
+ if (mTestException != null) {
+ throw mTestException;
+ }
+ }
+
+ private void doTestRenderingOutput(int w, int h, boolean runRemotely, boolean multipleWindows)
+ throws Throwable {
+ if (DBG) {
+ Log.i(TAG, "doTestRenderingOutput for w:" + w + " h:" + h);
+ }
+ try {
+ mIsQuitting = false;
+ mDecoder = MediaCodec.createDecoderByType(MIME_TYPE);
+ MediaFormat decoderFormat = MediaFormat.createVideoFormat(MIME_TYPE, w, h);
+ mDecodingSurface = new OutputSurface(w, h);
+ mDecoder.configure(decoderFormat, mDecodingSurface.getSurface(), null, 0);
+ mDecoder.start();
+ mDecoderInputBuffers = mDecoder.getInputBuffers();
+
+ mEncodingHelper = new EncodingHelper();
+ mEncodingSurface = mEncodingHelper.startEncoding(w, h,
+ new EncoderEventListener() {
+ @Override
+ public void onCodecConfig(ByteBuffer data, BufferInfo info) {
+ if (DBG) {
+ Log.i(TAG, "onCodecConfig l:" + info.size);
+ }
+ handleEncodedData(data, info);
+ }
+
+ @Override
+ public void onBufferReady(ByteBuffer data, BufferInfo info) {
+ if (DBG) {
+ Log.i(TAG, "onBufferReady l:" + info.size);
+ }
+ handleEncodedData(data, info);
+ }
+
+ @Override
+ public void onError(String errorMessage) {
+ fail(errorMessage);
+ }
+
+ private void handleEncodedData(ByteBuffer data, BufferInfo info) {
+ if (mIsQuitting) {
+ if (DBG) {
+ Log.i(TAG, "ignore data as test is quitting");
+ }
+ return;
+ }
+ int inputBufferIndex = mDecoder.dequeueInputBuffer(DEFAULT_WAIT_TIMEOUT_US);
+ if (inputBufferIndex < 0) {
+ if (DBG) {
+ Log.i(TAG, "dequeueInputBuffer returned:" + inputBufferIndex);
+ }
+ return;
+ }
+ assertTrue(inputBufferIndex >= 0);
+ ByteBuffer inputBuffer = mDecoderInputBuffers[inputBufferIndex];
+ inputBuffer.clear();
+ inputBuffer.put(data);
+ mDecoder.queueInputBuffer(inputBufferIndex, 0, info.size,
+ info.presentationTimeUs, info.flags);
+ }
+ });
+ GlCompositor compositor = new GlCompositor();
+ if (DBG) {
+ Log.i(TAG, "start composition");
+ }
+ compositor.startComposition(mEncodingSurface, w, h, multipleWindows ? 3 : 1);
+
+ if (DBG) {
+ Log.i(TAG, "create display");
+ }
+
+ Renderer renderer = null;
+ if (runRemotely) {
+ mRemotePresentation = new RemoteVirtualDisplayPresentation(getContext(),
+ compositor.getWindowSurface(multipleWindows? 1 : 0), w, h);
+ mRemotePresentation.connect();
+ mRemotePresentation.start();
+ renderer = mRemotePresentation;
+ } else {
+ mLocalPresentation = new VirtualDisplayPresentation(getContext(),
+ compositor.getWindowSurface(multipleWindows? 1 : 0), w, h);
+ mLocalPresentation.createVirtualDisplay();
+ mLocalPresentation.createPresentation();
+ renderer = mLocalPresentation;
+ }
+
+ if (DBG) {
+ Log.i(TAG, "start rendering and check");
+ }
+ renderColorAndCheckResult(renderer, w, h, COLOR_RED);
+ renderColorAndCheckResult(renderer, w, h, COLOR_BLUE);
+ renderColorAndCheckResult(renderer, w, h, COLOR_GREEN);
+ renderColorAndCheckResult(renderer, w, h, COLOR_GREY);
+
+ mIsQuitting = true;
+ if (runRemotely) {
+ mRemotePresentation.disconnect();
+ } else {
+ mLocalPresentation.dismissPresentation();
+ mLocalPresentation.destroyVirtualDisplay();
+ }
+
+ compositor.stopComposition();
+ } finally {
+ if (mEncodingHelper != null) {
+ mEncodingHelper.stopEncoding();
+ mEncodingHelper = null;
+ }
+ if (mDecoder != null) {
+ mDecoder.stop();
+ mDecoder.release();
+ mDecoder = null;
+ }
+ if (mDecodingSurface != null) {
+ mDecodingSurface.release();
+ mDecodingSurface = null;
+ }
+ }
+ }
+
+ private static final int NUM_MAX_RETRY = 120;
+ private static final int IMAGE_WAIT_TIMEOUT_MS = 1000;
+
+ private void renderColorAndCheckResult(Renderer renderer, int w, int h,
+ int color) throws Exception {
+ BufferInfo info = new BufferInfo();
+ for (int i = 0; i < NUM_MAX_RETRY; i++) {
+ renderer.doRendering(color);
+ int bufferIndex = mDecoder.dequeueOutputBuffer(info, DEFAULT_WAIT_TIMEOUT_US);
+ if (DBG) {
+ Log.i(TAG, "decoder dequeueOutputBuffer returned " + bufferIndex);
+ }
+ if (bufferIndex < 0) {
+ continue;
+ }
+ mDecoder.releaseOutputBuffer(bufferIndex, true);
+ if (mDecodingSurface.checkForNewImage(IMAGE_WAIT_TIMEOUT_MS)) {
+ mDecodingSurface.drawImage();
+ if (checkSurfaceFrameColor(w, h, color)) {
+ Log.i(TAG, "color " + Integer.toHexString(color) + " matched");
+ return;
+ }
+ } else if(DBG) {
+ Log.i(TAG, "no rendering yet");
+ }
+ }
+ fail("Color did not match");
+ }
+
+ private boolean checkSurfaceFrameColor(int w, int h, int color) {
+ // Read a pixel from the center of the surface. Might want to read from multiple points
+ // and average them together.
+ int x = w / 2;
+ int y = h / 2;
+ GLES20.glReadPixels(x, y, 1, 1, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, mPixelBuf);
+ int r = mPixelBuf.get(0) & 0xff;
+ int g = mPixelBuf.get(1) & 0xff;
+ int b = mPixelBuf.get(2) & 0xff;
+
+ int redExpected = (color >> 16) & 0xff;
+ int greenExpected = (color >> 8) & 0xff;
+ int blueExpected = color & 0xff;
+ if (approxEquals(redExpected, r) && approxEquals(greenExpected, g)
+ && approxEquals(blueExpected, b)) {
+ return true;
+ }
+ Log.i(TAG, "expected 0x" + Integer.toHexString(color) + " got 0x"
+ + Integer.toHexString(makeColor(r, g, b)));
+ return false;
+ }
+
+ /**
+ * Determines if two color values are approximately equal.
+ */
+ private static boolean approxEquals(int expected, int actual) {
+ final int MAX_DELTA = 4;
+ return Math.abs(expected - actual) <= MAX_DELTA;
+ }
+
+ private static final int NUM_CODEC_CREATION = 5;
+ private static final int NUM_DISPLAY_CREATION = 10;
+ private static final int NUM_RENDERING = 10;
+ private void doTestVirtualDisplayRecycles(int numDisplays) throws Exception {
+ CodecInfo codecInfo = getAvcSupportedFormatInfo();
VirtualDisplayPresentation[] virtualDisplays = new VirtualDisplayPresentation[numDisplays];
for (int i = 0; i < NUM_CODEC_CREATION; i++) {
mCodecConfigReceived = false;
@@ -107,12 +408,13 @@
Log.i(TAG, "start encoding");
}
EncodingHelper encodingHelper = new EncodingHelper();
- mSurface = encodingHelper.startEncoding(mCodecInfo, mEncoderEventListener);
+ mEncodingSurface = encodingHelper.startEncoding(codecInfo.mMaxW, codecInfo.mMaxH,
+ mEncoderEventListener);
GlCompositor compositor = new GlCompositor();
if (DBG) {
Log.i(TAG, "start composition");
}
- compositor.startComposition(mSurface, mCodecInfo.mMaxW, mCodecInfo.mMaxH,
+ compositor.startComposition(mEncodingSurface, codecInfo.mMaxW, codecInfo.mMaxH,
numDisplays);
for (int j = 0; j < NUM_DISPLAY_CREATION; j++) {
if (DBG) {
@@ -122,8 +424,7 @@
virtualDisplays[k] =
new VirtualDisplayPresentation(getContext(),
compositor.getWindowSurface(k),
- mCodecInfo.mMaxW/numDisplays, mCodecInfo.mMaxH,
- VirtualDisplayPresentation.RENDERING_VIEW_HIERARCHY);
+ codecInfo.mMaxW/numDisplays, codecInfo.mMaxH);
virtualDisplays[k].createVirtualDisplay();
virtualDisplays[k].createPresentation();
}
@@ -132,7 +433,7 @@
}
for (int k = 0; k < NUM_RENDERING; k++) {
for (int l = 0; l < numDisplays; l++) {
- virtualDisplays[l].doRendering();
+ virtualDisplays[l].doRendering(COLOR_RED);
}
// do not care how many frames are actually rendered.
Thread.sleep(1);
@@ -166,14 +467,16 @@
private MediaCodec mEncoder;
private volatile boolean mStopEncoding = false;
private EncoderEventListener mEventListener;
- private CodecInfo mCodecInfo;
+ private int mW;
+ private int mH;
private Thread mEncodingThread;
- private Surface mSurface;
- private static final int IFRAME_INTERVAL = 10;
+ private Surface mEncodingSurface;
private Semaphore mInitCompleted = new Semaphore(0);
- Surface startEncoding(CodecInfo codecInfo, EncoderEventListener eventListener) {
- mCodecInfo = codecInfo;
+ Surface startEncoding(int w, int h, EncoderEventListener eventListener) {
+ mStopEncoding = false;
+ mW = w;
+ mH = h;
mEventListener = eventListener;
mEncodingThread = new Thread(new Runnable() {
@Override
@@ -181,6 +484,7 @@
try {
doEncoding();
} catch (Exception e) {
+ e.printStackTrace();
mEventListener.onError(e.toString());
}
}
@@ -197,7 +501,7 @@
} catch (InterruptedException e) {
fail("should not happen");
}
- return mSurface;
+ return mEncodingSurface;
}
void stopEncoding() {
@@ -213,25 +517,36 @@
private void doEncoding() throws Exception {
final int TIMEOUT_USEC_NORMAL = 1000000;
- MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, mCodecInfo.mMaxW,
- mCodecInfo.mMaxH);
+ MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, mW, mH);
format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
- format.setInteger(MediaFormat.KEY_BIT_RATE, mCodecInfo.mBitRate);
- format.setInteger(MediaFormat.KEY_FRAME_RATE, mCodecInfo.mFps);
+ int bitRate = 10000000;
+ if (mW == 1920 && mH == 1080) {
+ bitRate = BITRATE_1080p;
+ } else if (mW == 1280 && mH == 720) {
+ bitRate = BITRATE_720p;
+ } else if (mW == 800 && mH == 480) {
+ bitRate = BITRATE_800x480;
+ }
+ format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
+ format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
- // Create a MediaCodec for the desired codec, then configure it as an encoder with
- // our desired properties. Request a Surface to use for input.
- mEncoder = MediaCodec.createByCodecName(mCodecInfo.mCodecName);
+ mEncoder = MediaCodec.createEncoderByType(MIME_TYPE);
mEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
- mSurface = mEncoder.createInputSurface();
+ mEncodingSurface = mEncoder.createInputSurface();
mEncoder.start();
mInitCompleted.release();
+ if (DBG) {
+ Log.i(TAG, "starting encoder");
+ }
try {
ByteBuffer[] encoderOutputBuffers = mEncoder.getOutputBuffers();
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
while (!mStopEncoding) {
int index = mEncoder.dequeueOutputBuffer(info, TIMEOUT_USEC_NORMAL);
+ if (DBG) {
+ Log.i(TAG, "dequeOutputBuffer returned " + index);
+ }
if (index >= 0) {
if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
Log.i(TAG, "codec config data");
@@ -241,6 +556,7 @@
mEventListener.onCodecConfig(encodedData, info);
mEncoder.releaseOutputBuffer(index, false);
} else if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+ Log.i(TAG, "EOS, stopping encoding");
break;
} else {
ByteBuffer encodedData = encoderOutputBuffers[index];
@@ -251,10 +567,15 @@
}
}
}
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw e;
} finally {
mEncoder.stop();
mEncoder.release();
mEncoder = null;
+ mEncodingSurface.release();
+ mEncodingSurface = null;
}
}
}
@@ -266,10 +587,8 @@
private Surface mSurface;
private int mWidth;
private int mHeight;
- private int mNumWindows;
- private ArrayList<GlWindow> mWindows = new ArrayList<GlWindow>();
- private HashMap<SurfaceTexture, GlWindow> mSurfaceTextureToWindowMap =
- new HashMap<SurfaceTexture, GlWindow>();
+ private volatile int mNumWindows;
+ private GlWindow mTopWindow;
private Thread mCompositionThread;
private Semaphore mStartCompletionSemaphore;
private Semaphore mRecreationCompletionSemaphore;
@@ -282,6 +601,7 @@
private int mGlaPositionHandle;
private int mGlaTextureHandle;
private float[] mMVPMatrix = new float[16];
+ private TopWindowVirtualDisplayPresentation mTopPresentation;
private static final String VERTEX_SHADER =
"uniform mat4 uMVPMatrix;\n" +
@@ -303,7 +623,7 @@
" gl_FragColor = texture2D(sTexture, vTextureCoord);\n" +
"}\n";
- void startComposition(Surface surface, int w, int h, int numWindows) {
+ void startComposition(Surface surface, int w, int h, int numWindows) throws Exception {
mSurface = surface;
mWidth = w;
mHeight = h;
@@ -329,14 +649,18 @@
}
Surface getWindowSurface(int windowIndex) {
- return mWindows.get(windowIndex).getSurface();
+ return mTopPresentation.getSurface(windowIndex);
}
void recreateWindows() throws Exception {
mRecreationCompletionSemaphore = new Semaphore(0);
Message msg = mHandler.obtainMessage(CompositionHandler.DO_RECREATE_WINDOWS);
mHandler.sendMessage(msg);
- mRecreationCompletionSemaphore.acquire();
+ if(!mRecreationCompletionSemaphore.tryAcquire(DEFAULT_WAIT_TIMEOUT_MS,
+ TimeUnit.MILLISECONDS)) {
+ fail("recreation timeout");
+ }
+ mTopPresentation.waitForSurfaceReady(DEFAULT_WAIT_TIMEOUT_MS);
}
@Override
@@ -344,16 +668,20 @@
if (DBG) {
Log.i(TAG, "onFrameAvailable " + surface);
}
- GlWindow w = mSurfaceTextureToWindowMap.get(surface);
+ GlWindow w = mTopWindow;
if (w != null) {
w.markTextureUpdated();
requestUpdate();
} else {
- Log.w(TAG, "cannot map Surface " + surface + " to window");
+ Log.w(TAG, "top window gone");
}
}
private void requestUpdate() {
+ Thread compositionThread = mCompositionThread;
+ if (compositionThread == null || !compositionThread.isAlive()) {
+ return;
+ }
Message msg = mHandler.obtainMessage(CompositionHandler.DO_RENDERING);
mHandler.sendMessage(msg);
}
@@ -442,25 +770,27 @@
Matrix.orthoM(projMatrix, 0, -wMid, wMid, -hMid, hMid, 1, 10);
Matrix.multiplyMM(mMVPMatrix, 0, projMatrix, 0, vMatrix, 0);
createWindows();
+
}
private void createWindows() throws GlException {
- // windows placed horizontally
- int windowWidth = mWidth / mNumWindows;
- for (int i = 0; i < mNumWindows; i++) {
- GlWindow window = new GlWindow(this, i * windowWidth, 0, windowWidth, mHeight);
- window.init();
- mSurfaceTextureToWindowMap.put(window.getSurfaceTexture(), window);
- mWindows.add(window);
- }
+ mTopWindow = new GlWindow(this, 0, 0, mWidth, mHeight);
+ mTopWindow.init();
+ mTopPresentation = new TopWindowVirtualDisplayPresentation(mContext,
+ mTopWindow.getSurface(), mWidth, mHeight, mNumWindows);
+ mTopPresentation.createVirtualDisplay();
+ mTopPresentation.createPresentation();
+ ((TopWindowPresentation) mTopPresentation.getPresentation()).populateWindows();
}
private void cleanupGl() {
- for (GlWindow w: mWindows) {
- w.cleanup();
+ if (mTopPresentation != null) {
+ mTopPresentation.dismissPresentation();
+ mTopPresentation.destroyVirtualDisplay();
}
- mWindows.clear();
- mSurfaceTextureToWindowMap.clear();
+ if (mTopWindow != null) {
+ mTopWindow.cleanup();
+ }
if (mEglHelper != null) {
mEglHelper.release();
}
@@ -470,52 +800,48 @@
if (DBG) {
Log.i(TAG, "doGlRendering");
}
- for (GlWindow w: mWindows) {
- w.updateTexImageIfNecessary();
- }
+ mTopWindow.updateTexImageIfNecessary();
GLES20.glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glUseProgram(mGlProgramId);
- for (GlWindow w: mWindows) {
- GLES20.glUniformMatrix4fv(mGluMVPMatrixHandle, 1, false, mMVPMatrix, 0);
- w.onDraw(mGluSTMatrixHandle, mGlaPositionHandle, mGlaTextureHandle);
- checkGlError("window draw");
- }
+ GLES20.glUniformMatrix4fv(mGluMVPMatrixHandle, 1, false, mMVPMatrix, 0);
+ mTopWindow.onDraw(mGluSTMatrixHandle, mGlaPositionHandle, mGlaTextureHandle);
+ checkGlError("window draw");
mEglHelper.swapBuffers();
}
+
private void doRecreateWindows() throws GlException {
- for (GlWindow w: mWindows) {
- w.cleanup();
- }
- mWindows.clear();
- mSurfaceTextureToWindowMap.clear();
+ mTopPresentation.dismissPresentation();
+ mTopPresentation.destroyVirtualDisplay();
+ mTopWindow.cleanup();
createWindows();
mRecreationCompletionSemaphore.release();
}
- private void waitForStartCompletion() {
- try {
- mStartCompletionSemaphore.acquire();
- } catch (InterruptedException e) {
- //ignore
+ private void waitForStartCompletion() throws Exception {
+ if (!mStartCompletionSemaphore.tryAcquire(DEFAULT_WAIT_TIMEOUT_MS,
+ TimeUnit.MILLISECONDS)) {
+ fail("start timeout");
}
mStartCompletionSemaphore = null;
+ mTopPresentation.waitForSurfaceReady(DEFAULT_WAIT_TIMEOUT_MS);
}
private class CompositionRunnable implements Runnable {
@Override
public void run() {
try {
- initGl();
- Looper.prepare();
mLooper = Looper.myLooper();
+ Looper.prepare();
mHandler = new CompositionHandler();
+ initGl();
// init done
mStartCompletionSemaphore.release();
Looper.loop();
} catch (GlException e) {
- // ignore and clean-up
+ e.printStackTrace();
+ fail("got gl exception");
} finally {
cleanupGl();
mHandler = null;
@@ -560,7 +886,7 @@
private volatile Surface mSurface;
private FloatBuffer mVerticesData;
private float[] mSTMatrix = new float[16];
- private AtomicBoolean mTextureUpdated = new AtomicBoolean(false);
+ private AtomicInteger mNumTextureUpdated = new AtomicInteger(0);
private GlCompositor mCompositor;
/**
@@ -620,7 +946,7 @@
}
public void cleanup() {
- mTextureUpdated.set(false);
+ mNumTextureUpdated.set(0);
if (mTextureId != 0) {
int[] textures = new int[] {
mTextureId
@@ -628,30 +954,38 @@
GLES20.glDeleteTextures(1, textures, 0);
}
GLES20.glFinish();
- mSurface.release();
- mSurface = null;
- mSurfaceTexture.release();
- mSurfaceTexture = null;
+ if (mSurface != null) {
+ mSurface.release();
+ mSurface = null;
+ }
+ if (mSurfaceTexture != null) {
+ mSurfaceTexture.release();
+ mSurfaceTexture = null;
+ }
}
/**
* make texture as updated so that it can be updated in the next rendering.
*/
public void markTextureUpdated() {
- mTextureUpdated.set(true);
+ mNumTextureUpdated.incrementAndGet();
}
/**
* update texture for rendering if it is updated.
*/
public void updateTexImageIfNecessary() {
- if (mTextureUpdated.getAndSet(false)) {
+ int numTextureUpdated = mNumTextureUpdated.getAndDecrement();
+ if (numTextureUpdated > 0) {
if (DBG) {
Log.i(TAG, "updateTexImageIfNecessary " + this);
}
mSurfaceTexture.updateTexImage();
mSurfaceTexture.getTransformMatrix(mSTMatrix);
}
+ if (numTextureUpdated < 0) {
+ fail("should not happen");
+ }
}
/**
@@ -701,26 +1035,24 @@
}
}
- private class VirtualDisplayPresentation {
- public static final int RENDERING_OPENGL = 0;
- public static final int RENDERING_VIEW_HIERARCHY = 1;
+ private interface Renderer {
+ void doRendering(final int color) throws Exception;
+ }
- private Context mContext;
- private Surface mSurface;
- private int mWidth;
- private int mHeight;
- private int mRenderingType;
+ private static class VirtualDisplayPresentation implements Renderer {
+ protected final Context mContext;
+ protected final Surface mSurface;
+ protected final int mWidth;
+ protected final int mHeight;
+ protected VirtualDisplay mVirtualDisplay;
+ protected TestPresentationBase mPresentation;
private final DisplayManager mDisplayManager;
- private VirtualDisplay mVirtualDisplay;
- private TestPresentation mPresentation;
- VirtualDisplayPresentation(Context context, Surface surface, int w, int h,
- int renderingType) {
+ VirtualDisplayPresentation(Context context, Surface surface, int w, int h) {
mContext = context;
mSurface = surface;
mWidth = w;
mHeight = h;
- mRenderingType = renderingType;
mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
}
@@ -747,13 +1079,20 @@
runOnMainSync(new Runnable() {
@Override
public void run() {
- mPresentation = new TestPresentation(getContext(),
- mVirtualDisplay.getDisplay());
+ mPresentation = doCreatePresentation();
mPresentation.show();
}
});
}
+ protected TestPresentationBase doCreatePresentation() {
+ return new TestPresentation(mContext, mVirtualDisplay.getDisplay());
+ }
+
+ TestPresentationBase getPresentation() {
+ return mPresentation;
+ }
+
void dismissPresentation() {
runOnMainSync(new Runnable() {
@Override
@@ -763,46 +1102,192 @@
});
}
- void doRendering() {
+ @Override
+ public void doRendering(final int color) throws Exception {
runOnMainSync(new Runnable() {
@Override
public void run() {
- mPresentation.doRendering();
+ mPresentation.doRendering(color);
+ }
+ });
+ }
+ }
+
+ private static class TestPresentationBase extends Presentation {
+
+ public TestPresentationBase(Context outerContext, Display display) {
+ super(outerContext, display);
+ getWindow().setType(WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
+ }
+
+ public void doRendering(int color) {
+ // to be implemented by child
+ }
+ }
+
+ private static class TestPresentation extends TestPresentationBase {
+ private ImageView mImageView;
+
+ public TestPresentation(Context outerContext, Display display) {
+ super(outerContext, display);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mImageView = new ImageView(getContext());
+ mImageView.setImageDrawable(new ColorDrawable(COLOR_RED));
+ mImageView.setLayoutParams(new LayoutParams(
+ LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+ setContentView(mImageView);
+ }
+
+ public void doRendering(int color) {
+ mImageView.setImageDrawable(new ColorDrawable(color));
+ }
+ }
+
+ private static class TopWindowPresentation extends TestPresentationBase {
+ private FrameLayout[] mWindowsLayout = new FrameLayout[MAX_NUM_WINDOWS];
+ private CompositionTextureView[] mWindows = new CompositionTextureView[MAX_NUM_WINDOWS];
+ private final int mNumWindows;
+ private final Semaphore mWindowWaitSemaphore = new Semaphore(0);
+
+ public TopWindowPresentation(int numWindows, Context outerContext, Display display) {
+ super(outerContext, display);
+ mNumWindows = numWindows;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (DBG) {
+ Log.i(TAG, "TopWindowPresentation onCreate, numWindows " + mNumWindows);
+ }
+ setContentView(R.layout.composition_layout);
+ mWindowsLayout[0] = (FrameLayout) findViewById(R.id.window0);
+ mWindowsLayout[1] = (FrameLayout) findViewById(R.id.window1);
+ mWindowsLayout[2] = (FrameLayout) findViewById(R.id.window2);
+ }
+
+ public void populateWindows() {
+ runOnMain(new Runnable() {
+ public void run() {
+ for (int i = 0; i < mNumWindows; i++) {
+ mWindows[i] = new CompositionTextureView(getContext());
+ mWindows[i].setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+ mWindowsLayout[i].setVisibility(View.VISIBLE);
+ mWindowsLayout[i].addView(mWindows[i]);
+ mWindows[i].startListening();
+ }
+ mWindowWaitSemaphore.release();
}
});
}
- private class TestPresentation extends Presentation {
- private TextView mTextView;
- private int mRenderingCount = 0;
-
- public TestPresentation(Context outerContext, Display display) {
- super(outerContext, display);
- getWindow().setType(WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION);
- }
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (VirtualDisplayPresentation.this.mRenderingType == RENDERING_OPENGL) {
- //TODO add init for opengl renderer
- } else {
- mTextView = new TextView(getContext());
- mTextView.setTextSize(14);
- mTextView.setTypeface(Typeface.DEFAULT_BOLD);
- mTextView.setText(Integer.toString(mRenderingCount));
- setContentView(mTextView);
+ public void waitForSurfaceReady(long timeoutMs) throws Exception {
+ mWindowWaitSemaphore.tryAcquire(DEFAULT_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ for (int i = 0; i < mNumWindows; i++) {
+ if(!mWindows[i].waitForSurfaceReady(timeoutMs)) {
+ fail("surface wait timeout");
}
}
+ }
- public void doRendering() {
- if (VirtualDisplayPresentation.this.mRenderingType == RENDERING_OPENGL) {
- //TODO add opengl rendering
- } else {
- mRenderingCount++;
- mTextView.setText(Integer.toString(mRenderingCount));
- }
+ public Surface getSurface(int windowIndex) {
+ Surface surface = mWindows[windowIndex].getSurface();
+ assertNotNull(surface);
+ return surface;
+ }
+ }
+
+ private static class TopWindowVirtualDisplayPresentation extends VirtualDisplayPresentation {
+ private final int mNumWindows;
+
+ TopWindowVirtualDisplayPresentation(Context context, Surface surface, int w, int h,
+ int numWindows) {
+ super(context, surface, w, h);
+ assertNotNull(surface);
+ mNumWindows = numWindows;
+ }
+
+ void waitForSurfaceReady(long timeoutMs) throws Exception {
+ ((TopWindowPresentation) mPresentation).waitForSurfaceReady(timeoutMs);
+ }
+
+ Surface getSurface(int windowIndex) {
+ return ((TopWindowPresentation) mPresentation).getSurface(windowIndex);
+ }
+
+ protected TestPresentationBase doCreatePresentation() {
+ return new TopWindowPresentation(mNumWindows, mContext, mVirtualDisplay.getDisplay());
+ }
+ }
+
+ private static class RemoteVirtualDisplayPresentation implements Renderer {
+ /** argument: Surface, int w, int h, return none */
+ private static final int BINDER_CMD_START = IBinder.FIRST_CALL_TRANSACTION;
+ /** argument: int color, return none */
+ private static final int BINDER_CMD_RENDER = IBinder.FIRST_CALL_TRANSACTION + 1;
+
+ private final Context mContext;
+ private final Surface mSurface;
+ private final int mWidth;
+ private final int mHeight;
+
+ private IBinder mService;
+ private final Semaphore mConnectionWait = new Semaphore(0);
+ private final ServiceConnection mConnection = new ServiceConnection() {
+
+ public void onServiceConnected(ComponentName arg0, IBinder arg1) {
+ mService = arg1;
+ mConnectionWait.release();
}
+
+ public void onServiceDisconnected(ComponentName arg0) {
+ //ignore
+ }
+
+ };
+
+ RemoteVirtualDisplayPresentation(Context context, Surface surface, int w, int h) {
+ mContext = context;
+ mSurface = surface;
+ mWidth = w;
+ mHeight = h;
+ }
+
+ void connect() throws Exception {
+ Intent intent = new Intent();
+ intent.setClassName("com.android.cts.media",
+ "android.media.cts.RemoteVirtualDisplayService");
+ mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+ if (!mConnectionWait.tryAcquire(DEFAULT_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ fail("cannot bind to service");
+ }
+ }
+
+ void disconnect() {
+ mContext.unbindService(mConnection);
+ }
+
+ void start() throws Exception {
+ Parcel parcel = Parcel.obtain();
+ mSurface.writeToParcel(parcel, 0);
+ parcel.writeInt(mWidth);
+ parcel.writeInt(mHeight);
+ mService.transact(BINDER_CMD_START, parcel, null, 0);
+ }
+
+ @Override
+ public void doRendering(int color) throws Exception {
+ Parcel parcel = Parcel.obtain();
+ parcel.writeInt(color);
+ mService.transact(BINDER_CMD_RENDER, parcel, null, 0);
}
}
@@ -813,6 +1298,7 @@
public int mBitRate;
public String mCodecName;
};
+
/**
* Returns the first codec capable of encoding the specified MIME type, or null if no
* match was found.
@@ -911,9 +1397,93 @@
return info;
}
- public void runOnMainSync(Runnable runner) {
+ /**
+ * Check maximum concurrent encoding / decoding resolution allowed.
+ * Some H/Ws cannot support maximum resolution reported in encoder if decoder is running
+ * at the same time.
+ * Check is done for 4 different levels: 1080p, 720p, 800x480 and max of encoder if is is
+ * smaller than 800x480.
+ */
+ private Pair<Integer, Integer> checkMaxConcurrentEncodingDecodingResolution() {
+ CodecInfo codecInfo = getAvcSupportedFormatInfo();
+ int maxW = codecInfo.mMaxW;
+ int maxH = codecInfo.mMaxH;
+ if (maxW >= 1920 && maxH >= 1080) {
+ if (isConcurrentEncodingDecodingSupported(1920, 1080, BITRATE_1080p)) {
+ return new Pair<Integer, Integer>(1920, 1080);
+ }
+ }
+ if (maxW >= 1280 && maxH >= 720) {
+ if (isConcurrentEncodingDecodingSupported(1280, 720, BITRATE_720p)) {
+ return new Pair<Integer, Integer>(1280, 720);
+ }
+ }
+ if (maxW >= 800 && maxH >= 480) {
+ if (isConcurrentEncodingDecodingSupported(800, 480, BITRATE_800x480)) {
+ return new Pair<Integer, Integer>(800, 480);
+ }
+ }
+ if (!isConcurrentEncodingDecodingSupported(codecInfo.mMaxW, codecInfo.mMaxH,
+ codecInfo.mBitRate)) {
+ fail("should work with advertised resolution");
+ }
+ return new Pair<Integer, Integer>(maxW, maxH);
+ }
+
+ private boolean isConcurrentEncodingDecodingSupported(int w, int h, int bitRate) {
+ MediaCodec decoder = null;
+ OutputSurface decodingSurface = null;
+ MediaCodec encoder = null;
+ Surface encodingSurface = null;
+ try {
+ decoder = MediaCodec.createDecoderByType(MIME_TYPE);
+ MediaFormat decoderFormat = MediaFormat.createVideoFormat(MIME_TYPE, w, h);
+ decodingSurface = new OutputSurface(w, h);
+ decodingSurface.makeCurrent();
+ decoder.configure(decoderFormat, decodingSurface.getSurface(), null, 0);
+ decoder.start();
+
+ MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, w, h);
+ format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+ MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
+ format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
+ format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
+ format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
+ encoder = MediaCodec.createEncoderByType(MIME_TYPE);;
+ encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+ encodingSurface = encoder.createInputSurface();
+ encoder.start();
+
+ encoder.stop();
+ decoder.stop();
+ } catch (Exception e) {
+ e.printStackTrace();
+ Log.i(TAG, "This H/W does not support w:" + w + " h:" + h);
+ return false;
+ } finally {
+ if (encodingSurface != null) {
+ encodingSurface.release();
+ }
+ if (encoder != null) {
+ encoder.release();
+ }
+ if (decoder != null) {
+ decoder.release();
+ }
+ if (decodingSurface != null) {
+ decodingSurface.release();
+ }
+ }
+ return true;
+ }
+
+ private static void runOnMain(Runnable runner) {
+ sHandlerForRunOnMain.post(runner);
+ }
+
+ private static void runOnMainSync(Runnable runner) {
SyncRunnable sr = new SyncRunnable(runner);
- mHandler.post(sr);
+ sHandlerForRunOnMain.post(sr);
sr.waitForComplete();
}
@@ -939,6 +1509,7 @@
try {
wait();
} catch (InterruptedException e) {
+ //ignore
}
}
}
diff --git a/tests/tests/media/src/android/media/cts/MediaPlayerTest.java b/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
index 247209d..275a648 100644
--- a/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
@@ -22,6 +22,7 @@
import android.content.res.AssetFileDescriptor;
import android.media.AudioManager;
import android.media.MediaPlayer;
+import android.media.MediaPlayer.OnErrorListener;
import android.media.MediaRecorder;
import android.media.MediaMetadataRetriever;
import android.media.TimedText;
@@ -30,7 +31,9 @@
import android.media.cts.MediaPlayerTestBase.Monitor;
import android.net.Uri;
import android.os.Environment;
+import android.os.IBinder;
import android.os.PowerManager;
+import android.os.ServiceManager;
import android.os.SystemClock;
import android.util.Log;
@@ -79,6 +82,35 @@
mOutFile.delete();
}
}
+
+ // Bug 13652927
+ public void testVorbisCrash() throws Exception {
+ MediaPlayer mp = mMediaPlayer;
+ MediaPlayer mp2 = mMediaPlayer2;
+ AssetFileDescriptor afd2 = mResources.openRawResourceFd(R.raw.testmp3_2);
+ mp2.setDataSource(afd2.getFileDescriptor(), afd2.getStartOffset(), afd2.getLength());
+ afd2.close();
+ mp2.prepare();
+ mp2.setLooping(true);
+ mp2.start();
+
+ for (int i = 0; i < 20; i++) {
+ try {
+ AssetFileDescriptor afd = mResources.openRawResourceFd(R.raw.bug13652927);
+ mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
+ afd.close();
+ mp.prepare();
+ fail("shouldn't be here");
+ } catch (Exception e) {
+ // expected to fail
+ Log.i("@@@", "failed: " + e);
+ }
+ Thread.sleep(500);
+ assertTrue("media server died", mp2.isPlaying());
+ mp.reset();
+ }
+ }
+
public void testPlayNullSource() throws Exception {
try {
mMediaPlayer.setDataSource((String) null);
diff --git a/tests/tests/media/src/android/media/cts/MediaScannerTest.java b/tests/tests/media/src/android/media/cts/MediaScannerTest.java
index f873fbc..1d492bb 100644
--- a/tests/tests/media/src/android/media/cts/MediaScannerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaScannerTest.java
@@ -76,6 +76,7 @@
@Override
protected void tearDown() throws Exception {
+ cleanup();
super.tearDown();
}
@@ -84,10 +85,12 @@
mMediaFile.delete();
}
if (mFileDir != null) {
- new File(mFileDir + "/testmp3.mp3").delete();
- new File(mFileDir + "/testmp3_2.mp3").delete();
- new File(mFileDir + "/ctsmediascanplaylist1.pls").delete();
- new File(mFileDir + "/ctsmediascanplaylist2.m3u").delete();
+ String files[] = new File(mFileDir).list();
+ if (files != null) {
+ for (String f: files) {
+ new File(mFileDir + "/" + f).delete();
+ }
+ }
new File(mFileDir).delete();
}
@@ -389,7 +392,7 @@
new String[] {"光良", "童话", "光良", "02.童话", "鍏夎壇"} ),
new MediaScanEntry(R.raw.gb18030_6,
new String[] {"张韶涵", "潘朵拉", "張韶涵", "隐形的翅膀", "王雅君"} ),
- new MediaScanEntry(R.raw.gb18030_7,
+ new MediaScanEntry(R.raw.gb18030_7, // this is actually utf-8
new String[] {"五月天", "后青春期的诗", null, "突然好想你", null} ),
new MediaScanEntry(R.raw.gb18030_8,
new String[] {"周杰伦", "Jay", null, "反方向的钟", null} ),
@@ -403,12 +406,17 @@
new String[] {"Мельница", "Перевал", null, "Королевна", null} ),
new MediaScanEntry(R.raw.cp1251_3,
new String[] {"Тату (tATu)", "200 По Встречной [Limited edi", null, "Я Сошла С Ума", null} ),
+ // The following 3 use cp1251 encoding, expanded to 16 bits and stored as utf16
new MediaScanEntry(R.raw.cp1251_4,
new String[] {"Александр Розенбаум", "Философия любви", null, "Разговор в гостинице (Как жить без веры)", "А.Розенбаум"} ),
new MediaScanEntry(R.raw.cp1251_5,
new String[] {"Александр Розенбаум", "Философия любви", null, "Четвертиночка", "А.Розенбаум"} ),
new MediaScanEntry(R.raw.cp1251_6,
new String[] {"Александр Розенбаум", "Философия ремесла", null, "Ну, вот...", "А.Розенбаум"} ),
+ new MediaScanEntry(R.raw.cp1251_7,
+ new String[] {"Вопли Видоплясова", "Хвилі Амура", null, "Або або", null} ),
+ new MediaScanEntry(R.raw.cp1251_8,
+ new String[] {"Вопли Видоплясова", "Хвилі Амура", null, "Таємнi сфери", null} ),
new MediaScanEntry(R.raw.shiftjis1,
new String[] {"", "", null, "中島敦「山月記」(第1回)", null} ),
new MediaScanEntry(R.raw.shiftjis2,
@@ -427,6 +435,8 @@
new String[] {"音人", "SoundEffects", null, "間違い", null} ),
new MediaScanEntry(R.raw.iso88591_1,
new String[] {"Mozart", "Best of Mozart", null, "Overtüre (Die Hochzeit des Figaro)", null} ),
+ new MediaScanEntry(R.raw.iso88591_2, // actually UTF16, but only uses iso8859-1 chars
+ new String[] {"Björk", "Telegram", "Björk", "Possibly Maybe (Lucy Mix)", null} ),
new MediaScanEntry(R.raw.hebrew,
new String[] {"אריק סיני", "", null, "לי ולך", null } ),
new MediaScanEntry(R.raw.hebrew2,
@@ -436,7 +446,8 @@
public void testEncodingDetection() throws Exception {
for (int i = 0; i< encodingtestfiles.length; i++) {
MediaScanEntry entry = encodingtestfiles[i];
- String path = mFileDir + "/" + "encodingtest" + i + ".mp3";
+ String name = mContext.getResources().getResourceEntryName(entry.res);
+ String path = mFileDir + "/" + name + ".mp3";
writeFile(entry.res, path);
}
@@ -452,7 +463,8 @@
ContentResolver res = mContext.getContentResolver();
for (int i = 0; i< encodingtestfiles.length; i++) {
MediaScanEntry entry = encodingtestfiles[i];
- String path = mFileDir + "/" + "encodingtest" + i + ".mp3";
+ String name = mContext.getResources().getResourceEntryName(entry.res);
+ String path = mFileDir + "/" + name + ".mp3";
Cursor c = res.query(MediaStore.Audio.Media.getContentUri("external"), columns,
MediaStore.Audio.Media.DATA + "=?", new String[] {path}, null);
assertNotNull("null cursor", c);
diff --git a/tests/tests/media/src/android/media/cts/OutputSurface.java b/tests/tests/media/src/android/media/cts/OutputSurface.java
index fd36d80..fc8ad9c 100644
--- a/tests/tests/media/src/android/media/cts/OutputSurface.java
+++ b/tests/tests/media/src/android/media/cts/OutputSurface.java
@@ -250,6 +250,35 @@
}
/**
+ * Wait up to given timeout until new image become available.
+ * @param timeoutMs
+ * @return true if new image is available. false for no new image until timeout.
+ */
+ public boolean checkForNewImage(int timeoutMs) {
+ 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(timeoutMs);
+ if (!mFrameAvailable) {
+ return false;
+ }
+ } catch (InterruptedException ie) {
+ // shouldn't happen
+ throw new RuntimeException(ie);
+ }
+ }
+ mFrameAvailable = false;
+ }
+
+ // Latch the data.
+ mTextureRender.checkGlError("before updateTexImage");
+ mSurfaceTexture.updateTexImage();
+ return true;
+ }
+
+ /**
* Draws the data from SurfaceTexture onto the current EGL surface.
*/
public void drawImage() {
diff --git a/tests/tests/media/src/android/media/cts/RemoteVirtualDisplayService.java b/tests/tests/media/src/android/media/cts/RemoteVirtualDisplayService.java
new file mode 100644
index 0000000..662cd5b
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/RemoteVirtualDisplayService.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.cts;
+
+import android.app.Presentation;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.ColorDrawable;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Parcel;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.Display;
+import android.view.Surface;
+import android.view.ViewGroup.LayoutParams;
+import android.view.WindowManager;
+import android.widget.ImageView;
+
+public class RemoteVirtualDisplayService extends Service {
+ private static final String TAG = "RemoteVirtualDisplayService";
+
+ /** argument: Surface, int w, int h, return none */
+ private static final int BINDER_CMD_START = IBinder.FIRST_CALL_TRANSACTION;
+ /** argument: int color, return none */
+ private static final int BINDER_CMD_RENDER = IBinder.FIRST_CALL_TRANSACTION + 1;
+ private final Handler mHandlerForRunOnMain = new Handler(Looper.getMainLooper());;
+ private IBinder mBinder;
+ private VirtualDisplayPresentation mPresentation;
+
+ @Override
+ public void onCreate() {
+ Log.i(TAG, "onCreate");
+ mBinder = new Binder() {
+ @Override
+ protected boolean onTransact(int code, Parcel data, Parcel reply,
+ int flags) throws RemoteException {
+ switch(code) {
+ case BINDER_CMD_START: {
+ Surface surface = Surface.CREATOR.createFromParcel(data);
+ int w = data.readInt();
+ int h = data.readInt();
+ start(surface, w, h);
+ break;
+ }
+ case BINDER_CMD_RENDER: {
+ int color = data.readInt();
+ render(color);
+ break;
+ }
+ default:
+ Log.e(TAG, "unrecognized binder command " + code);
+ return false;
+ }
+ return true;
+ }
+ };
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ Log.i(TAG, "onBind");
+ return mBinder;
+ }
+
+ @Override
+ public void onDestroy() {
+ Log.i(TAG, "onDestroy");
+ if (mPresentation != null) {
+ mPresentation.dismissPresentation();
+ mPresentation.destroyVirtualDisplay();
+ mPresentation = null;
+ }
+ }
+
+ private void start(Surface surface, int w, int h) {
+ Log.i(TAG, "start");
+ mPresentation = new VirtualDisplayPresentation(this, surface, w, h);
+ mPresentation.createVirtualDisplay();
+ mPresentation.createPresentation();
+ }
+
+ private void render(int color) {
+ Log.i(TAG, "render");
+ mPresentation.doRendering(color);
+ }
+
+ private class VirtualDisplayPresentation {
+ private Context mContext;
+ private Surface mSurface;
+ private int mWidth;
+ private int mHeight;
+ private final DisplayManager mDisplayManager;
+ private VirtualDisplay mVirtualDisplay;
+ private TestPresentation mPresentation;
+
+ VirtualDisplayPresentation(Context context, Surface surface, int w, int h) {
+ mContext = context;
+ mSurface = surface;
+ mWidth = w;
+ mHeight = h;
+ mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
+ }
+
+ void createVirtualDisplay() {
+ runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ mVirtualDisplay = mDisplayManager.createVirtualDisplay(
+ TAG, mWidth, mHeight, 200, mSurface, 0);
+ }
+ });
+ }
+
+ void destroyVirtualDisplay() {
+ if (mVirtualDisplay != null) {
+ mVirtualDisplay.release();
+ }
+ }
+
+ void createPresentation() {
+ runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ mPresentation = new TestPresentation(RemoteVirtualDisplayService.this,
+ mVirtualDisplay.getDisplay());
+ mPresentation.show();
+ }
+ });
+ }
+
+ void dismissPresentation() {
+ if (mPresentation != null) {
+ mPresentation.dismiss();
+ }
+ }
+
+ public void doRendering(final int color) {
+ runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ mPresentation.doRendering(color);
+ }
+ });
+ }
+
+ private class TestPresentation extends Presentation {
+ private ImageView mImageView;
+
+ public TestPresentation(Context outerContext, Display display) {
+ super(outerContext, display);
+ getWindow().setType(WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mImageView = new ImageView(getContext());
+ mImageView.setImageDrawable(new ColorDrawable(0));
+ mImageView.setLayoutParams(new LayoutParams(
+ LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+ setContentView(mImageView);
+ }
+
+ public void doRendering(int color) {
+ mImageView.setImageDrawable(new ColorDrawable(color));
+ }
+ }
+ }
+
+ private void runOnMainSync(Runnable runner) {
+ SyncRunnable sr = new SyncRunnable(runner);
+ mHandlerForRunOnMain.post(sr);
+ sr.waitForComplete();
+ }
+
+ private static final class SyncRunnable implements Runnable {
+ private final Runnable mTarget;
+ private boolean mComplete;
+
+ public SyncRunnable(Runnable target) {
+ mTarget = target;
+ }
+
+ public void run() {
+ mTarget.run();
+ synchronized (this) {
+ mComplete = true;
+ notifyAll();
+ }
+ }
+
+ public void waitForComplete() {
+ synchronized (this) {
+ while (!mComplete) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ //ignore
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java b/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java
index 1c3928b..2b93064 100644
--- a/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java
+++ b/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java
@@ -120,7 +120,7 @@
+ "0.0.0.0/ipbits/0/expire/19000000000/sparams/ip,ipbits,expire"
+ ",id,itag,source,playlist_type/signature/773AB8ACC68A96E5AA48"
+ "1996AD6A1BBCB70DCB87.95733B544ACC5F01A1223A837D2CF04DF85A336"
- + "0/key/ik0", 60 * 1000);
+ + "0/key/ik0/file/m3u8", 60 * 1000);
}
// Streaming audio from local HTTP server
diff --git a/tests/tests/net/src/android/net/cts/TrafficStatsTest.java b/tests/tests/net/src/android/net/cts/TrafficStatsTest.java
old mode 100644
new mode 100755
index 9483bdc..5b93bee
--- a/tests/tests/net/src/android/net/cts/TrafficStatsTest.java
+++ b/tests/tests/net/src/android/net/cts/TrafficStatsTest.java
@@ -207,27 +207,37 @@
final int maxExpectedExtraPackets = 7;
final int minExpectedExtraPackets = 5;
+ // Some other tests don't cleanup connections correctly.
+ // They have the same UID, so we discount their lingering traffic
+ // which happens only on non-localhost, such as TCP FIN retranmission packets
+ long deltaTxOtherPackets = (totalTxPacketsAfter - totalTxPacketsBefore) - uidTxDeltaPackets;
+ long deltaRxOtherPackets = (totalRxPacketsAfter - totalRxPacketsBefore) - uidRxDeltaPackets;
+ if (deltaTxOtherPackets > 0 || deltaRxOtherPackets > 0) {
+ Log.i(LOG_TAG, "lingering traffic data: " + deltaTxOtherPackets + "/" + deltaRxOtherPackets);
+ // Make sure that not too many non-localhost packets are accounted for
+ assertTrue("too many non-localhost packets on the sam UID", deltaTxOtherPackets + deltaTxOtherPackets < 20);
+ }
assertTrue("uidtxp: " + uidTxPacketsBefore + " -> " + uidTxPacketsAfter + " delta=" + uidTxDeltaPackets +
" Wanted: " + uidTxDeltaPackets + ">=" + packetCount + "+" + minExpectedExtraPackets + " && " +
- uidTxDeltaPackets + "<=" + packetCount + "+" + packetCount + "+" + maxExpectedExtraPackets,
+ uidTxDeltaPackets + "<=" + packetCount + "+" + packetCount + "+" + maxExpectedExtraPackets + "+" + deltaTxOtherPackets,
uidTxDeltaPackets >= packetCount + minExpectedExtraPackets &&
- uidTxDeltaPackets <= packetCount + packetCount + maxExpectedExtraPackets);
+ uidTxDeltaPackets <= packetCount + packetCount + maxExpectedExtraPackets + deltaTxOtherPackets);
assertTrue("uidrxp: " + uidRxPacketsBefore + " -> " + uidRxPacketsAfter + " delta=" + uidRxDeltaPackets +
" Wanted: " + uidRxDeltaPackets + ">=" + packetCount + "+" + minExpectedExtraPackets + " && " +
uidRxDeltaPackets + "<=" + packetCount + "+" + packetCount + "+" + maxExpectedExtraPackets,
uidRxDeltaPackets >= packetCount + minExpectedExtraPackets &&
- uidRxDeltaPackets <= packetCount + packetCount + maxExpectedExtraPackets);
+ uidRxDeltaPackets <= packetCount + packetCount + maxExpectedExtraPackets + deltaRxOtherPackets);
assertTrue("uidtxb: " + uidTxBytesBefore + " -> " + uidTxBytesAfter + " delta=" + uidTxDeltaBytes +
" Wanted: " + uidTxDeltaBytes + ">=" + tcpPacketToIpBytes(packetCount, byteCount) + "+" + tcpPacketToIpBytes(minExpectedExtraPackets, 0) + " && " +
uidTxDeltaBytes + "<=" + tcpPacketToIpBytes(packetCount, byteCount) + "+" + tcpPacketToIpBytes(packetCount + maxExpectedExtraPackets, 0),
uidTxDeltaBytes >= tcpPacketToIpBytes(packetCount, byteCount) + tcpPacketToIpBytes(minExpectedExtraPackets, 0) &&
- uidTxDeltaBytes <= tcpPacketToIpBytes(packetCount, byteCount) + tcpPacketToIpBytes(packetCount + maxExpectedExtraPackets, 0));
+ uidTxDeltaBytes <= tcpPacketToIpBytes(packetCount, byteCount) + tcpPacketToIpBytes(packetCount + maxExpectedExtraPackets + deltaTxOtherPackets, 0));
assertTrue("uidrxb: " + uidRxBytesBefore + " -> " + uidRxBytesAfter + " delta=" + uidRxDeltaBytes +
" Wanted: " + uidRxDeltaBytes + ">=" + tcpPacketToIpBytes(packetCount, byteCount) + "+" + tcpPacketToIpBytes(minExpectedExtraPackets, 0) + " && " +
uidRxDeltaBytes + "<=" + tcpPacketToIpBytes(packetCount, byteCount) + "+" + tcpPacketToIpBytes(packetCount + maxExpectedExtraPackets, 0),
uidRxDeltaBytes >= tcpPacketToIpBytes(packetCount, byteCount) + tcpPacketToIpBytes(minExpectedExtraPackets, 0) &&
- uidRxDeltaBytes <= tcpPacketToIpBytes(packetCount, byteCount) + tcpPacketToIpBytes(packetCount + maxExpectedExtraPackets, 0));
+ uidRxDeltaBytes <= tcpPacketToIpBytes(packetCount, byteCount) + tcpPacketToIpBytes(packetCount + maxExpectedExtraPackets + deltaRxOtherPackets, 0));
// Localhost traffic *does* count against total stats.
// Fudge by 132 packets of 1500 bytes not related to the test.
diff --git a/tests/tests/permission/AndroidManifest.xml b/tests/tests/permission/AndroidManifest.xml
index 31f7b52..fa03335 100644
--- a/tests/tests/permission/AndroidManifest.xml
+++ b/tests/tests/permission/AndroidManifest.xml
@@ -18,6 +18,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.cts.permission">
+ <uses-permission android:name="android.permission.INJECT_EVENTS" />
<application>
<uses-library android:name="android.test.runner" />
<activity android:name="android.permission.cts.PermissionStubActivity"
diff --git a/tests/tests/permission/src/android/permission/cts/NoReadLogsPermissionTest.java b/tests/tests/permission/src/android/permission/cts/NoReadLogsPermissionTest.java
index 2e09a49..9441319 100644
--- a/tests/tests/permission/src/android/permission/cts/NoReadLogsPermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/NoReadLogsPermissionTest.java
@@ -52,14 +52,14 @@
int lineCt = 0;
String line;
while ((line = reader.readLine()) != null) {
- if (!line.startsWith("--------- beginning of /dev/log")) {
+ if (!line.startsWith("--------- beginning of ")) {
lineCt++;
}
}
// no permission get an empty log buffer.
// Logcat returns only one line:
- // "--------- beginning of /dev/log/main"
+ // "--------- beginning of <log device>"
assertEquals("Unexpected logcat entries. Are you running the "
+ "the latest logger.c from the Android kernel?",
diff --git a/tests/tests/permission/src/android/permission/cts/PackageManagerRequiringPermissionsTest.java b/tests/tests/permission/src/android/permission/cts/PackageManagerRequiringPermissionsTest.java
index 6f4c11a..2bfef62 100644
--- a/tests/tests/permission/src/android/permission/cts/PackageManagerRequiringPermissionsTest.java
+++ b/tests/tests/permission/src/android/permission/cts/PackageManagerRequiringPermissionsTest.java
@@ -27,7 +27,9 @@
*/
@SmallTest
public class PackageManagerRequiringPermissionsTest extends AndroidTestCase {
- private static final String PACKAGE_NAME = "com.android.cts.stub";
+ // Must be a known-present application package other than the one hosting this class
+ private static final String PACKAGE_NAME = "android";
+
private PackageManager mPackageManager;
@Override
diff --git a/tests/tests/provider/src/android/provider/cts/ContactsProvider2_AccountRemovalTest.java b/tests/tests/provider/src/android/provider/cts/ContactsProvider2_AccountRemovalTest.java
old mode 100644
new mode 100755
index f700220..cdb3546
--- a/tests/tests/provider/src/android/provider/cts/ContactsProvider2_AccountRemovalTest.java
+++ b/tests/tests/provider/src/android/provider/cts/ContactsProvider2_AccountRemovalTest.java
@@ -113,6 +113,7 @@
*/
public void testAccountRemovalWithMergedContact_doesNotDeleteContactAndTimestampUpdated() {
mAccountManager.addAccountExplicitly(ACCT_1, null, null);
+ mAccountManager.addAccountExplicitly(ACCT_2, null, null);
ArrayList<ContactIdPair> idList = createAndAssertMergedContact(ACCT_1, ACCT_2);
long contactId = idList.get(0).mContactId;
@@ -125,6 +126,7 @@
"Contact " + contactId + " last updated timestamp has not been updated.");
SystemClock.sleep(SLEEP_BETWEEN_POLL_MS);
}
+ mAccountManager.removeAccount(ACCT_2, null, null);
}
public void testAccountRemovalWithMergedContact_hasDeleteLogsForContacts() {
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_FilesTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_FilesTest.java
index a362df3..e1b05a2 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_FilesTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_FilesTest.java
@@ -29,11 +29,16 @@
import android.provider.MediaStore;
import android.provider.MediaStore.MediaColumns;
import android.test.AndroidTestCase;
+import android.util.Log;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
public class MediaStore_FilesTest extends AndroidTestCase {
@@ -279,6 +284,83 @@
if (sdfile != null) {
assertEquals(true, sdfile.delete());
}
+
+ // test secondary storage if present
+ List<File> allpaths = getSecondaryPackageSpecificPaths(mContext);
+ List<String> trimmedPaths = new ArrayList<String>();
+
+ for (File extpath: allpaths) {
+ assertNotNull("Valid media must be inserted during CTS", extpath);
+ assertEquals("Valid media must be inserted during CTS", Environment.MEDIA_MOUNTED,
+ Environment.getStorageState(extpath));
+
+ File child = extpath;
+ while (true) {
+ File parent = child.getParentFile();
+ if (parent == null) {
+ fail("didn't expect to be here");
+ }
+ if (!Environment.MEDIA_MOUNTED.equals(Environment.getStorageState(parent))) {
+ // we went past the root
+ String abspath = child.getAbsolutePath();
+ if (!trimmedPaths.contains(abspath)) {
+ trimmedPaths.add(abspath);
+ }
+ break;
+ }
+ child = parent;
+ }
+ }
+
+ for (String s: trimmedPaths) {
+ File dir = new File(s + "/foobardir");
+ File file = new File(dir, "foobar.jpg");
+
+ values = new ContentValues();
+ values.put(MediaStore.Files.FileColumns.DATA, dir.getAbsolutePath());
+ Uri dirUri = mResolver.insert(MediaStore.Files.getContentUri("external"), values);
+ assertNotNull(dirUri);
+
+ values = new ContentValues();
+ values.put(MediaStore.Files.FileColumns.DATA, file.getAbsolutePath());
+ Uri fileUri = mResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
+ assertNotNull(fileUri);
+
+ // check that adding the file or its folder didn't cause either one to be created
+ assertFalse(dir.exists());
+ assertFalse(file.exists());
+
+ // check if opening the file for write works
+ try {
+ mResolver.openOutputStream(fileUri).close();
+ fail("shouldn't have been able to create output stream");
+ } catch (SecurityException e) {
+ // expected
+ }
+ // check that deleting the file or its folder doesn't cause either one to be created
+ mResolver.delete(fileUri, null, null);
+ assertFalse(dir.exists());
+ assertFalse(file.exists());
+ mResolver.delete(dirUri, null, null);
+ assertFalse(dir.exists());
+ assertFalse(file.exists());
+ }
+ }
+
+ public static List<File> getSecondaryPackageSpecificPaths(Context context) {
+ final List<File> paths = new ArrayList<File>();
+ Collections.addAll(paths, dropFirst(context.getExternalCacheDirs()));
+ Collections.addAll(paths, dropFirst(context.getExternalFilesDirs(null)));
+ Collections.addAll(
+ paths, dropFirst(context.getExternalFilesDirs(Environment.DIRECTORY_PICTURES)));
+ Collections.addAll(paths, dropFirst(context.getObbDirs()));
+ return paths;
+ }
+
+ private static File[] dropFirst(File[] before) {
+ final File[] after = new File[before.length - 1];
+ System.arraycopy(before, 1, after, 0, after.length);
+ return after;
}
private void writeFile(int resid, String path) throws IOException {
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/DeletedContactUtil.java b/tests/tests/provider/src/android/provider/cts/contacts/DeletedContactUtil.java
index 692570a..3b05cf9 100644
--- a/tests/tests/provider/src/android/provider/cts/contacts/DeletedContactUtil.java
+++ b/tests/tests/provider/src/android/provider/cts/contacts/DeletedContactUtil.java
@@ -38,8 +38,13 @@
};
Uri uri = ContentUris.withAppendedId(URI, contactId);
Cursor cursor = resolver.query(uri, projection, null, null, null);
- if (cursor.moveToNext()) {
- return cursor.getLong(0);
+ if (null != cursor) {
+ long deletedTime = -1;
+ if (cursor.moveToNext()) {
+ deletedTime = cursor.getLong(0);
+ cursor.close();
+ return deletedTime;
+ }
}
return CommonDatabaseUtils.NOT_FOUND;
}
diff --git a/tests/tests/rscpp/librscpptest/rs_jni.cpp b/tests/tests/rscpp/librscpptest/rs_jni.cpp
index 92ebd56..21eae96 100644
--- a/tests/tests/rscpp/librscpptest/rs_jni.cpp
+++ b/tests/tests/rscpp/librscpptest/rs_jni.cpp
@@ -29,6 +29,28 @@
using namespace android::RSC;
+/*returns an addr aligned to the byte boundary specified by align*/
+#define align_addr(addr,align) (void *)(((size_t)(addr) + ((align) - 1)) & (size_t) - (align))
+#define ADDRESS_STORAGE_SIZE sizeof(size_t)
+
+void * aligned_alloc(size_t align, size_t size) {
+ void * addr, * x = NULL;
+ addr = malloc(size + align - 1 + ADDRESS_STORAGE_SIZE);
+ if (addr) {
+ x = align_addr((unsigned char *) addr + ADDRESS_STORAGE_SIZE, (int) align);
+ /* save the actual malloc address */
+ ((size_t *) x)[-1] = (size_t) addr;
+ }
+ return x;
+}
+
+void aligned_free(void * memblk) {
+ if (memblk) {
+ void * addr = (void *) (((size_t *) memblk)[-1]);
+ free(addr);
+ }
+}
+
extern "C" JNIEXPORT jboolean JNICALL Java_android_cts_rscpp_RSInitTest_initTest(JNIEnv * env,
jclass obj,
jstring pathObj)
@@ -349,3 +371,50 @@
}
+extern "C" JNIEXPORT jboolean JNICALL
+Java_android_cts_rscpp_RSInterPredTest_interpredTest(JNIEnv * env,
+ jclass obj,
+ jstring pathObj,
+ jbyteArray jRef,
+ jintArray jParam,
+ jint jFirCount,
+ jint jSecCount,
+ jint jParamOffset)
+{
+ const char * path = env->GetStringUTFChars(pathObj, NULL);
+ jint * pParam = env->GetIntArrayElements(jParam, NULL);
+ jbyte * pRef = (jbyte *) env->GetPrimitiveArrayCritical(jRef, 0);
+
+ sp<RS> rs = new RS();
+ rs->init(path);
+
+ sp<const Element> e = Element::U8(rs);
+ Type::Builder builder(rs, e);
+
+ size_t frame_size = env->GetArrayLength(jRef);
+ uint8_t * frame_buffer_ptr = (uint8_t *) aligned_alloc(128, frame_size);
+ memcpy(frame_buffer_ptr, pRef, frame_size);
+
+ sp<Allocation> refAlloc = Allocation::createTyped(rs, builder.create(), RS_ALLOCATION_MIPMAP_NONE,
+ RS_ALLOCATION_USAGE_SHARED | RS_ALLOCATION_USAGE_SCRIPT,
+ frame_buffer_ptr);
+ sp<Allocation> paramAlloc = Allocation::createTyped(rs, builder.create(), RS_ALLOCATION_MIPMAP_NONE,
+ RS_ALLOCATION_USAGE_SHARED | RS_ALLOCATION_USAGE_SCRIPT,
+ pParam);
+ sp<Allocation> kernelAllocation = Allocation::createTyped(rs, builder.create());
+
+ sp<android::RSC::ScriptIntrinsicVP9InterPred> interPred = ScriptIntrinsicVP9InterPred::create(rs, e);
+ interPred->setRef(refAlloc);
+ interPred->setParamCount(jFirCount, jSecCount, jParamOffset * 11 * 4);
+ interPred->setParam(paramAlloc);
+ interPred->forEach(kernelAllocation);
+ rs->finish();
+
+ memcpy(pRef, frame_buffer_ptr, frame_size);
+ aligned_free(frame_buffer_ptr);
+ env->ReleasePrimitiveArrayCritical(jRef, pRef, 0);
+ env->ReleaseIntArrayElements(jParam, pParam, JNI_ABORT);
+ env->ReleaseStringUTFChars(pathObj, path);
+ return (rs->getError() == RS_SUCCESS);
+
+}
diff --git a/tests/tests/rscpp/src/android/cts/rscpp/RSInterPredTest.java b/tests/tests/rscpp/src/android/cts/rscpp/RSInterPredTest.java
new file mode 100644
index 0000000..90c37e4
--- /dev/null
+++ b/tests/tests/rscpp/src/android/cts/rscpp/RSInterPredTest.java
@@ -0,0 +1,478 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.rscpp;
+
+import com.android.cts.stub.R;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.test.AndroidTestCase;
+import android.renderscript.*;
+import android.util.Log;
+import java.io.InputStream;
+import java.util.Random;
+import org.apache.http.util.EncodingUtils;
+
+public class RSInterPredTest extends RSCppTest {
+ static {
+ System.loadLibrary("rscpptest_jni");
+ }
+
+ native boolean interpredTest(String cacheDir, byte[] Ref, int[] Param, int firCountt, int secCount, int paramOffset);
+
+ private static final int mRefW = 2450;
+ private static final int mRefH = 1920;
+
+ private byte[] refArray;
+ private byte[] refJAVA;
+ private int[] paramArray;
+
+ private final int firCount = 27228;
+ private final int secCount = 18;
+ private final int paramOffset = firCount;
+
+ private final int FILTER_BITS = 7;
+ private final int SUBPEL_BITS = 4;
+ private final int SUBPEL_MASK = (1 << SUBPEL_BITS) - 1;
+ private final int SUBPEL_TAPS = 8;
+
+ final short[] inter_pred_filters = new short[]{
+ 0, 0, 0, 128, 0, 0, 0, 0, 0, 1, -5, 126, 8, -3, 1, 0,
+ -1, 3, -10, 122, 18, -6, 2, 0, -1, 4, -13, 118, 27, -9, 3, -1,
+ -1, 4, -16, 112, 37, -11, 4, -1, -1, 5, -18, 105, 48, -14, 4, -1,
+ -1, 5, -19, 97, 58, -16, 5, -1, -1, 6, -19, 88, 68, -18, 5, -1,
+ -1, 6, -19, 78, 78, -19, 6, -1, -1, 5, -18, 68, 88, -19, 6, -1,
+ -1, 5, -16, 58, 97, -19, 5, -1, -1, 4, -14, 48, 105, -18, 5, -1,
+ -1, 4, -11, 37, 112, -16, 4, -1, -1, 3, -9, 27, 118, -13, 4, -1,
+ 0, 2, -6, 18, 122, -10, 3, -1, 0, 1, -3, 8, 126, -5, 1, 0,
+ 0, 0, 0, 128, 0, 0, 0, 0, -3, -1, 32, 64, 38, 1, -3, 0,
+ -2, -2, 29, 63, 41, 2, -3, 0, -2, -2, 26, 63, 43, 4, -4, 0,
+ -2, -3, 24, 62, 46, 5, -4, 0, -2, -3, 21, 60, 49, 7, -4, 0,
+ -1, -4, 18, 59, 51, 9, -4, 0, -1, -4, 16, 57, 53, 12, -4, -1,
+ -1, -4, 14, 55, 55, 14, -4, -1, -1, -4, 12, 53, 57, 16, -4, -1,
+ 0, -4, 9, 51, 59, 18, -4, -1, 0, -4, 7, 49, 60, 21, -3, -2,
+ 0, -4, 5, 46, 62, 24, -3, -2, 0, -4, 4, 43, 63, 26, -2, -2,
+ 0, -3, 2, 41, 63, 29, -2, -2, 0, -3, 1, 38, 64, 32, -1, -3,
+ 0, 0, 0, 128, 0, 0, 0, 0, -1, 3, -7, 127, 8, -3, 1, 0,
+ -2, 5, -13, 125, 17, -6, 3, -1, -3, 7, -17, 121, 27, -10, 5, -2,
+ -4, 9, -20, 115, 37, -13, 6, -2, -4, 10, -23, 108, 48, -16, 8, -3,
+ -4, 10, -24, 100, 59, -19, 9, -3, -4, 11, -24, 90, 70, -21, 10, -4,
+ -4, 11, -23, 80, 80, -23, 11, -4, -4, 10, -21, 70, 90, -24, 11, -4,
+ -3, 9, -19, 59, 100, -24, 10, -4, -3, 8, -16, 48, 108, -23, 10, -4,
+ -2, 6, -13, 37, 115, -20, 9, -4, -2, 5, -10, 27, 121, -17, 7, -3,
+ -1, 3, -6, 17, 125, -13, 5, -2, 0, 1, -3, 8, 127, -7, 3, -1,
+ 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 120, 8, 0, 0, 0,
+ 0, 0, 0, 112, 16, 0, 0, 0, 0, 0, 0, 104, 24, 0, 0, 0,
+ 0, 0, 0, 96, 32, 0, 0, 0, 0, 0, 0, 88, 40, 0, 0, 0,
+ 0, 0, 0, 80, 48, 0, 0, 0, 0, 0, 0, 72, 56, 0, 0, 0,
+ 0, 0, 0, 64, 64, 0, 0, 0, 0, 0, 0, 56, 72, 0, 0, 0,
+ 0, 0, 0, 48, 80, 0, 0, 0, 0, 0, 0, 40, 88, 0, 0, 0,
+ 0, 0, 0, 32, 96, 0, 0, 0, 0, 0, 0, 24, 104, 0, 0, 0,
+ 0, 0, 0, 16, 112, 0, 0, 0, 0, 0, 0, 8, 120, 0, 0, 0
+ };
+
+ private void initInArray(byte[] array) {
+ Random rand = new Random();
+ for (int i = 0; i < array.length; i++) {
+ array[i] = (byte)(rand.nextInt(255));
+ }
+ }
+
+ public void testRSInterPred() {
+ refArray = new byte[mRefW * mRefH * 3];
+ paramArray = new int[(firCount + secCount) * 11];
+ initInArray(refArray);
+
+ try {
+ InputStream in = getContext().getResources().openRawResource(R.raw.rs_interpred_param);
+ int length = in.available();
+ byte[] buffer = new byte[length];
+ in.read(buffer);
+ String str = EncodingUtils.getString(buffer, "BIG5");
+ String strArr[] = str.split(",");
+ for (int i = 0; i < firCount + secCount; i++) {
+ paramArray[i * 11 + 0] = Integer.parseInt(strArr[i * 12 + 0]);
+ if (Integer.parseInt(strArr[i * 12 + 1]) == 3) {
+ paramArray[i * 11 + 1] = Integer.parseInt(strArr[i * 12 + 2]) + mRefW * mRefH;
+ } else {
+ paramArray[i * 11 + 1] = Integer.parseInt(strArr[i * 12 + 2]);
+ }
+ paramArray[i * 11 + 2] = Integer.parseInt(strArr[i * 12 + 3]);
+ paramArray[i * 11 + 3] = Integer.parseInt(strArr[i * 12 + 4]) + mRefW * mRefH * 2;
+ paramArray[i * 11 + 4] = Integer.parseInt(strArr[i * 12 + 5]);
+ paramArray[i * 11 + 5] = Integer.parseInt(strArr[i * 12 + 6]);
+ paramArray[i * 11 + 6] = Integer.parseInt(strArr[i * 12 + 7]);
+ paramArray[i * 11 + 7] = Integer.parseInt(strArr[i * 12 + 8]);
+ paramArray[i * 11 + 8] = Integer.parseInt(strArr[i * 12 + 9]);
+ paramArray[i * 11 + 9] = Integer.parseInt(strArr[i * 12 + 10]);
+ paramArray[i * 11 + 10] = Integer.parseInt(strArr[i * 12 + 11]);
+ }
+ in.close();
+ } catch(Exception e) {
+ e.printStackTrace();
+ }
+
+ refJAVA = new byte[refArray.length];
+ for (int i = 0; i < refArray.length; i++) {
+ refJAVA[i] = refArray[i];
+ }
+
+ interpredTest(this.getContext().getCacheDir().toString(), refArray, paramArray, firCount, secCount, paramOffset);
+
+ interPred(refJAVA, firCount, secCount, paramOffset);
+
+ for (int i = mRefW * mRefH; i < mRefW * mRefH * 2; ++i) {
+ assertTrue(refArray[i] == refJAVA[i]);
+ }
+
+ }
+
+ private void interPred(byte[] srcArray, int firCount, int secCount, int paramOffset) {
+ final int[][] vp9_convolve_mode = new int[][]{{24, 16}, {8, 0}};
+ int ref_base = 0;
+ int fri_param = 0;
+ int sec_param = paramOffset;
+ int fri_count = firCount;
+ int sec_count = secCount;
+ int mode_num;
+ int src;
+ int dst;
+ int filter_x;
+ int filter_y;
+
+ for (int i = 0; i < fri_count; i++) {
+ mode_num = vp9_convolve_mode[paramArray[(fri_param + i) * 11 + 6] == 16 ? 1 : 0]
+ [paramArray[(fri_param + i) * 11 + 8] == 16 ? 1 : 0];
+ src = ref_base + paramArray[(fri_param + i) * 11 + 1];
+ dst = ref_base + paramArray[(fri_param + i) * 11 + 3];
+
+ filter_x = paramArray[(fri_param + i) * 11 + 5];
+ filter_y = paramArray[(fri_param + i) * 11 + 7];
+
+ mSwitchConvolve(paramArray[(fri_param + i) * 11 + 0] + mode_num,
+ srcArray, src, paramArray[(fri_param + i) * 11 + 2],
+ srcArray, dst, paramArray[(fri_param + i) * 11 + 4],
+ filter_x, paramArray[(fri_param + i) * 11 + 6],
+ filter_y, paramArray[(fri_param + i) * 11 + 8],
+ paramArray[(fri_param + i) * 11 + 9],
+ paramArray[(fri_param + i) * 11 + 10]);
+ }
+ for (int i = 0; i < sec_count; i++) {
+ mode_num = vp9_convolve_mode[paramArray[(sec_param + i) * 11 + 6] == 16 ? 1 : 0]
+ [paramArray[(sec_param + i) * 11 + 8] == 16 ? 1 : 0];
+ src = ref_base + paramArray[(sec_param + i) * 11 + 1];
+ dst = ref_base + paramArray[(sec_param + i) * 11 + 3];
+
+ filter_x = paramArray[(sec_param + i) * 11 + 5];
+ filter_y = paramArray[(sec_param + i) * 11 + 7];
+
+ mSwitchConvolve(paramArray[(sec_param + i) * 11 + 0] + mode_num + 1,
+ srcArray, src, paramArray[(sec_param + i) * 11 + 2],
+ srcArray, dst, paramArray[(sec_param + i) * 11 + 4],
+ filter_x, paramArray[(sec_param + i) * 11 + 6],
+ filter_y, paramArray[(sec_param + i) * 11 + 8],
+ paramArray[(sec_param + i) * 11 + 9],
+ paramArray[(sec_param + i) * 11 + 10]);
+ }
+ }
+
+ private void mSwitchConvolve(int mode,
+ byte[] srcArray, int src, int src_stride,
+ byte[] dstArray, int dst, int dst_stride,
+ int filter_x, int x_step_q4, int filter_y, int y_step_q4,
+ int w, int h) {
+ switch (mode) {
+ case 0:
+ vp9_convolve_copy_c(srcArray, src, src_stride, dstArray, dst, dst_stride,
+ filter_x, x_step_q4, filter_y, y_step_q4, w, h);
+ break;
+ case 1:
+ vp9_convolve_avg_c(srcArray, src, src_stride, dstArray, dst, dst_stride,
+ filter_x, x_step_q4, filter_y, y_step_q4, w, h);
+ break;
+ case 2:
+ case 8:
+ case 10:
+ vp9_convolve8_vert_c(srcArray, src, src_stride, dstArray, dst, dst_stride,
+ filter_x, x_step_q4, filter_y, y_step_q4, w, h);
+ break;
+ case 3:
+ case 9:
+ case 11:
+ vp9_convolve8_avg_vert_c(srcArray, src, src_stride, dstArray, dst, dst_stride,
+ filter_x, x_step_q4, filter_y, y_step_q4, w, h);
+ break;
+ case 4:
+ case 16:
+ case 20:
+ vp9_convolve8_horiz_c(srcArray, src, src_stride, dstArray, dst, dst_stride,
+ filter_x, x_step_q4, filter_y, y_step_q4, w, h);
+ break;
+ case 5:
+ case 17:
+ case 21:
+ vp9_convolve8_avg_horiz_c(srcArray, src, src_stride, dstArray, dst, dst_stride,
+ filter_x, x_step_q4, filter_y, y_step_q4, w, h);
+ break;
+ case 6:
+ case 12:
+ case 14:
+ case 18:
+ case 22:
+ case 24:
+ case 26:
+ case 28:
+ case 30:
+ vp9_convolve8_c(srcArray, src, src_stride, dstArray, dst, dst_stride,
+ filter_x, x_step_q4, filter_y, y_step_q4, w, h);
+ break;
+ case 7:
+ case 13:
+ case 15:
+ case 19:
+ case 23:
+ case 25:
+ case 27:
+ case 29:
+ case 31:
+ vp9_convolve8_avg_c(srcArray, src, src_stride, dstArray, dst, dst_stride,
+ filter_x, x_step_q4, filter_y, y_step_q4, w, h);
+ break;
+ default:
+ break;
+ }
+ }
+
+ private void vp9_convolve_copy_c(byte[] srcArray, int src, int src_stride,
+ byte[] dstArray, int dst, int dst_stride,
+ int filter_x, int filter_x_stride,
+ int filter_y, int filter_y_stride,
+ int w, int h) {
+ int r;
+ for (r = h; r > 0; --r) {
+ for (int i = 0; i < w; i++) {
+ dstArray[dst + i] = srcArray[src + i];
+ }
+ src += src_stride;
+ dst += dst_stride;
+ }
+ }
+
+ private void vp9_convolve_avg_c(byte[] srcArray, int src, int src_stride,
+ byte[] dstArray, int dst, int dst_stride,
+ int filter_x, int filter_x_stride,
+ int filter_y, int filter_y_stride,
+ int w, int h) {
+ int x, y;
+ for (y = 0; y < h; ++y) {
+ for (x = 0; x < w; ++x)
+ dstArray[dst + x] = (byte)ROUND_POWER_OF_TWO((0xff & dstArray[dst + x]) +
+ (0xff & srcArray[src + x]), 1);
+ src += src_stride;
+ dst += dst_stride;
+ }
+ }
+
+ private void vp9_convolve8_vert_c(byte[] srcArray, int src, int src_stride,
+ byte[] dstArray, int dst, int dst_stride,
+ int filter_x, int x_step_q4,
+ int filter_y, int y_step_q4,
+ int w, int h) {
+ int filters_y = get_filter_base(filter_y);
+ int y0_q4 = get_filter_offset(filter_y, filters_y);
+ convolve_vert(srcArray, src, src_stride, dstArray, dst, dst_stride,
+ filters_y, y0_q4, y_step_q4, w, h);
+ }
+
+ private void vp9_convolve8_avg_vert_c(byte[] srcArray, int src, int src_stride,
+ byte[] dstArray, int dst, int dst_stride,
+ int filter_x, int x_step_q4,
+ int filter_y, int y_step_q4,
+ int w, int h) {
+ int filters_y = get_filter_base(filter_y);
+ int y0_q4 = get_filter_offset(filter_y, filters_y);
+ convolve_avg_vert(srcArray, src, src_stride, dstArray, dst, dst_stride,
+ filters_y, y0_q4, y_step_q4, w, h);
+ }
+
+ private void vp9_convolve8_horiz_c(byte[] srcArray, int src, int src_stride,
+ byte[] dstArray, int dst, int dst_stride,
+ int filter_x, int x_step_q4,
+ int filter_y, int y_step_q4,
+ int w, int h) {
+ int filters_x = get_filter_base(filter_x);
+ int x0_q4 = get_filter_offset(filter_x, filters_x);
+ convolve_horiz(srcArray, src, src_stride, dstArray, dst, dst_stride,
+ filters_x, x0_q4, x_step_q4, w, h);
+ }
+
+ private void vp9_convolve8_avg_horiz_c(byte[] srcArray, int src, int src_stride,
+ byte[] dstArray, int dst, int dst_stride,
+ int filter_x, int x_step_q4,
+ int filter_y, int y_step_q4,
+ int w, int h) {
+ int filters_x = get_filter_base(filter_x);
+ int x0_q4 = get_filter_offset(filter_x, filters_x);
+ convolve_avg_horiz(srcArray, src, src_stride, dstArray, dst, dst_stride,
+ filters_x, x0_q4, x_step_q4, w, h);
+ }
+
+ private void vp9_convolve8_c(byte[] srcArray, int src, int src_stride,
+ byte[] dstArray, int dst, int dst_stride,
+ int filter_x, int x_step_q4,
+ int filter_y, int y_step_q4,
+ int w, int h) {
+ int filters_x = get_filter_base(filter_x);
+ int x0_q4 = get_filter_offset(filter_x, filters_x);
+ int filters_y = get_filter_base(filter_y);
+ int y0_q4 = get_filter_offset(filter_y, filters_y);
+ convolve(srcArray, src, src_stride, dstArray, dst, dst_stride,
+ filters_x, x0_q4, x_step_q4,
+ filters_y, y0_q4, y_step_q4, w, h);
+ }
+
+ private void vp9_convolve8_avg_c(byte[] srcArray, int src, int src_stride,
+ byte[] dstArray, int dst, int dst_stride,
+ int filter_x, int x_step_q4,
+ int filter_y, int y_step_q4,
+ int w, int h) {
+ byte[] temp_ = new byte[(64 * 64) + (16) / 1 + 1];
+ int temp = (0 + (16) - 1) & (-16);
+ vp9_convolve8_c(srcArray, src, src_stride, temp_, temp, 64,
+ filter_x, x_step_q4, filter_y, y_step_q4, w, h);
+ vp9_convolve_avg_c(temp_, temp, 64, dstArray, dst, dst_stride, 0, 0, 0, 0, w, h);
+ }
+
+ private void convolve_vert(byte[] srcArray, int src, int src_stride,
+ byte[] dstArray, int dst, int dst_stride, int y_filters,
+ int y0_q4, int y_step_q4, int w, int h) {
+ int x, y;
+ src -= src_stride * (SUBPEL_TAPS / 2 - 1);
+ for (x = 0; x < w; ++x) {
+ int y_q4 = y0_q4;
+ for (y = 0; y < h; ++y) {
+ int src_y = src + (y_q4 >> SUBPEL_BITS) * src_stride;
+ int y_filter = y_filters + (y_q4 & SUBPEL_MASK) * 8;
+ int k, sum = 0;
+ for (k = 0; k < SUBPEL_TAPS; ++k)
+ sum += (0xff & srcArray[src_y + k * src_stride]) * inter_pred_filters[y_filter + k];
+ dstArray[dst + y * dst_stride] = clip_pixel(ROUND_POWER_OF_TWO(sum, FILTER_BITS));
+ y_q4 += y_step_q4;
+ }
+ ++src;
+ ++dst;
+ }
+ }
+
+ private void convolve_avg_vert(byte[] srcArray, int src, int src_stride,
+ byte[] dstArray, int dst, int dst_stride,
+ int y_filters, int y0_q4, int y_step_q4, int w, int h) {
+ int x, y;
+ src -= src_stride * (SUBPEL_TAPS / 2 - 1);
+ for (x = 0; x < w; ++x) {
+ int y_q4 = y0_q4;
+ for (y = 0; y < h; ++y) {
+ int src_y = src + (y_q4 >> SUBPEL_BITS) * src_stride;
+ int y_filter = y_filters + (y_q4 & SUBPEL_MASK) * 8;
+ int k, sum = 0;
+ for (k = 0; k < SUBPEL_TAPS; ++k)
+ sum += (0xff & srcArray[src_y + k * src_stride]) * inter_pred_filters[y_filter + k];
+ dstArray[dst + y * dst_stride] = (byte)ROUND_POWER_OF_TWO(
+ (0xff & dstArray[dst + y * dst_stride]) +
+ (0xff & clip_pixel(ROUND_POWER_OF_TWO(sum, FILTER_BITS))), 1);
+ y_q4 += y_step_q4;
+ }
+ ++src;
+ ++dst;
+ }
+ }
+
+ private void convolve_horiz(byte[] srcArray, int src, int src_stride,
+ byte[] dstArray, int dst, int dst_stride,
+ int x_filters, int x0_q4, int x_step_q4, int w, int h) {
+ int x, y;
+ src -= SUBPEL_TAPS / 2 - 1;
+ for (y = 0; y < h; ++y) {
+ int x_q4 = x0_q4;
+ for (x = 0; x < w; ++x) {
+ int src_x = src + (x_q4 >> SUBPEL_BITS);
+ int x_filter = x_filters + (x_q4 & SUBPEL_MASK) * 8;
+ int k, sum = 0;
+ for (k = 0; k < SUBPEL_TAPS; ++k)
+ sum += (0xff & srcArray[src_x + k]) * inter_pred_filters[x_filter + k];
+ dstArray[dst + x] = clip_pixel(ROUND_POWER_OF_TWO(sum, FILTER_BITS));
+ x_q4 += x_step_q4;
+ }
+ src += src_stride;
+ dst += dst_stride;
+ }
+ }
+
+ private void convolve_avg_horiz(byte[] srcArray, int src, int src_stride,
+ byte[] dstArray, int dst, int dst_stride,
+ int x_filters, int x0_q4, int x_step_q4, int w, int h) {
+ int x, y;
+ src -= SUBPEL_TAPS / 2 - 1;
+ for (y = 0; y < h; ++y) {
+ int x_q4 = x0_q4;
+ for (x = 0; x < w; ++x) {
+ int src_x = src + (x_q4 >> SUBPEL_BITS);
+ int x_filter = x_filters + (x_q4 & SUBPEL_MASK) * 8;
+ int k, sum = 0;
+ for (k = 0; k < SUBPEL_TAPS; ++k)
+ sum += (0xff & srcArray[src_x + k]) * inter_pred_filters[x_filter + k];
+ dstArray[dst + x] = (byte)ROUND_POWER_OF_TWO((0xff & dstArray[dst + x]) +
+ (0xff & clip_pixel(ROUND_POWER_OF_TWO(sum, FILTER_BITS))), 1);
+ x_q4 += x_step_q4;
+ }
+ src += src_stride;
+ dst += dst_stride;
+ }
+ }
+
+ private void convolve(byte[] srcArray, int src, int src_stride,
+ byte[] dstArray, int dst, int dst_stride,
+ int x_filters, int x0_q4, int x_step_q4, int y_filters,
+ int y0_q4, int y_step_q4, int w, int h) {
+ byte[] temp = new byte[64 * 324];
+ int intermediate_height = (((h - 1) * y_step_q4 + 15) >> 4) + SUBPEL_TAPS;
+ if (intermediate_height < h)
+ intermediate_height = h;
+ convolve_horiz(srcArray, src - src_stride * (SUBPEL_TAPS / 2 - 1), src_stride, temp, 0, 64,
+ x_filters, x0_q4, x_step_q4, w, intermediate_height);
+ convolve_vert(temp, 0 + 64 * (SUBPEL_TAPS / 2 - 1), 64, dstArray, dst, dst_stride,
+ y_filters, y0_q4, y_step_q4, w, h);
+ }
+
+ private int ROUND_POWER_OF_TWO(int value, int n) {
+ int res = (((value) + (1 << ((n) - 1))) >> (n));
+ return res;
+ }
+
+ private int get_filter_base(int filter) {
+ return filter;
+ }
+
+ private int get_filter_offset(int f, int base) {
+ return 0;
+ }
+
+ private byte clip_pixel(int val) {
+ return (byte)(((val > 255) ? (0xff & 255) : (val < 0) ? 0 : val));
+ }
+
+}
diff --git a/tests/tests/security/Android.mk b/tests/tests/security/Android.mk
index 4b3a904..d0fefa1 100644
--- a/tests/tests/security/Android.mk
+++ b/tests/tests/security/Android.mk
@@ -18,12 +18,11 @@
LOCAL_MODULE_TAGS := tests
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner guava ctstestserver
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner guava
LOCAL_JNI_SHARED_LIBRARIES := libctssecurity_jni
-LOCAL_SRC_FILES := $(call all-java-files-under, src) \
- src/android/security/cts/activity/ISecureRandomService.aidl
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := CtsSecurityTestCases
@@ -31,8 +30,6 @@
LOCAL_SDK_VERSION := current
-LOCAL_ASSET_DIR := $(LOCAL_PATH)/assets
-
include $(BUILD_CTS_PACKAGE)
include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/security/AndroidManifest.xml b/tests/tests/security/AndroidManifest.xml
index 15b239a..da95e5c 100644
--- a/tests/tests/security/AndroidManifest.xml
+++ b/tests/tests/security/AndroidManifest.xml
@@ -25,12 +25,14 @@
<application>
<uses-library android:name="android.test.runner" />
- <service android:name="android.security.cts.activity.SecureRandomService"
- android:process=":secureRandom"/>
+ <service android:name="android.security.cts.SeccompDeathTestService"
+ android:process=":death_test_service"
+ android:isolatedProcess="true"
+ android:exported="true"/>
</application>
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.cts.security"
+ android:targetPackage="com.android.cts.stub"
android:label="CTS tests of com.android.cts.security">
<meta-data android:name="listener"
android:value="com.android.cts.runner.CtsTestRunListener" />
diff --git a/tests/tests/security/jni/Android.mk b/tests/tests/security/jni/Android.mk
index 64b6962..94fd02d 100644
--- a/tests/tests/security/jni/Android.mk
+++ b/tests/tests/security/jni/Android.mk
@@ -24,9 +24,11 @@
LOCAL_SRC_FILES := \
CtsSecurityJniOnLoad.cpp \
android_security_cts_CharDeviceTest.cpp \
+ android_security_cts_KernelSettingsTest.cpp \
android_security_cts_LinuxRngTest.cpp \
android_security_cts_LoadEffectLibraryTest.cpp \
android_security_cts_NativeCodeTest.cpp \
+ android_security_cts_SeccompDeathTestService.cpp \
android_security_cts_SELinuxTest.cpp
LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
diff --git a/tests/tests/security/jni/CtsSecurityJniOnLoad.cpp b/tests/tests/security/jni/CtsSecurityJniOnLoad.cpp
index d6179e8..9d00ad6 100644
--- a/tests/tests/security/jni/CtsSecurityJniOnLoad.cpp
+++ b/tests/tests/security/jni/CtsSecurityJniOnLoad.cpp
@@ -17,10 +17,12 @@
#include <jni.h>
#include <stdio.h>
+extern int register_android_security_cts_KernelSettingsTest(JNIEnv*);
extern int register_android_security_cts_CharDeviceTest(JNIEnv*);
extern int register_android_security_cts_LinuxRngTest(JNIEnv*);
extern int register_android_security_cts_NativeCodeTest(JNIEnv*);
extern int register_android_security_cts_LoadEffectLibraryTest(JNIEnv*);
+extern int register_android_security_cts_SeccompDeathTestService(JNIEnv*);
extern int register_android_security_cts_SELinuxTest(JNIEnv*);
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
@@ -46,9 +48,17 @@
return JNI_ERR;
}
+ if (register_android_security_cts_SeccompDeathTestService(env)) {
+ return JNI_ERR;
+ }
+
if (register_android_security_cts_SELinuxTest(env)) {
return JNI_ERR;
}
+ if (register_android_security_cts_KernelSettingsTest(env)) {
+ return JNI_ERR;
+ }
+
return JNI_VERSION_1_4;
}
diff --git a/tests/tests/security/jni/android_security_cts_KernelSettingsTest.cpp b/tests/tests/security/jni/android_security_cts_KernelSettingsTest.cpp
new file mode 100644
index 0000000..bab7b57
--- /dev/null
+++ b/tests/tests/security/jni/android_security_cts_KernelSettingsTest.cpp
@@ -0,0 +1,39 @@
+/*
+ * 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 <sys/types.h>
+#include <unistd.h>
+#include <sys/xattr.h>
+#include <errno.h>
+
+static jboolean android_security_cts_KernelSettingsTest_supportsXattr(JNIEnv* env, jobject thiz)
+{
+ int result = getxattr("/system/bin/toolbox", "security.capability", NULL, 0);
+ return ((result >= 0) || (errno == ENODATA));
+}
+
+static JNINativeMethod gMethods[] = {
+ { "supportsXattr", "()Z",
+ (void *) android_security_cts_KernelSettingsTest_supportsXattr },
+};
+
+int register_android_security_cts_KernelSettingsTest(JNIEnv* env)
+{
+ jclass clazz = env->FindClass("android/security/cts/KernelSettingsTest");
+ return env->RegisterNatives(clazz, gMethods,
+ sizeof(gMethods) / sizeof(JNINativeMethod));
+}
diff --git a/tests/tests/security/jni/android_security_cts_NativeCodeTest.cpp b/tests/tests/security/jni/android_security_cts_NativeCodeTest.cpp
index 1db8e97..2f3fb79 100644
--- a/tests/tests/security/jni/android_security_cts_NativeCodeTest.cpp
+++ b/tests/tests/security/jni/android_security_cts_NativeCodeTest.cpp
@@ -27,9 +27,13 @@
#include <sys/wait.h>
#include <signal.h>
#include <stdlib.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <fcntl.h>
#include <cutils/log.h>
#include <linux/perf_event.h>
#include <errno.h>
+#include <inttypes.h>
#define PASSED 0
#define UNKNOWN_ERROR -1
@@ -198,7 +202,7 @@
// We found an address which isn't in our our, or our child's,
// address space, but yet which is still writable. Scribble
// all over it.
- ALOGE("parent: found writable at %x", addr);
+ ALOGE("parent: found writable at %" PRIxPTR, addr);
uintptr_t addr2;
for (addr2 = addr; addr2 < addr + SEARCH_SIZE; addr2++) {
syscall(__NR_ptrace, PTRACE_PEEKDATA, child, &secret, addr2);
@@ -238,6 +242,58 @@
return parent(pid);
}
+static void* mmap_syscall(void* addr, size_t len, int prot, int flags, int fd, off_t offset)
+{
+#ifdef __LP64__
+ return mmap(addr, len, prot, flags, fd, offset);
+#else
+ return (void*) syscall(__NR_mmap2, addr, len, prot, flags, fd, offset);
+#endif
+}
+
+#define KBASE_REG_COOKIE_TB 2
+#define KBASE_REG_COOKIE_MTP 3
+
+/*
+ * Returns true if the device is immune to CVE-2014-1710,
+ * false if the device is vulnerable.
+ */
+static jboolean android_security_cts_NativeCodeTest_doCVE20141710Test(JNIEnv*, jobject)
+{
+ jboolean result = false;
+ int fd = open("/dev/mali0", O_RDWR);
+ if (fd < 0) {
+ return true; /* not vulnerable */
+ }
+
+ void* a = mmap_syscall(NULL, 0x1000, PROT_READ, MAP_SHARED, fd, KBASE_REG_COOKIE_MTP);
+ void* b = mmap_syscall(NULL, 0x1000, PROT_READ, MAP_SHARED, fd, KBASE_REG_COOKIE_TB);
+
+ if (a == MAP_FAILED) {
+ result = true; /* assume not vulnerable */
+ goto done;
+ }
+
+ if (b == MAP_FAILED) {
+ result = true; /* assume not vulnerable */
+ goto done;
+ }
+
+ /* mprotect should return an error if not vulnerable */
+ result = (mprotect(b, 0x1000, PROT_READ | PROT_WRITE) == -1);
+
+ done:
+ if (a != MAP_FAILED) {
+ munmap(a, 0x1000);
+ }
+ if (b != MAP_FAILED) {
+ munmap(b, 0x1000);
+ }
+ close(fd);
+ return result;
+}
+
+
static JNINativeMethod gMethods[] = {
{ "doPerfEventTest", "()Z",
(void *) android_security_cts_NativeCodeTest_doPerfEventTest },
@@ -247,6 +303,8 @@
(void *) android_security_cts_NativeCodeTest_doSockDiagTest },
{ "doVrootTest", "()Z",
(void *) android_security_cts_NativeCodeTest_doVrootTest },
+ { "doCVE20141710Test", "()Z",
+ (void *) android_security_cts_NativeCodeTest_doCVE20141710Test },
};
int register_android_security_cts_NativeCodeTest(JNIEnv* env)
diff --git a/tests/tests/security/jni/android_security_cts_SeccompDeathTestService.cpp b/tests/tests/security/jni/android_security_cts_SeccompDeathTestService.cpp
new file mode 100644
index 0000000..eb32521
--- /dev/null
+++ b/tests/tests/security/jni/android_security_cts_SeccompDeathTestService.cpp
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <jni.h>
+#include <signal.h>
+#include <unistd.h>
+
+void android_security_cts_SeccompDeathTestService_testSigSysSelf(JNIEnv* env, jobject thiz)
+{
+ kill(getpid(), SIGSYS);
+}
+
+static JNINativeMethod methods[] = {
+ { "testSigSysSelf", "()V",
+ (void *)android_security_cts_SeccompDeathTestService_testSigSysSelf }
+};
+
+int register_android_security_cts_SeccompDeathTestService(JNIEnv* env) {
+ jclass clazz = env->FindClass("android/security/cts/SeccompDeathTestService");
+ return env->RegisterNatives(clazz, methods, sizeof(methods) / sizeof(JNINativeMethod));
+}
diff --git a/tests/tests/security/src/android/security/cts/KernelSettingsTest.java b/tests/tests/security/src/android/security/cts/KernelSettingsTest.java
index 51be6be..fc76027 100644
--- a/tests/tests/security/src/android/security/cts/KernelSettingsTest.java
+++ b/tests/tests/security/src/android/security/cts/KernelSettingsTest.java
@@ -30,6 +30,10 @@
*/
public class KernelSettingsTest extends TestCase {
+ static {
+ System.loadLibrary("ctssecurity_jni");
+ }
+
/**
* Ensure that SELinux is in enforcing mode.
*/
@@ -111,6 +115,23 @@
new File("/proc/config.gz").exists());
}
+ /**
+ * Verify that ext4 extended attributes (xattrs) are enabled in the
+ * Linux kernel.
+ *
+ * To fix this failure, you need to enable the following kernel options:
+ * - CONFIG_EXT4_FS_XATTR
+ * - CONFIG_EXT4_FS_SECURITY
+ *
+ * Failure to enable this option may result in upgrade problems when
+ * trying to upgrade to Android 4.4.
+ */
+ public void testXattrInKernel() {
+ assertTrue(supportsXattr());
+ }
+
+ private static native boolean supportsXattr();
+
private String getFile(String filename) throws IOException {
BufferedReader in = null;
try {
diff --git a/tests/tests/security/src/android/security/cts/NativeCodeTest.java b/tests/tests/security/src/android/security/cts/NativeCodeTest.java
index 30bf834..5e3ffa4 100644
--- a/tests/tests/security/src/android/security/cts/NativeCodeTest.java
+++ b/tests/tests/security/src/android/security/cts/NativeCodeTest.java
@@ -86,4 +86,14 @@
* https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/arch/arm/include/asm/uaccess.h?id=8404663f81d212918ff85f493649a7991209fa04
*/
private static native boolean doVrootTest();
+
+ public void testCVE20141710() throws Exception {
+ assertTrue("Device is vulnerable to CVE-2014-1710", doCVE20141710Test());
+ }
+
+ /**
+ * Returns true if the device is immune to CVE-2014-1710,
+ * false if the device is vulnerable.
+ */
+ private static native boolean doCVE20141710Test();
}
diff --git a/tests/tests/security/src/android/security/cts/SELinuxDomainTest.java b/tests/tests/security/src/android/security/cts/SELinuxDomainTest.java
index a227109..5aea166 100644
--- a/tests/tests/security/src/android/security/cts/SELinuxDomainTest.java
+++ b/tests/tests/security/src/android/security/cts/SELinuxDomainTest.java
@@ -45,7 +45,9 @@
*/
private void assertDomainEmpty(String domain) throws FileNotFoundException {
List<ProcessDetails> procs = ProcessDetails.getProcessMap().get(domain);
- assertNull(procs);
+ String msg = "Expected no processes in SELinux domain \"" + domain + "\""
+ + " Found: \"" + procs + "\"";
+ assertNull(msg, procs);
}
/**
@@ -59,9 +61,14 @@
*/
private void assertDomainOne(String domain, String executable) throws FileNotFoundException {
List<ProcessDetails> procs = ProcessDetails.getProcessMap().get(domain);
- assertNotNull(procs);
- assertEquals(1, procs.size());
- assertEquals(executable, procs.get(0).procTitle);
+ String msg = "Expected 1 process in SELinux domain \"" + domain + "\""
+ + " Found \"" + procs + "\"";
+ assertNotNull(msg, procs);
+ assertEquals(msg, 1, procs.size());
+
+ msg = "Expected executable \"" + executable + "\" in SELinux domain \"" + domain + "\""
+ + "Found: \"" + procs + "\"";
+ assertEquals(msg, executable, procs.get(0).procTitle);
}
/**
@@ -81,8 +88,37 @@
/* not on all devices */
return;
}
- assertEquals(1, procs.size());
- assertEquals(executable, procs.get(0).procTitle);
+
+ String msg = "Expected 1 process in SELinux domain \"" + domain + "\""
+ + " Found: \"" + procs + "\"";
+ assertEquals(msg, 1, procs.size());
+
+ msg = "Expected executable \"" + executable + "\" in SELinux domain \"" + domain + "\""
+ + "Found: \"" + procs.get(0) + "\"";
+ assertEquals(msg, executable, procs.get(0).procTitle);
+ }
+
+ /**
+ * Asserts that a domain must exist, and that the cardinality is greater
+ * than or equal to 1.
+ *
+ * @param domain
+ * The domain or SELinux context to check.
+ * @param executable
+ * The path of the executable or application package name.
+ */
+ private void assertDomainN(String domain, String executable)
+ throws FileNotFoundException {
+ List<ProcessDetails> procs = ProcessDetails.getProcessMap().get(domain);
+ String msg = "Expected 1 or more processes in SELinux domain \"" + domain + "\""
+ + " Found \"" + procs + "\"";
+ assertNotNull(msg, procs);
+
+ for(ProcessDetails p : procs) {
+ msg = "Expected executable \"" + executable + "\" in SELinux domain \"" + domain + "\""
+ + "Found: \"" + p + "\"";
+ assertEquals(msg, executable, p.procTitle);
+ }
}
/* Init is always there */
@@ -137,7 +173,7 @@
/* Media server is always running */
public void testMediaserverDomain() throws FileNotFoundException {
- assertDomainOne("u:r:mediaserver:s0", "/system/bin/mediaserver");
+ assertDomainN("u:r:mediaserver:s0", "/system/bin/mediaserver");
}
/* Installd is always running */
@@ -203,7 +239,7 @@
List<ProcessDetails> procs = ProcessDetails.getProcessMap().get(domain);
assertNotNull(procs);
for (ProcessDetails p : procs) {
- assertTrue("Non Kernel thread \"" + p.procTitle + "\" found!", p.isKernel());
+ assertTrue("Non Kernel thread \"" + p + "\" found!", p.isKernel());
}
}
diff --git a/tests/tests/security/src/android/security/cts/SeccompBpfTest.java b/tests/tests/security/src/android/security/cts/SeccompBpfTest.java
new file mode 100644
index 0000000..1f13b78
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/SeccompBpfTest.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.test.AndroidTestCase;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * Test for seccomp-bpf sandboxing technology. This makes use of the
+ * SeccompDeathTestService to run sandboxing death tests out-of-process.
+ */
+public class SeccompBpfTest extends AndroidTestCase implements ServiceConnection,
+ IBinder.DeathRecipient {
+ static final String TAG = "SeccompBpfTest";
+
+ /**
+ * Message sent from the SeccompDeathTestService before it runs a test.
+ */
+ static final int MSG_TEST_STARTED = 1;
+ /**
+ * Message sent from the SeccompDeathTestService after a test exits cleanly.
+ */
+ static final int MSG_TEST_ENDED_CLEAN = 2;
+
+ final private Messenger mMessenger = new Messenger(new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_TEST_STARTED:
+ onTestStarted();
+ break;
+ case MSG_TEST_ENDED_CLEAN:
+ onTestEnded(false);
+ break;
+ default:
+ super.handleMessage(msg);
+ }
+ }
+ });
+
+ /**
+ * Condition that blocks the test/instrumentation thread that runs the
+ * test cases, while the SeccompDeathTestService runs the test out-of-process.
+ */
+ final private ConditionVariable mCondition = new ConditionVariable();
+
+ /**
+ * The SeccompDeathTestService number to run.
+ */
+ private int mTestNumber = -1;
+
+ /**
+ * If the test has started.
+ */
+ private boolean mTestStarted = false;
+ /**
+ * If the test ended (either cleanly or with death).
+ */
+ private boolean mTestEnded = false;
+ /**
+ * If the test ended cleanly or died.
+ */
+ private boolean mTestDied = false;
+
+ public void testDeathTest() {
+ runDeathTest(SeccompDeathTestService.TEST_DEATH_TEST);
+ assertTrue(mTestDied);
+ }
+
+ public void testCleanTest() {
+ runDeathTest(SeccompDeathTestService.TEST_CLEAN_TEST);
+ assertFalse(mTestDied);
+ }
+
+ public void testSigSysSelf() {
+ runDeathTest(SeccompDeathTestService.TEST_SIGSYS_SELF);
+ assertTrue(mTestDied);
+ }
+
+ /**
+ * Runs a death test by its test number, which needs to match a value in
+ * SeccompDeathTestService.
+ *
+ * This blocks until the completion of the test, after which the test body
+ * can use mTestEnded/mTestDied to see if the test died.
+ */
+ public void runDeathTest(final int testNumber) {
+ mTestStarted = false;
+ mTestEnded = false;
+ mTestDied = false;
+
+ mTestNumber = testNumber;
+
+ Log.d(TAG, "Starting runDeathTest");
+ launchDeathTestService();
+ mCondition.block();
+
+ assertTrue(mTestStarted);
+ assertTrue(mTestEnded);
+ }
+
+ private void launchDeathTestService() {
+ Log.d(TAG, "launchDeathTestService");
+ mCondition.close();
+
+ Intent intent = new Intent();
+ intent.setComponent(new ComponentName("com.android.cts.security", "android.security.cts.SeccompDeathTestService"));
+
+ if (!getContext().bindService(intent, this, Context.BIND_AUTO_CREATE)) {
+ mCondition.open();
+ fail("Failed to start DeathTestService");
+ }
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ Log.d(TAG, "onServiceConnected");
+
+ Messenger remoteMessenger = new Messenger(service);
+ Message msg = Message.obtain(null, SeccompDeathTestService.MSG_RUN_TEST);
+ msg.getData().putBinder(SeccompDeathTestService.REPLY_BINDER_NAME, mMessenger.getBinder());
+ msg.getData().putInt(SeccompDeathTestService.RUN_TEST_IDENTIFIER, mTestNumber);
+
+ try {
+ service.linkToDeath(this, 0);
+ remoteMessenger.send(msg);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error setting up SeccompDeathTestService: " + e.getMessage());
+ }
+ Log.d(TAG, "Send MSG_TEST_START");
+ }
+
+ private void onTestStarted() {
+ Log.d(TAG, "onTestStarted");
+ mTestStarted = true;
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ Log.d(TAG, "onServiceDisconnected");
+ }
+
+ @Override
+ public void binderDied() {
+ Log.d(TAG, "binderDied");
+ if (mTestEnded)
+ return;
+ onTestEnded(true);
+ }
+
+ private void onTestEnded(boolean died) {
+ Log.d(TAG, "onTestEnded, died=" + died);
+ mTestEnded = true;
+ mTestDied = died;
+ getContext().unbindService(this);
+ mCondition.open();
+ }
+}
diff --git a/tests/tests/security/src/android/security/cts/SeccompDeathTestService.java b/tests/tests/security/src/android/security/cts/SeccompDeathTestService.java
new file mode 100644
index 0000000..c78ea35
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/SeccompDeathTestService.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * Service used to run tests for seccomp-bpf sandboxing. Since sandbox violations
+ * result in process termination, they cannot be run from within the test case
+ * itself. The SeccompBpfTest starts this service to run code out-of-process and
+ * then observes when the Binder channel dies. If the test does not die, the
+ * service reports back to the test that it exited cleanly.
+ */
+public class SeccompDeathTestService extends Service {
+ static final String TAG = SeccompBpfTest.TAG;
+
+ static {
+ System.loadLibrary("ctssecurity_jni");
+ }
+
+ /**
+ * Message sent from SeccompBpfTest to run a test.
+ */
+ final static int MSG_RUN_TEST = 100;
+ /**
+ * In MSG_RUN_TEST, the test number to run.
+ */
+ final static String RUN_TEST_IDENTIFIER = "android.security.cts.SeccompDeathTestService.testID";
+ /**
+ * In MSG_RUN_TEST, the Binder on which to report clean death.
+ */
+ static final String REPLY_BINDER_NAME = "android.security.cts.SeccompBpfTest";
+
+ // Test numbers that map to test methods in this service.
+ final static int TEST_DEATH_TEST = 1;
+ final static int TEST_CLEAN_TEST = 2;
+ final static int TEST_SIGSYS_SELF = 3;
+
+ final private Messenger mMessenger = new Messenger(new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_RUN_TEST:
+ runTest(msg);
+ break;
+ default:
+ super.handleMessage(msg);
+ }
+ }
+ });
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ Log.d(TAG, "onBind");
+ return mMessenger.getBinder();
+ }
+
+ private void runTest(Message msg) {
+ Log.d(TAG, "runTest");
+ IBinder harnessBinder = msg.getData().getBinder(REPLY_BINDER_NAME);
+ Messenger harness = new Messenger(harnessBinder);
+
+ try {
+ Log.d(TAG, "Send MSG_TEST_STARTED");
+ harness.send(Message.obtain(null, SeccompBpfTest.MSG_TEST_STARTED));
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to MSG_TEST_STARTED: " + e.getMessage());
+ }
+
+ demuxTest(msg.getData().getInt(RUN_TEST_IDENTIFIER));
+
+ try {
+ Log.d(TAG, "Send MSG_TEST_ENDED_CLEAN");
+ harness.send(Message.obtain(null, SeccompBpfTest.MSG_TEST_ENDED_CLEAN));
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to MSG_TEST_ENDED_CLEAN: " + e.getMessage());
+ }
+ }
+
+ private void demuxTest(int testNumber) {
+ switch (testNumber) {
+ case TEST_DEATH_TEST:
+ testDeath();
+ break;
+ case TEST_CLEAN_TEST:
+ break;
+ case TEST_SIGSYS_SELF:
+ testSigSysSelf();
+ break;
+ default:
+ throw new RuntimeException("Unknown test number " + testNumber);
+ }
+ }
+
+ public void testDeath() {
+ String s = null;
+ s.hashCode();
+ }
+
+ public native void testSigSysSelf();
+}
diff --git a/tests/tests/security/src/android/security/cts/activity/ISecureRandomService.aidl b/tests/tests/security/src/android/security/cts/activity/ISecureRandomService.aidl
deleted file mode 100644
index af264c9..0000000
--- a/tests/tests/security/src/android/security/cts/activity/ISecureRandomService.aidl
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.security.cts.activity;
-
-interface ISecureRandomService {
- int getRandomBytesAndPid(inout byte[] randomBytes);
-}
diff --git a/tests/tests/security/src/android/security/cts/activity/SecureRandomService.java b/tests/tests/security/src/android/security/cts/activity/SecureRandomService.java
deleted file mode 100644
index 2d425b3..0000000
--- a/tests/tests/security/src/android/security/cts/activity/SecureRandomService.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.security.cts.activity;
-
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-import android.security.cts.activity.ISecureRandomService;
-
-import android.app.Service;
-import android.content.Intent;
-
-import java.security.SecureRandom;
-
-public class SecureRandomService extends Service {
- /**
- * This helps the process shut down a little faster and get us a new
- * PID earlier than calling stopService.
- */
- private Handler mShutdownHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- stopSelf();
- }
- };
-
- private final ISecureRandomService.Stub mBinder = new ISecureRandomService.Stub() {
-
- /**
- * Returns output from SecureRandom and the current process PID. Note
- * that this should only be called once. To ensure that it's only called
- * once, this will throw an error if it's called twice in a row.
- */
- public int getRandomBytesAndPid(byte[] randomBytes) {
- mShutdownHandler.sendEmptyMessage(-1);
-
- SecureRandom sr = new SecureRandom();
- sr.nextBytes(randomBytes);
- return android.os.Process.myPid();
- }
- };
-
- @Override
- public IBinder onBind(Intent intent) {
- return mBinder;
- }
-}
diff --git a/tests/tests/view/src/android/view/cts/ViewTest.java b/tests/tests/view/src/android/view/cts/ViewTest.java
index 2fd939c..95a365f 100644
--- a/tests/tests/view/src/android/view/cts/ViewTest.java
+++ b/tests/tests/view/src/android/view/cts/ViewTest.java
@@ -48,11 +48,13 @@
import android.view.ContextMenu.ContextMenuInfo;
import android.view.Display;
import android.view.HapticFeedbackConstants;
+import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.SoundEffectConstants;
import android.view.TouchDelegate;
import android.view.View;
+import android.view.MotionEvent.PointerProperties;
import android.view.View.BaseSavedState;
import android.view.View.OnClickListener;
import android.view.View.OnCreateContextMenuListener;
@@ -3285,6 +3287,57 @@
});
}
+ public void testFilterTouchesWhenObscured() throws Throwable {
+ OnTouchListenerImpl touchListener = new OnTouchListenerImpl();
+ View view = new View(mActivity);
+ view.setOnTouchListener(touchListener);
+
+ MotionEvent.PointerProperties[] props = new MotionEvent.PointerProperties[] {
+ new MotionEvent.PointerProperties()
+ };
+ MotionEvent.PointerCoords[] coords = new MotionEvent.PointerCoords[] {
+ new MotionEvent.PointerCoords()
+ };
+ MotionEvent obscuredTouch = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN,
+ 1, props, coords, 0, 0, 0, 0, -1, 0, InputDevice.SOURCE_TOUCHSCREEN,
+ MotionEvent.FLAG_WINDOW_IS_OBSCURED);
+ MotionEvent unobscuredTouch = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN,
+ 1, props, coords, 0, 0, 0, 0, -1, 0, InputDevice.SOURCE_TOUCHSCREEN,
+ 0);
+
+ // Initially filter touches is false so all touches are dispatched.
+ assertFalse(view.getFilterTouchesWhenObscured());
+
+ view.dispatchTouchEvent(unobscuredTouch);
+ assertTrue(touchListener.hasOnTouch());
+ touchListener.reset();
+ view.dispatchTouchEvent(obscuredTouch);
+ assertTrue(touchListener.hasOnTouch());
+ touchListener.reset();
+
+ // Set filter touches to true so only unobscured touches are dispatched.
+ view.setFilterTouchesWhenObscured(true);
+ assertTrue(view.getFilterTouchesWhenObscured());
+
+ view.dispatchTouchEvent(unobscuredTouch);
+ assertTrue(touchListener.hasOnTouch());
+ touchListener.reset();
+ view.dispatchTouchEvent(obscuredTouch);
+ assertFalse(touchListener.hasOnTouch());
+ touchListener.reset();
+
+ // Set filter touches to false so all touches are dispatched.
+ view.setFilterTouchesWhenObscured(false);
+ assertFalse(view.getFilterTouchesWhenObscured());
+
+ view.dispatchTouchEvent(unobscuredTouch);
+ assertTrue(touchListener.hasOnTouch());
+ touchListener.reset();
+ view.dispatchTouchEvent(obscuredTouch);
+ assertTrue(touchListener.hasOnTouch());
+ touchListener.reset();
+ }
+
private static class MockEditText extends EditText {
private boolean mCalledCheckInputConnectionProxy = false;
private boolean mCalledOnCreateInputConnection = false;
diff --git a/tests/tests/view/src/android/view/inputmethod/cts/InputMethodInfoTest.java b/tests/tests/view/src/android/view/inputmethod/cts/InputMethodInfoTest.java
index 9f92d23..b4d65a5 100644
--- a/tests/tests/view/src/android/view/inputmethod/cts/InputMethodInfoTest.java
+++ b/tests/tests/view/src/android/view/inputmethod/cts/InputMethodInfoTest.java
@@ -164,6 +164,7 @@
mInputMethodInfo.writeToParcel(p, 0);
p.setDataPosition(0);
final InputMethodInfo imi = InputMethodInfo.CREATOR.createFromParcel(p);
+ p.recycle();
assertEquals(mInputMethodInfo.getPackageName(), imi.getPackageName());
assertEquals(mInputMethodInfo.getServiceName(), imi.getServiceName());
@@ -178,6 +179,7 @@
mInputMethodSubtype.writeToParcel(p, 0);
p.setDataPosition(0);
final InputMethodSubtype subtype = InputMethodSubtype.CREATOR.createFromParcel(p);
+ p.recycle();
assertEquals(mInputMethodSubtype.containsExtraValueKey(mSubtypeExtraValue_key),
subtype.containsExtraValueKey(mSubtypeExtraValue_key));
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java
index 458d5be..9aca8c7 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java
@@ -284,7 +284,14 @@
String url1 = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
mOnUiThread.loadUrlAndWaitForCompletion(url1);
- mOnUiThread.zoomIn();
+ new PollingCheck(TEST_TIMEOUT) {
+ @Override
+ protected boolean check() {
+ return mOnUiThread.canZoomIn();
+ }
+ }.run();
+
+ assertTrue(mOnUiThread.zoomIn());
new PollingCheck(TEST_TIMEOUT) {
@Override
protected boolean check() {
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
index fa1fa0a..6ca37cd 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
@@ -236,6 +236,7 @@
mWebServer = new CtsTestServer(getActivity());
mOnUiThread.loadUrlAndWaitForCompletion(
mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL));
+ pollingCheckForCanZoomIn();
WebSettings settings = mOnUiThread.getSettings();
settings.setSupportZoom(false);
@@ -1629,8 +1630,9 @@
assertFalse(webViewClient.onScaleChangedCalled());
String url1 = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
mOnUiThread.loadUrlAndWaitForCompletion(url1);
+ pollingCheckForCanZoomIn();
- mOnUiThread.zoomIn();
+ assertTrue(mOnUiThread.zoomIn());
webViewClient.waitForScaleChanged();
}
@@ -1915,9 +1917,13 @@
// the WebView will load the new URL.
mOnUiThread.setDownloadListener(listener);
mOnUiThread.getSettings().setJavaScriptEnabled(true);
+
+ // See b/13675265 for discussion on why the setTimeout is necessary.
+ // Works around a Blink bug.
mOnUiThread.loadDataAndWaitForCompletion(
- "<html><body onload=\"window.location = \'" + url + "\'\"></body></html>",
- "text/html", null);
+ "<html><body onload=\"setTimeout(" +
+ "function() { window.location = \'" + url + "\'; }, 100);\">" +
+ "</body></html>", "text/html", null);
// Wait for layout to complete before setting focus.
getInstrumentation().waitForIdleSync();
@@ -2341,6 +2347,15 @@
}
}
+ private void pollingCheckForCanZoomIn() {
+ new PollingCheck(TEST_TIMEOUT) {
+ @Override
+ protected boolean check() {
+ return mOnUiThread.canZoomIn();
+ }
+ }.run();
+ }
+
final class ScaleChangedWebViewClient extends WaitForLoadedClient {
private boolean mOnScaleChangedCalled = false;
public ScaleChangedWebViewClient() {
diff --git a/tests/tests/widget/src/android/widget/cts/TextViewTest.java b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
index 9e03f84..36ace30 100644
--- a/tests/tests/widget/src/android/widget/cts/TextViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
@@ -2833,6 +2833,17 @@
}
@UiThreadTest
+ public void testSetTextLong() {
+ final int MAX_COUNT = 1 << 21;
+ char[] longText = new char[MAX_COUNT];
+ for (int n = 0; n < MAX_COUNT; n++) {
+ longText[n] = 'm';
+ }
+ mTextView = findTextView(R.id.textview_text);
+ mTextView.setText(new String(longText));
+ }
+
+ @UiThreadTest
public void testSetExtractedText() {
mTextView = findTextView(R.id.textview_text);
assertEquals(mActivity.getResources().getString(R.string.text_view_hello),
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 1cc86ca..06951b9 100644
--- a/tools/cts-java-scanner/src/com/android/cts/javascanner/DocletRunner.java
+++ b/tools/cts-java-scanner/src/com/android/cts/javascanner/DocletRunner.java
@@ -77,7 +77,6 @@
sourcePath.add("./cts/tests/src");
sourcePath.add("./cts/libs/commonutil/src");
sourcePath.add("./cts/libs/deviceutil/src");
- sourcePath.add("./frameworks/uiautomator/src");
sourcePath.add(sourceDir.toString());
return join(sourcePath, ":");
}
@@ -85,6 +84,7 @@
private String getClassPath() {
List<String> classPath = new ArrayList<String>();
classPath.add("./prebuilts/misc/common/tradefed/tradefed-prebuilt.jar");
+ classPath.add("./prebuilts/misc/common/ub-uiautomator/ub-uiautomator.jar");
return join(classPath, ":");
}
diff --git a/tools/tradefed-host/.classpath b/tools/tradefed-host/.classpath
index 59f6deb..b82e340 100644
--- a/tools/tradefed-host/.classpath
+++ b/tools/tradefed-host/.classpath
@@ -7,6 +7,6 @@
<classpathentry exported="true" kind="var" path="CTS_SRC_ROOT/out/host/common/obj/JAVA_LIBRARIES/ctsdeviceinfolib_intermediates/javalib.jar"/>
<classpathentry exported="true" kind="var" path="CTS_SRC_ROOT/out/host/common/obj/JAVA_LIBRARIES/hosttestlib_intermediates/javalib.jar"/>
<classpathentry kind="var" path="CTS_SRC_ROOT/out/host/common/obj/JAVA_LIBRARIES/ddmlib-prebuilt_intermediates/ddmlib-prebuilt.jar" sourcepath="/SDK_SRC_ROOT"/>
- <classpathentry kind="var" path="CTS_SRC_ROOT/out/host/common/obj/JAVA_LIBRARIES/tradefed-prebuilt_intermediates/tradefed-prebuilt.jar" sourcepath="/TRADEFED_ROOT/tools/tradefederation/src"/>
+ <classpathentry kind="var" path="CTS_SRC_ROOT/out/host/common/obj/JAVA_LIBRARIES/tradefed-prebuilt_intermediates/tradefed-prebuilt.jar" sourcepath="/TRADEFED_ROOT/tools/tradefederation/src"/>
<classpathentry kind="output" path="bin"/>
</classpath>
diff --git a/tools/tradefed-host/res/config/cts.xml b/tools/tradefed-host/res/config/cts.xml
index 158f49d..416b400 100644
--- a/tools/tradefed-host/res/config/cts.xml
+++ b/tools/tradefed-host/res/config/cts.xml
@@ -22,6 +22,7 @@
<test class="com.android.cts.tradefed.testtype.CtsTest" />
<logger class="com.android.tradefed.log.FileLogger" />
<result_reporter class="com.android.cts.tradefed.result.CtsXmlResultReporter" />
+ <result_reporter class="com.android.cts.tradefed.result.CtsTestLogReporter" />
<result_reporter class="com.android.cts.tradefed.result.IssueReporter" />
</configuration>
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsTestLogReporter.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsTestLogReporter.java
new file mode 100644
index 0000000..bbdcb05
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsTestLogReporter.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.tradefed.result;
+
+import com.android.cts.tradefed.device.DeviceInfoCollector;
+import com.android.ddmlib.Log;
+import com.android.ddmlib.Log.LogLevel;
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.config.OptionCopier;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.IShardableListener;
+import com.android.tradefed.result.StubTestInvocationListener;
+
+import java.util.Map;
+
+/**
+ * Dumps tests in progress to stdout
+ */
+public class CtsTestLogReporter extends StubTestInvocationListener implements IShardableListener {
+
+ @Option(name = "quiet-output", description = "Mute display of test results.")
+ private boolean mQuietOutput = false;
+
+ protected IBuildInfo mBuildInfo;
+ private String mDeviceSerial;
+ private TestResults mResults = new TestResults();
+ private TestPackageResult mCurrentPkgResult = null;
+ private boolean mIsDeviceInfoRun = false;
+
+ @Override
+ public void invocationStarted(IBuildInfo buildInfo) {
+ mDeviceSerial = buildInfo.getDeviceSerial() == null ? "unknown_device" : buildInfo.getDeviceSerial();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void testRunStarted(String name, int numTests) {
+ if (mCurrentPkgResult != null && !name.equals(mCurrentPkgResult.getAppPackageName())) {
+ // display results from previous run
+ logCompleteRun(mCurrentPkgResult);
+ }
+ mIsDeviceInfoRun = name.equals(DeviceInfoCollector.APP_PACKAGE_NAME);
+ if (mIsDeviceInfoRun) {
+ logResult("Collecting device info");
+ } else {
+ if (mCurrentPkgResult == null || !name.equals(mCurrentPkgResult.getAppPackageName())) {
+ logResult("-----------------------------------------");
+ logResult("Test package %s started", name);
+ logResult("-----------------------------------------");
+ }
+ mCurrentPkgResult = mResults.getOrCreatePackage(name);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void testStarted(TestIdentifier test) {
+ mCurrentPkgResult.insertTest(test);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void testFailed(TestFailure status, TestIdentifier test, String trace) {
+ mCurrentPkgResult.reportTestFailure(test, CtsTestStatus.FAIL, trace);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void testEnded(TestIdentifier test, Map<String, String> testMetrics) {
+ mCurrentPkgResult.reportTestEnded(test);
+ Test result = mCurrentPkgResult.findTest(test);
+ String stack = result.getStackTrace() == null ? "" : "\n" + result.getStackTrace();
+ logResult("%s#%s %s %s", test.getClassName(), test.getTestName(), result.getResult(),
+ stack);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void invocationEnded(long elapsedTime) {
+ // display the results of the last completed run
+ if (mCurrentPkgResult != null) {
+ logCompleteRun(mCurrentPkgResult);
+ }
+ }
+
+ private void logResult(String format, Object... args) {
+ if (mQuietOutput) {
+ CLog.i(format, args);
+ } else {
+ Log.logAndDisplay(LogLevel.INFO, mDeviceSerial, String.format(format, args));
+ }
+ }
+
+ private void logCompleteRun(TestPackageResult pkgResult) {
+ if (pkgResult.getAppPackageName().equals(DeviceInfoCollector.APP_PACKAGE_NAME)) {
+ logResult("Device info collection complete");
+ return;
+ }
+ logResult("%s package complete: Passed %d, Failed %d, Not Executed %d",
+ pkgResult.getAppPackageName(), pkgResult.countTests(CtsTestStatus.PASS),
+ pkgResult.countTests(CtsTestStatus.FAIL),
+ pkgResult.countTests(CtsTestStatus.NOT_EXECUTED));
+ }
+
+ @Override
+ public IShardableListener clone() {
+ CtsTestLogReporter clone = new CtsTestLogReporter();
+ OptionCopier.copyOptionsNoThrow(this, clone);
+ return clone;
+ }
+}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsXmlResultReporter.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsXmlResultReporter.java
index 6643934..9836b47 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
@@ -17,7 +17,6 @@
package com.android.cts.tradefed.result;
import com.android.cts.tradefed.build.CtsBuildHelper;
-import com.android.cts.tradefed.device.DeviceInfoCollector;
import com.android.cts.tradefed.testtype.CtsTest;
import com.android.cts.tradefed.util.CtsHostStore;
import com.android.ddmlib.Log;
@@ -69,6 +68,9 @@
static final String PLAN_ATTR = "testPlan";
static final String STARTTIME_ATTR = "starttime";
+ @Option(name = "quiet-output", description = "Mute display of test results.")
+ private boolean mQuietOutput = false;
+
private static final String REPORT_DIR_NAME = "output-file-path";
@Option(name=REPORT_DIR_NAME, description="root file system path to directory to store xml " +
"test results and associated logs. If not specified, results will be stored at " +
@@ -83,9 +85,6 @@
@Option(name = CtsTest.CONTINUE_OPTION, description = "the test result session to continue.")
private Integer mContinueSessionId = null;
- @Option(name = "quiet-output", description = "Mute display of test results.")
- private boolean mQuietOutput = false;
-
@Option(name = "result-server", description = "Server to publish test results.")
private String mResultServer;
@@ -220,27 +219,10 @@
return new LogFileSaver(mLogDir);
}
- /**
- * {@inheritDoc}
- */
+
@Override
public void testRunStarted(String name, int numTests) {
- if (mCurrentPkgResult != null && !name.equals(mCurrentPkgResult.getAppPackageName())) {
- // display results from previous run
- logCompleteRun(mCurrentPkgResult);
- }
- mIsDeviceInfoRun = name.equals(DeviceInfoCollector.APP_PACKAGE_NAME);
- if (mIsDeviceInfoRun) {
- logResult("Collecting device info");
- } else {
- if (mCurrentPkgResult == null || !name.equals(mCurrentPkgResult.getAppPackageName())) {
- logResult("-----------------------------------------");
- logResult("Test package %s started", name);
- logResult("-----------------------------------------");
- }
- mCurrentPkgResult = mResults.getOrCreatePackage(name);
- }
-
+ mCurrentPkgResult = mResults.getOrCreatePackage(name);
}
/**
@@ -266,10 +248,6 @@
public void testEnded(TestIdentifier test, Map<String, String> testMetrics) {
collectCtsResults(test, testMetrics);
mCurrentPkgResult.reportTestEnded(test);
- Test result = mCurrentPkgResult.findTest(test);
- String stack = result.getStackTrace() == null ? "" : "\n" + result.getStackTrace();
- logResult("%s#%s %s %s", test.getClassName(), test.getTestName(), result.getResult(),
- stack);
}
/**
@@ -314,10 +292,6 @@
*/
@Override
public void invocationEnded(long elapsedTime) {
- // display the results of the last completed run
- if (mCurrentPkgResult != null) {
- logCompleteRun(mCurrentPkgResult);
- }
if (mReportDir == null || mStartTime == null) {
// invocationStarted must have failed, abort
CLog.w("Unable to create XML report");
@@ -344,17 +318,6 @@
}
}
- private void logCompleteRun(TestPackageResult pkgResult) {
- if (pkgResult.getAppPackageName().equals(DeviceInfoCollector.APP_PACKAGE_NAME)) {
- logResult("Device info collection complete");
- return;
- }
- logResult("%s package complete: Passed %d, Failed %d, Not Executed %d",
- pkgResult.getAppPackageName(), pkgResult.countTests(CtsTestStatus.PASS),
- pkgResult.countTests(CtsTestStatus.FAIL),
- pkgResult.countTests(CtsTestStatus.NOT_EXECUTED));
- }
-
/**
* Creates a report file and populates it with the report data from the completed tests.
*/
@@ -382,7 +345,7 @@
} catch (IOException e) {
Log.e(LOG_TAG, "Failed to generate report data");
} finally {
- StreamUtil.closeStream(stream);
+ StreamUtil.close(stream);
}
}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/CtsTest.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/CtsTest.java
index 48f4773..2f35266 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/CtsTest.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/CtsTest.java
@@ -27,6 +27,7 @@
import com.android.tradefed.config.ConfigurationException;
import com.android.tradefed.config.Option;
import com.android.tradefed.config.Option.Importance;
+import com.android.tradefed.config.OptionCopier;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.device.TestDeviceOptions;
@@ -167,8 +168,8 @@
/** data structure for a {@link IRemoteTest} and its known tests */
class TestPackage {
private final IRemoteTest mTestForPackage;
- private final Collection<TestIdentifier> mKnownTests;
private final ITestPackageDef mPackageDef;
+ private final Collection<TestIdentifier> mKnownTests;
TestPackage(ITestPackageDef packageDef, IRemoteTest testForPackage,
Collection<TestIdentifier> knownTests) {
@@ -467,6 +468,12 @@
uninstallPrequisiteApks(uninstallPackages);
+ } catch (RuntimeException e) {
+ CLog.e(e);
+ throw e;
+ } catch (Error e) {
+ CLog.e(e);
+ throw e;
} finally {
filter.reportUnexecutedTests();
}
@@ -736,6 +743,8 @@
// don't create more shards than the number of tests we have!
for (int i = 0; i < mShards && i < allTests.size(); i++) {
CtsTest shard = new CtsTest();
+ OptionCopier.copyOptionsNoThrow(this, shard);
+ shard.mShards = 0;
shard.mRemainingTestPkgs = new LinkedList<TestPackage>();
shardQueue.add(shard);
}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/ITestPackageDef.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/ITestPackageDef.java
index 68d6743..0c3d9bc 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/ITestPackageDef.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/ITestPackageDef.java
@@ -39,10 +39,6 @@
* Creates a runnable {@link IRemoteTest} from info stored in this definition.
*
* @param testCaseDir {@link File} representing directory of test case data
- * @param className the test class to restrict this run to or <code>null</code> to run all tests
- * in package
- * @param methodName the optional test method to restrict this run to, or <code>null</code> to
- * run all tests in class/package
* @return a {@link IRemoteTest} with all necessary data populated to run the test or
* <code>null</code> if test could not be created
*/
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/InstrumentationApkTest.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/InstrumentationApkTest.java
index 809696a..01c3370 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/InstrumentationApkTest.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/InstrumentationApkTest.java
@@ -23,12 +23,12 @@
import com.android.tradefed.testtype.IBuildReceiver;
import com.android.tradefed.testtype.InstrumentationTest;
+import junit.framework.Assert;
+
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Collection;
-import junit.framework.Assert;
-
/**
* A {@link InstrumentationTest] that will install CTS apks before test execution,
* and uninstall on execution completion.