Merge "Add tests for DENSITY_360" into mnc-dev
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 fbedca9..bc94d2c 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java
@@ -29,15 +29,22 @@
import android.hardware.camera2.CaptureFailure;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.cts.helpers.CameraErrorCollector;
+import android.hardware.camera2.cts.helpers.StaticMetadata;
import android.hardware.camera2.params.InputConfiguration;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.cts.helpers.CameraUtils;
import android.hardware.camera2.params.MeteringRectangle;
import android.hardware.camera2.params.StreamConfigurationMap;
+import android.location.Location;
+import android.location.LocationManager;
+import android.media.ExifInterface;
import android.media.Image;
import android.media.ImageReader;
import android.media.ImageWriter;
import android.media.Image.Plane;
+import android.os.Build;
+import android.os.Environment;
import android.os.Handler;
import android.util.Log;
import android.util.Pair;
@@ -62,12 +69,15 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
+import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
/**
* A package private utility class for wrapping up the camera2 cts test common utility functions
@@ -98,6 +108,62 @@
public static final int MAX_READER_IMAGES = 5;
+ private static final int EXIF_DATETIME_LENGTH = 19;
+ private static final int EXIF_DATETIME_ERROR_MARGIN_SEC = 60;
+ private static final float EXIF_FOCAL_LENGTH_ERROR_MARGIN = 0.001f;
+ private static final float EXIF_EXPOSURE_TIME_ERROR_MARGIN_RATIO = 0.05f;
+ private static final float EXIF_EXPOSURE_TIME_MIN_ERROR_MARGIN_SEC = 0.002f;
+ private static final float EXIF_APERTURE_ERROR_MARGIN = 0.001f;
+
+ // Some exif tags that are not defined by ExifInterface but supported.
+ private static final String TAG_DATETIME_DIGITIZED = "DateTimeDigitized";
+ private static final String TAG_SUBSEC_TIME = "SubSecTime";
+ private static final String TAG_SUBSEC_TIME_ORIG = "SubSecTimeOriginal";
+ private static final String TAG_SUBSEC_TIME_DIG = "SubSecTimeDigitized";
+
+ private static final Location sTestLocation0 = new Location(LocationManager.GPS_PROVIDER);
+ private static final Location sTestLocation1 = new Location(LocationManager.GPS_PROVIDER);
+ private static final Location sTestLocation2 = new Location(LocationManager.NETWORK_PROVIDER);
+
+ protected static final String DEBUG_FILE_NAME_BASE =
+ Environment.getExternalStorageDirectory().getPath();
+
+ static {
+ sTestLocation0.setTime(1199145600L);
+ sTestLocation0.setLatitude(37.736071);
+ sTestLocation0.setLongitude(-122.441983);
+ sTestLocation0.setAltitude(21.0);
+
+ sTestLocation1.setTime(1199145601L);
+ sTestLocation1.setLatitude(0.736071);
+ sTestLocation1.setLongitude(0.441983);
+ sTestLocation1.setAltitude(1.0);
+
+ sTestLocation2.setTime(1199145602L);
+ sTestLocation2.setLatitude(-89.736071);
+ sTestLocation2.setLongitude(-179.441983);
+ sTestLocation2.setAltitude(100000.0);
+ }
+
+ // Exif test data vectors.
+ public static final ExifTestData[] EXIF_TEST_DATA = {
+ new ExifTestData(
+ /*gpsLocation*/ sTestLocation0,
+ /* orientation */90,
+ /* jpgQuality */(byte) 80,
+ /* thumbQuality */(byte) 75),
+ new ExifTestData(
+ /*gpsLocation*/ sTestLocation1,
+ /* orientation */180,
+ /* jpgQuality */(byte) 90,
+ /* thumbQuality */(byte) 85),
+ new ExifTestData(
+ /*gpsLocation*/ sTestLocation2,
+ /* orientation */270,
+ /* jpgQuality */(byte) 100,
+ /* thumbQuality */(byte) 100)
+ };
+
/**
* Create an {@link android.media.ImageReader} object and get the surface.
*
@@ -1487,4 +1553,436 @@
return true;
}
+
+ /**
+ * Set jpeg related keys in a capture request builder.
+ *
+ * @param builder The capture request builder to set the keys inl
+ * @param exifData The exif data to set.
+ * @param thumbnailSize The thumbnail size to set.
+ * @param collector The camera error collector to collect errors.
+ */
+ public static void setJpegKeys(CaptureRequest.Builder builder, ExifTestData exifData,
+ Size thumbnailSize, CameraErrorCollector collector) {
+ builder.set(CaptureRequest.JPEG_THUMBNAIL_SIZE, thumbnailSize);
+ builder.set(CaptureRequest.JPEG_GPS_LOCATION, exifData.gpsLocation);
+ builder.set(CaptureRequest.JPEG_ORIENTATION, exifData.jpegOrientation);
+ builder.set(CaptureRequest.JPEG_QUALITY, exifData.jpegQuality);
+ builder.set(CaptureRequest.JPEG_THUMBNAIL_QUALITY,
+ exifData.thumbnailQuality);
+
+ // Validate request set and get.
+ collector.expectEquals("JPEG thumbnail size request set and get should match",
+ thumbnailSize, builder.get(CaptureRequest.JPEG_THUMBNAIL_SIZE));
+ collector.expectTrue("GPS locations request set and get should match.",
+ areGpsFieldsEqual(exifData.gpsLocation,
+ builder.get(CaptureRequest.JPEG_GPS_LOCATION)));
+ collector.expectEquals("JPEG orientation request set and get should match",
+ exifData.jpegOrientation,
+ builder.get(CaptureRequest.JPEG_ORIENTATION));
+ collector.expectEquals("JPEG quality request set and get should match",
+ exifData.jpegQuality, builder.get(CaptureRequest.JPEG_QUALITY));
+ collector.expectEquals("JPEG thumbnail quality request set and get should match",
+ exifData.thumbnailQuality,
+ builder.get(CaptureRequest.JPEG_THUMBNAIL_QUALITY));
+ }
+
+ /**
+ * Simple validation of JPEG image size and format.
+ * <p>
+ * Only validate the image object sanity. It is fast, but doesn't actually
+ * check the buffer data. Assert is used here as it make no sense to
+ * continue the test if the jpeg image captured has some serious failures.
+ * </p>
+ *
+ * @param image The captured jpeg image
+ * @param expectedSize Expected capture jpeg size
+ */
+ public static void basicValidateJpegImage(Image image, Size expectedSize) {
+ Size imageSz = new Size(image.getWidth(), image.getHeight());
+ assertTrue(
+ String.format("Image size doesn't match (expected %s, actual %s) ",
+ expectedSize.toString(), imageSz.toString()), expectedSize.equals(imageSz));
+ assertEquals("Image format should be JPEG", ImageFormat.JPEG, image.getFormat());
+ assertNotNull("Image plane shouldn't be null", image.getPlanes());
+ assertEquals("Image plane number should be 1", 1, image.getPlanes().length);
+
+ // Jpeg decoding validate was done in ImageReaderTest, no need to duplicate the test here.
+ }
+
+ /**
+ * Verify the JPEG EXIF and JPEG related keys in a capture result are expected.
+ * - Capture request get values are same as were set.
+ * - capture result's exif data is the same as was set by
+ * the capture request.
+ * - new tags in the result set by the camera service are
+ * present and semantically correct.
+ *
+ * @param image The output JPEG image to verify.
+ * @param captureResult The capture result to verify.
+ * @param expectedSize The expected JPEG size.
+ * @param expectedThumbnailSize The expected thumbnail size.
+ * @param expectedExifData The expected EXIF data
+ * @param staticInfo The static metadata for the camera device.
+ * @param jpegFilename The filename to dump the jpeg to.
+ * @param collector The camera error collector to collect errors.
+ */
+ public static void verifyJpegKeys(Image image, CaptureResult captureResult, Size expectedSize,
+ Size expectedThumbnailSize, ExifTestData expectedExifData, StaticMetadata staticInfo,
+ CameraErrorCollector collector) throws Exception {
+
+ basicValidateJpegImage(image, expectedSize);
+
+ byte[] jpegBuffer = getDataFromImage(image);
+ // Have to dump into a file to be able to use ExifInterface
+ String jpegFilename = DEBUG_FILE_NAME_BASE + "/verifyJpegKeys.jpeg";
+ dumpFile(jpegFilename, jpegBuffer);
+ ExifInterface exif = new ExifInterface(jpegFilename);
+
+ if (expectedThumbnailSize.equals(new Size(0,0))) {
+ collector.expectTrue("Jpeg shouldn't have thumbnail when thumbnail size is (0, 0)",
+ !exif.hasThumbnail());
+ } else {
+ collector.expectTrue("Jpeg must have thumbnail for thumbnail size " +
+ expectedThumbnailSize, exif.hasThumbnail());
+ }
+
+ // Validate capture result vs. request
+ Size resultThumbnailSize = captureResult.get(CaptureResult.JPEG_THUMBNAIL_SIZE);
+ int orientationTested = expectedExifData.jpegOrientation;
+ if ((orientationTested == 90 || orientationTested == 270)) {
+ int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,
+ /*defaultValue*/-1);
+ if (exifOrientation == ExifInterface.ORIENTATION_UNDEFINED) {
+ // Device physically rotated image+thumbnail data
+ // Expect thumbnail size to be also rotated
+ resultThumbnailSize = new Size(resultThumbnailSize.getHeight(),
+ resultThumbnailSize.getWidth());
+ }
+ }
+
+ collector.expectEquals("JPEG thumbnail size result and request should match",
+ expectedThumbnailSize, resultThumbnailSize);
+ if (collector.expectKeyValueNotNull(captureResult, CaptureResult.JPEG_GPS_LOCATION) !=
+ null) {
+ collector.expectTrue("GPS location result and request should match.",
+ areGpsFieldsEqual(expectedExifData.gpsLocation,
+ captureResult.get(CaptureResult.JPEG_GPS_LOCATION)));
+ }
+ collector.expectEquals("JPEG orientation result and request should match",
+ expectedExifData.jpegOrientation,
+ captureResult.get(CaptureResult.JPEG_ORIENTATION));
+ collector.expectEquals("JPEG quality result and request should match",
+ expectedExifData.jpegQuality, captureResult.get(CaptureResult.JPEG_QUALITY));
+ collector.expectEquals("JPEG thumbnail quality result and request should match",
+ expectedExifData.thumbnailQuality,
+ captureResult.get(CaptureResult.JPEG_THUMBNAIL_QUALITY));
+
+ // Validate other exif tags for all non-legacy devices
+ if (!staticInfo.isHardwareLevelLegacy()) {
+ verifyJpegExifExtraTags(exif, expectedSize, captureResult, staticInfo, collector);
+ }
+ }
+
+ /**
+ * Get the degree of an EXIF orientation.
+ */
+ private static int getExifOrientationInDegree(int exifOrientation,
+ CameraErrorCollector collector) {
+ switch (exifOrientation) {
+ case ExifInterface.ORIENTATION_NORMAL:
+ return 0;
+ case ExifInterface.ORIENTATION_ROTATE_90:
+ return 90;
+ case ExifInterface.ORIENTATION_ROTATE_180:
+ return 180;
+ case ExifInterface.ORIENTATION_ROTATE_270:
+ return 270;
+ default:
+ collector.addMessage("It is impossible to get non 0, 90, 180, 270 degress exif" +
+ "info based on the request orientation range");
+ return 0;
+ }
+ }
+
+ /**
+ * Validate and return the focal length.
+ *
+ * @param result Capture result to get the focal length
+ * @return Focal length from capture result or -1 if focal length is not available.
+ */
+ private static float validateFocalLength(CaptureResult result, StaticMetadata staticInfo,
+ CameraErrorCollector collector) {
+ float[] focalLengths = staticInfo.getAvailableFocalLengthsChecked();
+ Float resultFocalLength = result.get(CaptureResult.LENS_FOCAL_LENGTH);
+ if (collector.expectTrue("Focal length is invalid",
+ resultFocalLength != null && resultFocalLength > 0)) {
+ List<Float> focalLengthList =
+ Arrays.asList(CameraTestUtils.toObject(focalLengths));
+ collector.expectTrue("Focal length should be one of the available focal length",
+ focalLengthList.contains(resultFocalLength));
+ return resultFocalLength;
+ }
+ return -1;
+ }
+
+ /**
+ * Validate and return the aperture.
+ *
+ * @param result Capture result to get the aperture
+ * @return Aperture from capture result or -1 if aperture is not available.
+ */
+ private static float validateAperture(CaptureResult result, StaticMetadata staticInfo,
+ CameraErrorCollector collector) {
+ float[] apertures = staticInfo.getAvailableAperturesChecked();
+ Float resultAperture = result.get(CaptureResult.LENS_APERTURE);
+ if (collector.expectTrue("Capture result aperture is invalid",
+ resultAperture != null && resultAperture > 0)) {
+ List<Float> apertureList =
+ Arrays.asList(CameraTestUtils.toObject(apertures));
+ collector.expectTrue("Aperture should be one of the available apertures",
+ apertureList.contains(resultAperture));
+ return resultAperture;
+ }
+ return -1;
+ }
+
+ /**
+ * Return the closest value in an array of floats.
+ */
+ private static float getClosestValueInArray(float[] values, float target) {
+ int minIdx = 0;
+ float minDistance = Math.abs(values[0] - target);
+ for(int i = 0; i < values.length; i++) {
+ float distance = Math.abs(values[i] - target);
+ if (minDistance > distance) {
+ minDistance = distance;
+ minIdx = i;
+ }
+ }
+
+ return values[minIdx];
+ }
+
+ /**
+ * Return if two Location's GPS field are the same.
+ */
+ private static boolean areGpsFieldsEqual(Location a, Location b) {
+ if (a == null || b == null) {
+ return false;
+ }
+
+ return a.getTime() == b.getTime() && a.getLatitude() == b.getLatitude() &&
+ a.getLongitude() == b.getLongitude() && a.getAltitude() == b.getAltitude() &&
+ a.getProvider() == b.getProvider();
+ }
+
+ /**
+ * Verify extra tags in JPEG EXIF
+ */
+ private static void verifyJpegExifExtraTags(ExifInterface exif, Size jpegSize,
+ CaptureResult result, StaticMetadata staticInfo, CameraErrorCollector collector)
+ throws ParseException {
+ /**
+ * TAG_IMAGE_WIDTH and TAG_IMAGE_LENGTH and TAG_ORIENTATION.
+ * Orientation and exif width/height need to be tested carefully, two cases:
+ *
+ * 1. Device rotate the image buffer physically, then exif width/height may not match
+ * the requested still capture size, we need swap them to check.
+ *
+ * 2. Device use the exif tag to record the image orientation, it doesn't rotate
+ * the jpeg image buffer itself. In this case, the exif width/height should always match
+ * the requested still capture size, and the exif orientation should always match the
+ * requested orientation.
+ *
+ */
+ int exifWidth = exif.getAttributeInt(ExifInterface.TAG_IMAGE_WIDTH, /*defaultValue*/0);
+ int exifHeight = exif.getAttributeInt(ExifInterface.TAG_IMAGE_LENGTH, /*defaultValue*/0);
+ Size exifSize = new Size(exifWidth, exifHeight);
+ // Orientation could be missing, which is ok, default to 0.
+ int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,
+ /*defaultValue*/-1);
+ // Get requested orientation from result, because they should be same.
+ if (collector.expectKeyValueNotNull(result, CaptureResult.JPEG_ORIENTATION) != null) {
+ int requestedOrientation = result.get(CaptureResult.JPEG_ORIENTATION);
+ final int ORIENTATION_MIN = ExifInterface.ORIENTATION_UNDEFINED;
+ final int ORIENTATION_MAX = ExifInterface.ORIENTATION_ROTATE_270;
+ boolean orientationValid = collector.expectTrue(String.format(
+ "Exif orientation must be in range of [%d, %d]",
+ ORIENTATION_MIN, ORIENTATION_MAX),
+ exifOrientation >= ORIENTATION_MIN && exifOrientation <= ORIENTATION_MAX);
+ if (orientationValid) {
+ /**
+ * Device captured image doesn't respect the requested orientation,
+ * which means it rotates the image buffer physically. Then we
+ * should swap the exif width/height accordingly to compare.
+ */
+ boolean deviceRotatedImage = exifOrientation == ExifInterface.ORIENTATION_UNDEFINED;
+
+ if (deviceRotatedImage) {
+ // Case 1.
+ boolean needSwap = (requestedOrientation % 180 == 90);
+ if (needSwap) {
+ exifSize = new Size(exifHeight, exifWidth);
+ }
+ } else {
+ // Case 2.
+ collector.expectEquals("Exif orientaiton should match requested orientation",
+ requestedOrientation, getExifOrientationInDegree(exifOrientation,
+ collector));
+ }
+ }
+ }
+
+ /**
+ * Ideally, need check exifSize == jpegSize == actual buffer size. But
+ * jpegSize == jpeg decode bounds size(from jpeg jpeg frame
+ * header, not exif) was validated in ImageReaderTest, no need to
+ * validate again here.
+ */
+ collector.expectEquals("Exif size should match jpeg capture size", jpegSize, exifSize);
+
+ // TAG_DATETIME, it should be local time
+ long currentTimeInMs = System.currentTimeMillis();
+ long currentTimeInSecond = currentTimeInMs / 1000;
+ Date date = new Date(currentTimeInMs);
+ String localDatetime = new SimpleDateFormat("yyyy:MM:dd HH:").format(date);
+ String dateTime = exif.getAttribute(ExifInterface.TAG_DATETIME);
+ if (collector.expectTrue("Exif TAG_DATETIME shouldn't be null", dateTime != null)) {
+ collector.expectTrue("Exif TAG_DATETIME is wrong",
+ dateTime.length() == EXIF_DATETIME_LENGTH);
+ long exifTimeInSecond =
+ new SimpleDateFormat("yyyy:MM:dd HH:mm:ss").parse(dateTime).getTime() / 1000;
+ long delta = currentTimeInSecond - exifTimeInSecond;
+ collector.expectTrue("Capture time deviates too much from the current time",
+ Math.abs(delta) < EXIF_DATETIME_ERROR_MARGIN_SEC);
+ // It should be local time.
+ collector.expectTrue("Exif date time should be local time",
+ dateTime.startsWith(localDatetime));
+ }
+
+ // TAG_FOCAL_LENGTH.
+ float[] focalLengths = staticInfo.getAvailableFocalLengthsChecked();
+ float exifFocalLength = (float)exif.getAttributeDouble(ExifInterface.TAG_FOCAL_LENGTH, -1);
+ collector.expectEquals("Focal length should match",
+ getClosestValueInArray(focalLengths, exifFocalLength),
+ exifFocalLength, EXIF_FOCAL_LENGTH_ERROR_MARGIN);
+ // More checks for focal length.
+ collector.expectEquals("Exif focal length should match capture result",
+ validateFocalLength(result, staticInfo, collector), exifFocalLength);
+
+ // TAG_EXPOSURE_TIME
+ // ExifInterface API gives exposure time value in the form of float instead of rational
+ String exposureTime = exif.getAttribute(ExifInterface.TAG_EXPOSURE_TIME);
+ collector.expectNotNull("Exif TAG_EXPOSURE_TIME shouldn't be null", exposureTime);
+ if (staticInfo.areKeysAvailable(CaptureResult.SENSOR_EXPOSURE_TIME)) {
+ if (exposureTime != null) {
+ double exposureTimeValue = Double.parseDouble(exposureTime);
+ long expTimeResult = result.get(CaptureResult.SENSOR_EXPOSURE_TIME);
+ double expected = expTimeResult / 1e9;
+ double tolerance = expected * EXIF_EXPOSURE_TIME_ERROR_MARGIN_RATIO;
+ tolerance = Math.max(tolerance, EXIF_EXPOSURE_TIME_MIN_ERROR_MARGIN_SEC);
+ collector.expectEquals("Exif exposure time doesn't match", expected,
+ exposureTimeValue, tolerance);
+ }
+ }
+
+ // TAG_APERTURE
+ // ExifInterface API gives aperture value in the form of float instead of rational
+ String exifAperture = exif.getAttribute(ExifInterface.TAG_APERTURE);
+ collector.expectNotNull("Exif TAG_APERTURE shouldn't be null", exifAperture);
+ if (staticInfo.areKeysAvailable(CameraCharacteristics.LENS_INFO_AVAILABLE_APERTURES)) {
+ float[] apertures = staticInfo.getAvailableAperturesChecked();
+ if (exifAperture != null) {
+ float apertureValue = Float.parseFloat(exifAperture);
+ collector.expectEquals("Aperture value should match",
+ getClosestValueInArray(apertures, apertureValue),
+ apertureValue, EXIF_APERTURE_ERROR_MARGIN);
+ // More checks for aperture.
+ collector.expectEquals("Exif aperture length should match capture result",
+ validateAperture(result, staticInfo, collector), apertureValue);
+ }
+ }
+
+ /**
+ * TAG_FLASH. TODO: For full devices, can check a lot more info
+ * (http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html#Flash)
+ */
+ String flash = exif.getAttribute(ExifInterface.TAG_FLASH);
+ collector.expectNotNull("Exif TAG_FLASH shouldn't be null", flash);
+
+ /**
+ * TAG_WHITE_BALANCE. TODO: For full devices, with the DNG tags, we
+ * should be able to cross-check android.sensor.referenceIlluminant.
+ */
+ String whiteBalance = exif.getAttribute(ExifInterface.TAG_WHITE_BALANCE);
+ collector.expectNotNull("Exif TAG_WHITE_BALANCE shouldn't be null", whiteBalance);
+
+ // TAG_MAKE
+ String make = exif.getAttribute(ExifInterface.TAG_MAKE);
+ collector.expectEquals("Exif TAG_MAKE is incorrect", Build.MANUFACTURER, make);
+
+ // TAG_MODEL
+ String model = exif.getAttribute(ExifInterface.TAG_MODEL);
+ collector.expectEquals("Exif TAG_MODEL is incorrect", Build.MODEL, model);
+
+
+ // TAG_ISO
+ int iso = exif.getAttributeInt(ExifInterface.TAG_ISO, /*defaultValue*/-1);
+ if (staticInfo.areKeysAvailable(CaptureResult.SENSOR_SENSITIVITY)) {
+ int expectedIso = result.get(CaptureResult.SENSOR_SENSITIVITY);
+ collector.expectEquals("Exif TAG_ISO is incorrect", expectedIso, iso);
+ }
+
+ // TAG_DATETIME_DIGITIZED (a.k.a Create time for digital cameras).
+ String digitizedTime = exif.getAttribute(TAG_DATETIME_DIGITIZED);
+ collector.expectNotNull("Exif TAG_DATETIME_DIGITIZED shouldn't be null", digitizedTime);
+ if (digitizedTime != null) {
+ String expectedDateTime = exif.getAttribute(ExifInterface.TAG_DATETIME);
+ collector.expectNotNull("Exif TAG_DATETIME shouldn't be null", expectedDateTime);
+ if (expectedDateTime != null) {
+ collector.expectEquals("dataTime should match digitizedTime",
+ expectedDateTime, digitizedTime);
+ }
+ }
+
+ /**
+ * TAG_SUBSEC_TIME. Since the sub second tag strings are truncated to at
+ * most 9 digits in ExifInterface implementation, use getAttributeInt to
+ * sanitize it. When the default value -1 is returned, it means that
+ * this exif tag either doesn't exist or is a non-numerical invalid
+ * string. Same rule applies to the rest of sub second tags.
+ */
+ int subSecTime = exif.getAttributeInt(TAG_SUBSEC_TIME, /*defaultValue*/-1);
+ collector.expectTrue("Exif TAG_SUBSEC_TIME value is null or invalid!", subSecTime > 0);
+
+ // TAG_SUBSEC_TIME_ORIG
+ int subSecTimeOrig = exif.getAttributeInt(TAG_SUBSEC_TIME_ORIG, /*defaultValue*/-1);
+ collector.expectTrue("Exif TAG_SUBSEC_TIME_ORIG value is null or invalid!",
+ subSecTimeOrig > 0);
+
+ // TAG_SUBSEC_TIME_DIG
+ int subSecTimeDig = exif.getAttributeInt(TAG_SUBSEC_TIME_DIG, /*defaultValue*/-1);
+ collector.expectTrue(
+ "Exif TAG_SUBSEC_TIME_DIG value is null or invalid!", subSecTimeDig > 0);
+ }
+
+
+ /**
+ * Immutable class wrapping the exif test data.
+ */
+ public static class ExifTestData {
+ public final Location gpsLocation;
+ public final int jpegOrientation;
+ public final byte jpegQuality;
+ public final byte thumbnailQuality;
+
+ public ExifTestData(Location location, int orientation,
+ byte jpgQuality, byte thumbQuality) {
+ gpsLocation = location;
+ jpegOrientation = orientation;
+ jpegQuality = jpgQuality;
+ thumbnailQuality = thumbQuality;
+ }
+ }
}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/ReprocessCaptureTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/ReprocessCaptureTest.java
index a115fbe..6a9415c 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/ReprocessCaptureTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/ReprocessCaptureTest.java
@@ -78,7 +78,8 @@
BURST,
MIXED_BURST,
ABORT_CAPTURE,
- TIMESTAMPS
+ TIMESTAMPS,
+ JPEG_EXIF
}
/**
@@ -265,13 +266,8 @@
try {
TotalCaptureResult reprocessResult;
// issue and wait on reprocess capture request
- if (mShareOneImageReader) {
- reprocessResult =
- submitCaptureRequest(mFirstImageReader.getSurface(), result);
- } else {
- reprocessResult =
- submitCaptureRequest(mSecondImageReader.getSurface(), result);
- }
+ reprocessResult = submitCaptureRequest(
+ getReprocessOutputImageReader().getSurface(), result);
fail("Camera " + id + ": should get IllegalArgumentException for cross " +
"session reprocess captrue.");
} catch (IllegalArgumentException e) {
@@ -396,6 +392,40 @@
}
/**
+ * Test reprocess jpeg output's exif data for the largest input and output sizes for each
+ * supported format.
+ */
+ public void testReprocessJpegExif() throws Exception {
+ for (String id : mCameraIds) {
+ if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) {
+ continue;
+ }
+
+ try {
+ // open Camera device
+ openDevice(id);
+
+ int[] supportedInputFormats =
+ mStaticInfo.getAvailableFormats(StaticMetadata.StreamDirection.Input);
+
+ for (int inputFormat : supportedInputFormats) {
+ int[] supportedReprocessOutputFormats =
+ mStaticInfo.getValidOutputFormatsForInput(inputFormat);
+
+ for (int reprocessOutputFormat : supportedReprocessOutputFormats) {
+ if (reprocessOutputFormat == ImageFormat.JPEG) {
+ testReprocessingMaxSizes(id, inputFormat, ImageFormat.JPEG,
+ /*previewSize*/null, CaptureTestCase.JPEG_EXIF);
+ }
+ }
+ }
+ } finally {
+ closeDevice();
+ }
+ }
+ }
+
+ /**
* Test the input format and output format with the largest input and output sizes.
*/
private void testBasicReprocessing(String cameraId, int inputFormat,
@@ -434,6 +464,9 @@
testReprocessTimestamps(cameraId, maxInputSize, inputFormat, maxReprocessOutputSize,
reprocessOutputFormat);
break;
+ case JPEG_EXIF:
+ testReprocessJpegExif(cameraId, maxInputSize, inputFormat, maxReprocessOutputSize);
+ break;
default:
throw new IllegalArgumentException("Invalid test case");
}
@@ -712,11 +745,7 @@
mImageWriter.queueInputImage(
mFirstImageReaderListener.getImage(CAPTURE_TIMEOUT_MS));
CaptureRequest.Builder builder = mCamera.createReprocessCaptureRequest(result);
- if (mShareOneImageReader) {
- builder.addTarget(mFirstImageReader.getSurface());
- } else {
- builder.addTarget(mSecondImageReader.getSurface());
- }
+ builder.addTarget(getReprocessOutputImageReader().getSurface());
reprocessRequests.add(builder.build());
}
@@ -803,11 +832,7 @@
mImageWriter.queueInputImage(
mFirstImageReaderListener.getImage(CAPTURE_TIMEOUT_MS));
CaptureRequest.Builder builder = mCamera.createReprocessCaptureRequest(result);
- if (mShareOneImageReader) {
- builder.addTarget(mFirstImageReader.getSurface());
- } else {
- builder.addTarget(mSecondImageReader.getSurface());
- }
+ builder.addTarget(getReprocessOutputImageReader().getSurface());
reprocessRequests.add(builder.build());
// Reprocess result's timestamp should match input image's timestamp.
expectedTimestamps.add(result.get(CaptureResult.SENSOR_TIMESTAMP));
@@ -835,12 +860,7 @@
"image's timestamp (" + expected + ")", expected, timestamp);
// Verify the reprocess output image timestamps match the input image's timestamps.
- Image image;
- if (mShareOneImageReader) {
- image = mFirstImageReaderListener.getImage(CAPTURE_TIMEOUT_MS);
- } else {
- image = mSecondImageReaderListener.getImage(CAPTURE_TIMEOUT_MS);
- }
+ Image image = getReprocessOutputImageReaderListener().getImage(CAPTURE_TIMEOUT_MS);
timestamp = image.getTimestamp();
image.close();
@@ -858,6 +878,64 @@
}
}
+ private void testReprocessJpegExif(String cameraId, Size inputSize, int inputFormat,
+ Size reprocessOutputSize) throws Exception {
+ if (VERBOSE) {
+ Log.v(TAG, "testReprocessJpegExif: cameraId: " + cameraId + " inputSize: " +
+ inputSize + " inputFormat: " + inputFormat + " reprocessOutputSize: " +
+ reprocessOutputSize);
+ }
+
+ Size[] thumbnailSizes = mStaticInfo.getAvailableThumbnailSizesChecked();
+ Size[] testThumbnailSizes = new Size[EXIF_TEST_DATA.length];
+ Arrays.fill(testThumbnailSizes, thumbnailSizes[thumbnailSizes.length - 1]);
+ // Make sure thumbnail size (0, 0) is covered.
+ testThumbnailSizes[0] = new Size(0, 0);
+
+ try {
+ setupImageReaders(inputSize, inputFormat, reprocessOutputSize, ImageFormat.JPEG,
+ EXIF_TEST_DATA.length);
+ setupReprocessableSession(/*previewSurface*/null, EXIF_TEST_DATA.length);
+
+ // Prepare reprocess capture requests.
+ ArrayList<CaptureRequest> reprocessRequests = new ArrayList<>(EXIF_TEST_DATA.length);
+
+ for (int i = 0; i < EXIF_TEST_DATA.length; i++) {
+ TotalCaptureResult result = submitCaptureRequest(mFirstImageReader.getSurface(),
+ /*inputResult*/null);
+ mImageWriter.queueInputImage(
+ mFirstImageReaderListener.getImage(CAPTURE_TIMEOUT_MS));
+
+ CaptureRequest.Builder builder = mCamera.createReprocessCaptureRequest(result);
+ builder.addTarget(getReprocessOutputImageReader().getSurface());
+
+ // set jpeg keys
+ setJpegKeys(builder, EXIF_TEST_DATA[i], testThumbnailSizes[i], mCollector);
+ reprocessRequests.add(builder.build());
+ }
+
+ // Submit reprocess requests.
+ SimpleCaptureCallback captureCallback = new SimpleCaptureCallback();
+ mSession.captureBurst(reprocessRequests, captureCallback, mHandler);
+
+ TotalCaptureResult[] reprocessResults =
+ captureCallback.getTotalCaptureResultsForRequests(reprocessRequests,
+ CAPTURE_TIMEOUT_FRAMES);
+
+ for (int i = 0; i < EXIF_TEST_DATA.length; i++) {
+ // Verify output image's and result's JPEG EXIF data.
+ Image image = getReprocessOutputImageReaderListener().getImage(CAPTURE_TIMEOUT_MS);
+ verifyJpegKeys(image, reprocessResults[i], reprocessOutputSize,
+ testThumbnailSizes[i], EXIF_TEST_DATA[i], mStaticInfo, mCollector);
+ image.close();
+
+ }
+ } finally {
+ closeReprossibleSession();
+ closeImageReaders();
+ }
+ }
+
/**
* Set up two image readers: one for regular capture (used for reprocess input) and one for
* reprocess capture.
@@ -897,6 +975,25 @@
}
/**
+ * Get the ImageReader for reprocess output.
+ */
+ private ImageReader getReprocessOutputImageReader() {
+ if (mShareOneImageReader) {
+ return mFirstImageReader;
+ } else {
+ return mSecondImageReader;
+ }
+ }
+
+ private SimpleImageReaderListener getReprocessOutputImageReaderListener() {
+ if (mShareOneImageReader) {
+ return mFirstImageReaderListener;
+ } else {
+ return mSecondImageReaderListener;
+ }
+ }
+
+ /**
* Set up a reprocessable session and create an ImageWriter with the sessoin's input surface.
*/
private void setupReprocessableSession(Surface previewSurface, int numImageWriterImages)
@@ -1002,23 +1099,14 @@
Surface[] outputSurfaces = new Surface[isReprocessCaptures.length];
for (int i = 0; i < isReprocessCaptures.length; i++) {
- if (mShareOneImageReader) {
- outputSurfaces[i] = mFirstImageReader.getSurface();
- } else {
- outputSurfaces[i] = mSecondImageReader.getSurface();
- }
+ outputSurfaces[i] = getReprocessOutputImageReader().getSurface();
}
TotalCaptureResult[] finalResults = submitMixedCaptureBurstRequest(outputSurfaces, results);
ImageResultHolder[] holders = new ImageResultHolder[isReprocessCaptures.length];
for (int i = 0; i < isReprocessCaptures.length; i++) {
- Image image;
- if (mShareOneImageReader) {
- image = mFirstImageReaderListener.getImage(CAPTURE_TIMEOUT_MS);
- } else {
- image = mSecondImageReaderListener.getImage(CAPTURE_TIMEOUT_MS);
- }
+ Image image = getReprocessOutputImageReaderListener().getImage(CAPTURE_TIMEOUT_MS);
holders[i] = new ImageResultHolder(image, finalResults[i]);
}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/StillCaptureTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/StillCaptureTest.java
index c3cf8d0..9c00510 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/StillCaptureTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/StillCaptureTest.java
@@ -27,8 +27,6 @@
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
-import android.location.Location;
-import android.location.LocationManager;
import android.hardware.camera2.DngCreator;
import android.media.ImageReader;
import android.util.Pair;
@@ -38,9 +36,7 @@
import android.hardware.camera2.cts.helpers.Camera2Focuser;
import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase;
import android.hardware.camera2.params.MeteringRectangle;
-import android.media.ExifInterface;
import android.media.Image;
-import android.os.Build;
import android.os.ConditionVariable;
import android.util.Log;
import android.util.Range;
@@ -51,11 +47,8 @@
import com.android.ex.camera2.exceptions.TimeoutRuntimeException;
import java.io.ByteArrayOutputStream;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Date;
import java.util.List;
public class StillCaptureTest extends Camera2SurfaceViewTestCase {
@@ -63,56 +56,7 @@
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
// 60 second to accommodate the possible long exposure time.
- private static final int EXIF_DATETIME_ERROR_MARGIN_SEC = 60;
- private static final float EXIF_FOCAL_LENGTH_ERROR_MARGIN = 0.001f;
- private static final float EXIF_EXPOSURE_TIME_ERROR_MARGIN_RATIO = 0.05f;
- private static final float EXIF_EXPOSURE_TIME_MIN_ERROR_MARGIN_SEC = 0.002f;
- private static final float EXIF_APERTURE_ERROR_MARGIN = 0.001f;
- private static final Location sTestLocation0 = new Location(LocationManager.GPS_PROVIDER);
- private static final Location sTestLocation1 = new Location(LocationManager.GPS_PROVIDER);
- private static final Location sTestLocation2 = new Location(LocationManager.NETWORK_PROVIDER);
private static final int RELAXED_CAPTURE_IMAGE_TIMEOUT_MS = CAPTURE_IMAGE_TIMEOUT_MS + 1000;
- static {
- sTestLocation0.setTime(1199145600L);
- sTestLocation0.setLatitude(37.736071);
- sTestLocation0.setLongitude(-122.441983);
- sTestLocation0.setAltitude(21.0);
-
- sTestLocation1.setTime(1199145601L);
- sTestLocation1.setLatitude(0.736071);
- sTestLocation1.setLongitude(0.441983);
- sTestLocation1.setAltitude(1.0);
-
- sTestLocation2.setTime(1199145602L);
- sTestLocation2.setLatitude(-89.736071);
- sTestLocation2.setLongitude(-179.441983);
- sTestLocation2.setAltitude(100000.0);
- }
- // Exif test data vectors.
- private static final ExifTestData[] EXIF_TEST_DATA = {
- new ExifTestData(
- /*gpsLocation*/ sTestLocation0,
- /* orientation */90,
- /* jpgQuality */(byte) 80,
- /* thumbQuality */(byte) 75),
- new ExifTestData(
- /*gpsLocation*/ sTestLocation1,
- /* orientation */180,
- /* jpgQuality */(byte) 90,
- /* thumbQuality */(byte) 85),
- new ExifTestData(
- /*gpsLocation*/ sTestLocation2,
- /* orientation */270,
- /* jpgQuality */(byte) 100,
- /* thumbQuality */(byte) 100)
- };
-
- // Some exif tags that are not defined by ExifInterface but supported.
- private static final String TAG_DATETIME_DIGITIZED = "DateTimeDigitized";
- private static final String TAG_SUBSEC_TIME = "SubSecTime";
- private static final String TAG_SUBSEC_TIME_ORIG = "SubSecTimeOriginal";
- private static final String TAG_SUBSEC_TIME_DIG = "SubSecTimeDigitized";
- private static final int EXIF_DATETIME_LENGTH = 19;
private static final int MAX_REGIONS_AE_INDEX = 0;
private static final int MAX_REGIONS_AWB_INDEX = 1;
private static final int MAX_REGIONS_AF_INDEX = 2;
@@ -901,15 +845,6 @@
}
- private static boolean areGpsFieldsEqual(Location a, Location b) {
- if (a == null || b == null) {
- return false;
- }
-
- return a.getTime() == b.getTime() && a.getLatitude() == b.getLatitude() &&
- a.getLongitude() == b.getLongitude() && a.getAltitude() == b.getAltitude() &&
- a.getProvider() == b.getProvider();
- }
/**
* Issue a Jpeg capture and validate the exif information.
* <p>
@@ -944,38 +879,7 @@
testThumbnailSizes[0] = new Size(0, 0);
for (int i = 0; i < EXIF_TEST_DATA.length; i++) {
- /**
- * Capture multiple shots.
- *
- * Verify that:
- * - Capture request get values are same as were set.
- * - capture result's exif data is the same as was set by
- * the capture request.
- * - new tags in the result set by the camera service are
- * present and semantically correct.
- */
- stillBuilder.set(CaptureRequest.JPEG_THUMBNAIL_SIZE, testThumbnailSizes[i]);
- stillBuilder.set(CaptureRequest.JPEG_GPS_LOCATION, EXIF_TEST_DATA[i].gpsLocation);
- stillBuilder.set(CaptureRequest.JPEG_ORIENTATION, EXIF_TEST_DATA[i].jpegOrientation);
- stillBuilder.set(CaptureRequest.JPEG_QUALITY, EXIF_TEST_DATA[i].jpegQuality);
- stillBuilder.set(CaptureRequest.JPEG_THUMBNAIL_QUALITY,
- EXIF_TEST_DATA[i].thumbnailQuality);
-
- // Validate request set and get.
- mCollector.expectEquals("JPEG thumbnail size request set and get should match",
- testThumbnailSizes[i],
- stillBuilder.get(CaptureRequest.JPEG_THUMBNAIL_SIZE));
- mCollector.expectTrue("GPS locations request set and get should match.",
- areGpsFieldsEqual(EXIF_TEST_DATA[i].gpsLocation,
- stillBuilder.get(CaptureRequest.JPEG_GPS_LOCATION)));
- mCollector.expectEquals("JPEG orientation request set and get should match",
- EXIF_TEST_DATA[i].jpegOrientation,
- stillBuilder.get(CaptureRequest.JPEG_ORIENTATION));
- mCollector.expectEquals("JPEG quality request set and get should match",
- EXIF_TEST_DATA[i].jpegQuality, stillBuilder.get(CaptureRequest.JPEG_QUALITY));
- mCollector.expectEquals("JPEG thumbnail quality request set and get should match",
- EXIF_TEST_DATA[i].thumbnailQuality,
- stillBuilder.get(CaptureRequest.JPEG_THUMBNAIL_QUALITY));
+ setJpegKeys(stillBuilder, EXIF_TEST_DATA[i], testThumbnailSizes[i], mCollector);
// Capture a jpeg image.
CaptureRequest request = stillBuilder.build();
@@ -983,287 +887,15 @@
CaptureResult stillResult =
resultListener.getCaptureResultForRequest(request, NUM_RESULTS_WAIT_TIMEOUT);
Image image = imageListener.getImage(CAPTURE_IMAGE_TIMEOUT_MS);
- basicValidateJpegImage(image, maxStillSz);
- byte[] jpegBuffer = getDataFromImage(image);
- // Have to dump into a file to be able to use ExifInterface
- String jpegFileName =
- DEBUG_FILE_NAME_BASE + "/Camera_" + mCamera.getId() + "_test.jpeg";
- dumpFile(jpegFileName, jpegBuffer);
- ExifInterface exif = new ExifInterface(jpegFileName);
-
- if (testThumbnailSizes[i].equals(new Size(0,0))) {
- mCollector.expectTrue(
- "Jpeg shouldn't have thumbnail when thumbnail size is (0, 0)",
- !exif.hasThumbnail());
- } else {
- mCollector.expectTrue(
- "Jpeg must have thumbnail for thumbnail size " + testThumbnailSizes[i],
- exif.hasThumbnail());
- }
-
- // Validate capture result vs. request
- Size resultThumbnailSize = stillResult.get(CaptureResult.JPEG_THUMBNAIL_SIZE);
- int orientationTested = EXIF_TEST_DATA[i].jpegOrientation;
- if ((orientationTested == 90 || orientationTested == 270)) {
- int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,
- /*defaultValue*/-1);
- if (exifOrientation == ExifInterface.ORIENTATION_UNDEFINED) {
- // Device physically rotated image+thumbnail data
- // Expect thumbnail size to be also rotated
- resultThumbnailSize = new Size(
- resultThumbnailSize.getHeight(),
- resultThumbnailSize.getWidth());
- }
- }
-
- mCollector.expectEquals("JPEG thumbnail size result and request should match",
- testThumbnailSizes[i],
- resultThumbnailSize);
- if (mCollector.expectKeyValueNotNull(stillResult, CaptureResult.JPEG_GPS_LOCATION) !=
- null) {
- mCollector.expectTrue("GPS location result and request should match.",
- areGpsFieldsEqual(EXIF_TEST_DATA[i].gpsLocation,
- stillResult.get(CaptureResult.JPEG_GPS_LOCATION)));
- }
- mCollector.expectEquals("JPEG orientation result and request should match",
- EXIF_TEST_DATA[i].jpegOrientation,
- stillResult.get(CaptureResult.JPEG_ORIENTATION));
- mCollector.expectEquals("JPEG quality result and request should match",
- EXIF_TEST_DATA[i].jpegQuality, stillResult.get(CaptureResult.JPEG_QUALITY));
- mCollector.expectEquals("JPEG thumbnail quality result and request should match",
- EXIF_TEST_DATA[i].thumbnailQuality,
- stillResult.get(CaptureResult.JPEG_THUMBNAIL_QUALITY));
-
- // Validate other exif tags for all non-legacy devices
- if (!mStaticInfo.isHardwareLevelLegacy()) {
- jpegTestExifExtraTags(exif, maxStillSz, stillResult);
- }
+ verifyJpegKeys(image, stillResult, maxStillSz, testThumbnailSizes[i], EXIF_TEST_DATA[i],
+ mStaticInfo, mCollector);
// Free image resources
image.close();
}
}
- private void jpegTestExifExtraTags(ExifInterface exif, Size jpegSize, CaptureResult result)
- throws ParseException {
- /**
- * TAG_IMAGE_WIDTH and TAG_IMAGE_LENGTH and TAG_ORIENTATION.
- * Orientation and exif width/height need to be tested carefully, two cases:
- *
- * 1. Device rotate the image buffer physically, then exif width/height may not match
- * the requested still capture size, we need swap them to check.
- *
- * 2. Device use the exif tag to record the image orientation, it doesn't rotate
- * the jpeg image buffer itself. In this case, the exif width/height should always match
- * the requested still capture size, and the exif orientation should always match the
- * requested orientation.
- *
- */
- int exifWidth = exif.getAttributeInt(ExifInterface.TAG_IMAGE_WIDTH, /*defaultValue*/0);
- int exifHeight = exif.getAttributeInt(ExifInterface.TAG_IMAGE_LENGTH, /*defaultValue*/0);
- Size exifSize = new Size(exifWidth, exifHeight);
- // Orientation could be missing, which is ok, default to 0.
- int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,
- /*defaultValue*/-1);
- // Get requested orientation from result, because they should be same.
- if (mCollector.expectKeyValueNotNull(result, CaptureResult.JPEG_ORIENTATION) != null) {
- int requestedOrientation = result.get(CaptureResult.JPEG_ORIENTATION);
- final int ORIENTATION_MIN = ExifInterface.ORIENTATION_UNDEFINED;
- final int ORIENTATION_MAX = ExifInterface.ORIENTATION_ROTATE_270;
- boolean orientationValid = mCollector.expectTrue(String.format(
- "Exif orientation must be in range of [%d, %d]",
- ORIENTATION_MIN, ORIENTATION_MAX),
- exifOrientation >= ORIENTATION_MIN && exifOrientation <= ORIENTATION_MAX);
- if (orientationValid) {
- /**
- * Device captured image doesn't respect the requested orientation,
- * which means it rotates the image buffer physically. Then we
- * should swap the exif width/height accordingly to compare.
- */
- boolean deviceRotatedImage = exifOrientation == ExifInterface.ORIENTATION_UNDEFINED;
-
- if (deviceRotatedImage) {
- // Case 1.
- boolean needSwap = (requestedOrientation % 180 == 90);
- if (needSwap) {
- exifSize = new Size(exifHeight, exifWidth);
- }
- } else {
- // Case 2.
- mCollector.expectEquals("Exif orientaiton should match requested orientation",
- requestedOrientation, getExifOrientationInDegress(exifOrientation));
- }
- }
- }
-
- /**
- * Ideally, need check exifSize == jpegSize == actual buffer size. But
- * jpegSize == jpeg decode bounds size(from jpeg jpeg frame
- * header, not exif) was validated in ImageReaderTest, no need to
- * validate again here.
- */
- mCollector.expectEquals("Exif size should match jpeg capture size", jpegSize, exifSize);
-
- // TAG_DATETIME, it should be local time
- long currentTimeInMs = System.currentTimeMillis();
- long currentTimeInSecond = currentTimeInMs / 1000;
- Date date = new Date(currentTimeInMs);
- String localDatetime = new SimpleDateFormat("yyyy:MM:dd HH:").format(date);
- String dateTime = exif.getAttribute(ExifInterface.TAG_DATETIME);
- if (mCollector.expectTrue("Exif TAG_DATETIME shouldn't be null", dateTime != null)) {
- mCollector.expectTrue("Exif TAG_DATETIME is wrong",
- dateTime.length() == EXIF_DATETIME_LENGTH);
- long exifTimeInSecond =
- new SimpleDateFormat("yyyy:MM:dd HH:mm:ss").parse(dateTime).getTime() / 1000;
- long delta = currentTimeInSecond - exifTimeInSecond;
- mCollector.expectTrue("Capture time deviates too much from the current time",
- Math.abs(delta) < EXIF_DATETIME_ERROR_MARGIN_SEC);
- // It should be local time.
- mCollector.expectTrue("Exif date time should be local time",
- dateTime.startsWith(localDatetime));
- }
-
- // TAG_FOCAL_LENGTH.
- float[] focalLengths = mStaticInfo.getAvailableFocalLengthsChecked();
- float exifFocalLength = (float)exif.getAttributeDouble(ExifInterface.TAG_FOCAL_LENGTH, -1);
- mCollector.expectEquals("Focal length should match",
- getClosestValueInArray(focalLengths, exifFocalLength),
- exifFocalLength, EXIF_FOCAL_LENGTH_ERROR_MARGIN);
- // More checks for focal length.
- mCollector.expectEquals("Exif focal length should match capture result",
- validateFocalLength(result), exifFocalLength);
-
- // TAG_EXPOSURE_TIME
- // ExifInterface API gives exposure time value in the form of float instead of rational
- String exposureTime = exif.getAttribute(ExifInterface.TAG_EXPOSURE_TIME);
- mCollector.expectNotNull("Exif TAG_EXPOSURE_TIME shouldn't be null", exposureTime);
- if (mStaticInfo.areKeysAvailable(CaptureResult.SENSOR_EXPOSURE_TIME)) {
- if (exposureTime != null) {
- double exposureTimeValue = Double.parseDouble(exposureTime);
- long expTimeResult = result.get(CaptureResult.SENSOR_EXPOSURE_TIME);
- double expected = expTimeResult / 1e9;
- double tolerance = expected * EXIF_EXPOSURE_TIME_ERROR_MARGIN_RATIO;
- tolerance = Math.max(tolerance, EXIF_EXPOSURE_TIME_MIN_ERROR_MARGIN_SEC);
- mCollector.expectEquals("Exif exposure time doesn't match", expected,
- exposureTimeValue, tolerance);
- }
- }
-
- // TAG_APERTURE
- // ExifInterface API gives aperture value in the form of float instead of rational
- String exifAperture = exif.getAttribute(ExifInterface.TAG_APERTURE);
- mCollector.expectNotNull("Exif TAG_APERTURE shouldn't be null", exifAperture);
- if (mStaticInfo.areKeysAvailable(CameraCharacteristics.LENS_INFO_AVAILABLE_APERTURES)) {
- float[] apertures = mStaticInfo.getAvailableAperturesChecked();
- if (exifAperture != null) {
- float apertureValue = Float.parseFloat(exifAperture);
- mCollector.expectEquals("Aperture value should match",
- getClosestValueInArray(apertures, apertureValue),
- apertureValue, EXIF_APERTURE_ERROR_MARGIN);
- // More checks for aperture.
- mCollector.expectEquals("Exif aperture length should match capture result",
- validateAperture(result), apertureValue);
- }
- }
-
- /**
- * TAG_FLASH. TODO: For full devices, can check a lot more info
- * (http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html#Flash)
- */
- String flash = exif.getAttribute(ExifInterface.TAG_FLASH);
- mCollector.expectNotNull("Exif TAG_FLASH shouldn't be null", flash);
-
- /**
- * TAG_WHITE_BALANCE. TODO: For full devices, with the DNG tags, we
- * should be able to cross-check android.sensor.referenceIlluminant.
- */
- String whiteBalance = exif.getAttribute(ExifInterface.TAG_WHITE_BALANCE);
- mCollector.expectNotNull("Exif TAG_WHITE_BALANCE shouldn't be null", whiteBalance);
-
- // TAG_MAKE
- String make = exif.getAttribute(ExifInterface.TAG_MAKE);
- mCollector.expectEquals("Exif TAG_MAKE is incorrect", Build.MANUFACTURER, make);
-
- // TAG_MODEL
- String model = exif.getAttribute(ExifInterface.TAG_MODEL);
- mCollector.expectEquals("Exif TAG_MODEL is incorrect", Build.MODEL, model);
-
-
- // TAG_ISO
- int iso = exif.getAttributeInt(ExifInterface.TAG_ISO, /*defaultValue*/-1);
- if (mStaticInfo.areKeysAvailable(CaptureResult.SENSOR_SENSITIVITY)) {
- int expectedIso = result.get(CaptureResult.SENSOR_SENSITIVITY);
- mCollector.expectEquals("Exif TAG_ISO is incorrect", expectedIso, iso);
- }
-
- // TAG_DATETIME_DIGITIZED (a.k.a Create time for digital cameras).
- String digitizedTime = exif.getAttribute(TAG_DATETIME_DIGITIZED);
- mCollector.expectNotNull("Exif TAG_DATETIME_DIGITIZED shouldn't be null", digitizedTime);
- if (digitizedTime != null) {
- String expectedDateTime = exif.getAttribute(ExifInterface.TAG_DATETIME);
- mCollector.expectNotNull("Exif TAG_DATETIME shouldn't be null", expectedDateTime);
- if (expectedDateTime != null) {
- mCollector.expectEquals("dataTime should match digitizedTime",
- expectedDateTime, digitizedTime);
- }
- }
-
- /**
- * TAG_SUBSEC_TIME. Since the sub second tag strings are truncated to at
- * most 9 digits in ExifInterface implementation, use getAttributeInt to
- * sanitize it. When the default value -1 is returned, it means that
- * this exif tag either doesn't exist or is a non-numerical invalid
- * string. Same rule applies to the rest of sub second tags.
- */
- int subSecTime = exif.getAttributeInt(TAG_SUBSEC_TIME, /*defaultValue*/-1);
- mCollector.expectTrue("Exif TAG_SUBSEC_TIME value is null or invalid!", subSecTime > 0);
-
- // TAG_SUBSEC_TIME_ORIG
- int subSecTimeOrig = exif.getAttributeInt(TAG_SUBSEC_TIME_ORIG, /*defaultValue*/-1);
- mCollector.expectTrue("Exif TAG_SUBSEC_TIME_ORIG value is null or invalid!",
- subSecTimeOrig > 0);
-
- // TAG_SUBSEC_TIME_DIG
- int subSecTimeDig = exif.getAttributeInt(TAG_SUBSEC_TIME_DIG, /*defaultValue*/-1);
- mCollector.expectTrue(
- "Exif TAG_SUBSEC_TIME_DIG value is null or invalid!", subSecTimeDig > 0);
- }
-
- private int getExifOrientationInDegress(int exifOrientation) {
- switch (exifOrientation) {
- case ExifInterface.ORIENTATION_NORMAL:
- return 0;
- case ExifInterface.ORIENTATION_ROTATE_90:
- return 90;
- case ExifInterface.ORIENTATION_ROTATE_180:
- return 180;
- case ExifInterface.ORIENTATION_ROTATE_270:
- return 270;
- default:
- mCollector.addMessage("It is impossible to get non 0, 90, 180, 270 degress exif" +
- "info based on the request orientation range");
- return 0;
- }
- }
- /**
- * Immutable class wrapping the exif test data.
- */
- private static class ExifTestData {
- public final Location gpsLocation;
- public final int jpegOrientation;
- public final byte jpegQuality;
- public final byte thumbnailQuality;
-
- public ExifTestData(Location location, int orientation,
- byte jpgQuality, byte thumbQuality) {
- gpsLocation = location;
- jpegOrientation = orientation;
- jpegQuality = jpgQuality;
- thumbnailQuality = thumbQuality;
- }
- }
-
private void aeCompensationTestByCamera() throws Exception {
Range<Integer> compensationRange = mStaticInfo.getAeCompensationRangeChecked();
// Skip the test if exposure compensation is not supported.
@@ -1438,30 +1070,6 @@
//----------------------------------------------------------------
//---------Below are common functions for all tests.--------------
//----------------------------------------------------------------
-
- /**
- * Simple validation of JPEG image size and format.
- * <p>
- * Only validate the image object sanity. It is fast, but doesn't actually
- * check the buffer data. Assert is used here as it make no sense to
- * continue the test if the jpeg image captured has some serious failures.
- * </p>
- *
- * @param image The captured jpeg image
- * @param expectedSize Expected capture jpeg size
- */
- private static void basicValidateJpegImage(Image image, Size expectedSize) {
- Size imageSz = new Size(image.getWidth(), image.getHeight());
- assertTrue(
- String.format("Image size doesn't match (expected %s, actual %s) ",
- expectedSize.toString(), imageSz.toString()), expectedSize.equals(imageSz));
- assertEquals("Image format should be JPEG", ImageFormat.JPEG, image.getFormat());
- assertNotNull("Image plane shouldn't be null", image.getPlanes());
- assertEquals("Image plane number should be 1", 1, image.getPlanes().length);
-
- // Jpeg decoding validate was done in ImageReaderTest, no need to duplicate the test here.
- }
-
/**
* Validate standard raw (RAW16) capture image.
*
@@ -1488,60 +1096,6 @@
ImageFormat.JPEG, /*filePath*/null);
}
- private static float getClosestValueInArray(float[] values, float target) {
- int minIdx = 0;
- float minDistance = Math.abs(values[0] - target);
- for(int i = 0; i < values.length; i++) {
- float distance = Math.abs(values[i] - target);
- if (minDistance > distance) {
- minDistance = distance;
- minIdx = i;
- }
- }
-
- return values[minIdx];
- }
-
- /**
- * Validate and return the focal length.
- *
- * @param result Capture result to get the focal length
- * @return Focal length from capture result or -1 if focal length is not available.
- */
- private float validateFocalLength(CaptureResult result) {
- float[] focalLengths = mStaticInfo.getAvailableFocalLengthsChecked();
- Float resultFocalLength = result.get(CaptureResult.LENS_FOCAL_LENGTH);
- if (mCollector.expectTrue("Focal length is invalid",
- resultFocalLength != null && resultFocalLength > 0)) {
- List<Float> focalLengthList =
- Arrays.asList(CameraTestUtils.toObject(focalLengths));
- mCollector.expectTrue("Focal length should be one of the available focal length",
- focalLengthList.contains(resultFocalLength));
- return resultFocalLength;
- }
- return -1;
- }
-
- /**
- * Validate and return the aperture.
- *
- * @param result Capture result to get the aperture
- * @return Aperture from capture result or -1 if aperture is not available.
- */
- private float validateAperture(CaptureResult result) {
- float[] apertures = mStaticInfo.getAvailableAperturesChecked();
- Float resultAperture = result.get(CaptureResult.LENS_APERTURE);
- if (mCollector.expectTrue("Capture result aperture is invalid",
- resultAperture != null && resultAperture > 0)) {
- List<Float> apertureList =
- Arrays.asList(CameraTestUtils.toObject(apertures));
- mCollector.expectTrue("Aperture should be one of the available apertures",
- apertureList.contains(resultAperture));
- return resultAperture;
- }
- return -1;
- }
-
private static class SimpleAutoFocusListener implements Camera2Focuser.AutoFocusListener {
final ConditionVariable focusDone = new ConditionVariable();
@Override