blob: db326ba6e2c20a4d128f5b110ff96dc33f707831 [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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.mediaframeworktest.unit;
import com.android.mediaframeworktest.R;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.ExifInterface;
import android.os.Environment;
import android.test.AndroidTestCase;
import android.util.Log;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.IOException;
import libcore.io.IoUtils;
import libcore.io.Streams;
public class ExifInterfaceTest extends AndroidTestCase {
private static final String TAG = ExifInterface.class.getSimpleName();
private static final boolean VERBOSE = false; // lots of logging
private static final double DIFFERENCE_TOLERANCE = .001;
// List of files.
private static final String EXIF_BYTE_ORDER_II_JPEG = "image_exif_byte_order_ii.jpg";
private static final String EXIF_BYTE_ORDER_MM_JPEG = "image_exif_byte_order_mm.jpg";
private static final String LG_G4_ISO_800_DNG = "lg_g4_iso_800.dng";
private static final String VOLANTIS_JPEG = "volantis.jpg";
private static final int[] IMAGE_RESOURCES = new int[] {
R.raw.image_exif_byte_order_ii, R.raw.image_exif_byte_order_mm, R.raw.lg_g4_iso_800,
R.raw.volantis };
private static final String[] IMAGE_FILENAMES = new String[] {
EXIF_BYTE_ORDER_II_JPEG, EXIF_BYTE_ORDER_MM_JPEG, LG_G4_ISO_800_DNG, VOLANTIS_JPEG };
private static final String[] EXIF_TAGS = {
ExifInterface.TAG_MAKE,
ExifInterface.TAG_MODEL,
ExifInterface.TAG_F_NUMBER,
ExifInterface.TAG_DATETIME,
ExifInterface.TAG_EXPOSURE_TIME,
ExifInterface.TAG_FLASH,
ExifInterface.TAG_FOCAL_LENGTH,
ExifInterface.TAG_GPS_ALTITUDE,
ExifInterface.TAG_GPS_ALTITUDE_REF,
ExifInterface.TAG_GPS_DATESTAMP,
ExifInterface.TAG_GPS_LATITUDE,
ExifInterface.TAG_GPS_LATITUDE_REF,
ExifInterface.TAG_GPS_LONGITUDE,
ExifInterface.TAG_GPS_LONGITUDE_REF,
ExifInterface.TAG_GPS_PROCESSING_METHOD,
ExifInterface.TAG_GPS_TIMESTAMP,
ExifInterface.TAG_IMAGE_LENGTH,
ExifInterface.TAG_IMAGE_WIDTH,
ExifInterface.TAG_ISO_SPEED_RATINGS,
ExifInterface.TAG_ORIENTATION,
ExifInterface.TAG_WHITE_BALANCE
};
private static class ExpectedValue {
// Thumbnail information.
public final boolean hasThumbnail;
public final int thumbnailWidth;
public final int thumbnailHeight;
// GPS information.
public final boolean hasLatLong;
public final float latitude;
public final float longitude;
public final float altitude;
// Values.
public final String make;
public final String model;
public final float fNumber;
public final String datetime;
public final float exposureTime;
public final float flash;
public final String focalLength;
public final String gpsAltitude;
public final String gpsAltitudeRef;
public final String gpsDatestamp;
public final String gpsLatitude;
public final String gpsLatitudeRef;
public final String gpsLongitude;
public final String gpsLongitudeRef;
public final String gpsProcessingMethod;
public final String gpsTimestamp;
public final int imageLength;
public final int imageWidth;
public final String iso;
public final int orientation;
public final int whiteBalance;
private static String getString(TypedArray typedArray, int index) {
String stringValue = typedArray.getString(index);
if (stringValue == null || stringValue.equals("")) {
return null;
}
return stringValue.trim();
}
public ExpectedValue(TypedArray typedArray) {
// Reads thumbnail information.
hasThumbnail = typedArray.getBoolean(0, false);
thumbnailWidth = typedArray.getInt(1, 0);
thumbnailHeight = typedArray.getInt(2, 0);
// Reads GPS information.
hasLatLong = typedArray.getBoolean(3, false);
latitude = typedArray.getFloat(4, 0f);
longitude = typedArray.getFloat(5, 0f);
altitude = typedArray.getFloat(6, 0f);
// Reads values.
make = getString(typedArray, 7);
model = getString(typedArray, 8);
fNumber = typedArray.getFloat(9, 0f);
datetime = getString(typedArray, 10);
exposureTime = typedArray.getFloat(11, 0f);
flash = typedArray.getFloat(12, 0f);
focalLength = getString(typedArray, 13);
gpsAltitude = getString(typedArray, 14);
gpsAltitudeRef = getString(typedArray, 15);
gpsDatestamp = getString(typedArray, 16);
gpsLatitude = getString(typedArray, 17);
gpsLatitudeRef = getString(typedArray, 18);
gpsLongitude = getString(typedArray, 19);
gpsLongitudeRef = getString(typedArray, 20);
gpsProcessingMethod = getString(typedArray, 21);
gpsTimestamp = getString(typedArray, 22);
imageLength = typedArray.getInt(23, 0);
imageWidth = typedArray.getInt(24, 0);
iso = getString(typedArray, 25);
orientation = typedArray.getInt(26, 0);
whiteBalance = typedArray.getInt(27, 0);
typedArray.recycle();
}
}
@Override
protected void setUp() throws Exception {
for (int i = 0; i < IMAGE_RESOURCES.length; ++i) {
String outputPath = new File(Environment.getExternalStorageDirectory(),
IMAGE_FILENAMES[i]).getAbsolutePath();
try (InputStream inputStream = getContext().getResources().openRawResource(
IMAGE_RESOURCES[i])) {
try (FileOutputStream outputStream = new FileOutputStream(outputPath)) {
Streams.copy(inputStream, outputStream);
}
}
}
super.setUp();
}
@Override
protected void tearDown() throws Exception {
for (int i = 0; i < IMAGE_RESOURCES.length; ++i) {
String imageFilePath = new File(Environment.getExternalStorageDirectory(),
IMAGE_FILENAMES[i]).getAbsolutePath();
File imageFile = new File(imageFilePath);
if (imageFile.exists()) {
imageFile.delete();
}
}
super.tearDown();
}
private void printExifTagsAndValues(String fileName, ExifInterface exifInterface) {
// Prints thumbnail information.
if (exifInterface.hasThumbnail()) {
byte[] thumbnailBytes = exifInterface.getThumbnail();
if (thumbnailBytes != null) {
Log.v(TAG, fileName + " Thumbnail size = " + thumbnailBytes.length);
Bitmap bitmap = BitmapFactory.decodeByteArray(
thumbnailBytes, 0, thumbnailBytes.length);
if (bitmap == null) {
Log.e(TAG, fileName + " Corrupted thumbnail!");
} else {
Log.v(TAG, fileName + " Thumbnail size: " + bitmap.getWidth() + ", "
+ bitmap.getHeight());
}
} else {
Log.e(TAG, fileName + " Unexpected result: No thumbnails were found. "
+ "A thumbnail is expected.");
}
} else {
if (exifInterface.getThumbnail() != null) {
Log.e(TAG, fileName + " Unexpected result: A thumbnail was found. "
+ "No thumbnail is expected.");
} else {
Log.v(TAG, fileName + " No thumbnail");
}
}
// Prints GPS information.
Log.v(TAG, fileName + " Altitude = " + exifInterface.getAltitude(.0));
float[] latLong = new float[2];
if (exifInterface.getLatLong(latLong)) {
Log.v(TAG, fileName + " Latitude = " + latLong[0]);
Log.v(TAG, fileName + " Longitude = " + latLong[1]);
} else {
Log.v(TAG, fileName + " No latlong data");
}
// Prints values.
for (String tagKey : EXIF_TAGS) {
String tagValue = exifInterface.getAttribute(tagKey);
Log.v(TAG, fileName + " Key{" + tagKey + "} = '" + tagValue + "'");
}
}
private void assertIntTag(ExifInterface exifInterface, String tag, int expectedValue) {
int intValue = exifInterface.getAttributeInt(tag, 0);
assertEquals(expectedValue, intValue);
}
private void assertDoubleTag(ExifInterface exifInterface, String tag, float expectedValue) {
double doubleValue = exifInterface.getAttributeDouble(tag, 0.0);
assertEquals(expectedValue, doubleValue, DIFFERENCE_TOLERANCE);
}
private void assertStringTag(ExifInterface exifInterface, String tag, String expectedValue) {
String stringValue = exifInterface.getAttribute(tag);
if (stringValue != null) {
stringValue = stringValue.trim();
}
assertEquals(expectedValue, stringValue);
}
private void compareWithExpectedValue(ExifInterface exifInterface,
ExpectedValue expectedValue, String verboseTag) {
if (VERBOSE) {
printExifTagsAndValues(verboseTag, exifInterface);
}
// Checks a thumbnail image.
assertEquals(expectedValue.hasThumbnail, exifInterface.hasThumbnail());
if (expectedValue.hasThumbnail) {
byte[] thumbnailBytes = exifInterface.getThumbnail();
assertNotNull(thumbnailBytes);
Bitmap thumbnailBitmap =
BitmapFactory.decodeByteArray(thumbnailBytes, 0, thumbnailBytes.length);
assertNotNull(thumbnailBitmap);
assertEquals(expectedValue.thumbnailWidth, thumbnailBitmap.getWidth());
assertEquals(expectedValue.thumbnailHeight, thumbnailBitmap.getHeight());
} else {
assertNull(exifInterface.getThumbnail());
}
// Checks GPS information.
float[] latLong = new float[2];
assertEquals(expectedValue.hasLatLong, exifInterface.getLatLong(latLong));
if (expectedValue.hasLatLong) {
assertEquals(expectedValue.latitude, latLong[0], DIFFERENCE_TOLERANCE);
assertEquals(expectedValue.longitude, latLong[1], DIFFERENCE_TOLERANCE);
}
assertEquals(expectedValue.altitude, exifInterface.getAltitude(.0), DIFFERENCE_TOLERANCE);
// Checks values.
assertStringTag(exifInterface, ExifInterface.TAG_MAKE, expectedValue.make);
assertStringTag(exifInterface, ExifInterface.TAG_MODEL, expectedValue.model);
assertDoubleTag(exifInterface, ExifInterface.TAG_F_NUMBER, expectedValue.fNumber);
assertStringTag(exifInterface, ExifInterface.TAG_DATETIME, expectedValue.datetime);
assertDoubleTag(exifInterface, ExifInterface.TAG_EXPOSURE_TIME, expectedValue.exposureTime);
assertDoubleTag(exifInterface, ExifInterface.TAG_FLASH, expectedValue.flash);
assertStringTag(exifInterface, ExifInterface.TAG_FOCAL_LENGTH, expectedValue.focalLength);
assertStringTag(exifInterface, ExifInterface.TAG_GPS_ALTITUDE, expectedValue.gpsAltitude);
assertStringTag(exifInterface, ExifInterface.TAG_GPS_ALTITUDE_REF,
expectedValue.gpsAltitudeRef);
assertStringTag(exifInterface, ExifInterface.TAG_GPS_DATESTAMP, expectedValue.gpsDatestamp);
assertStringTag(exifInterface, ExifInterface.TAG_GPS_LATITUDE, expectedValue.gpsLatitude);
assertStringTag(exifInterface, ExifInterface.TAG_GPS_LATITUDE_REF,
expectedValue.gpsLatitudeRef);
assertStringTag(exifInterface, ExifInterface.TAG_GPS_LONGITUDE, expectedValue.gpsLongitude);
assertStringTag(exifInterface, ExifInterface.TAG_GPS_LONGITUDE_REF,
expectedValue.gpsLongitudeRef);
assertStringTag(exifInterface, ExifInterface.TAG_GPS_PROCESSING_METHOD,
expectedValue.gpsProcessingMethod);
assertStringTag(exifInterface, ExifInterface.TAG_GPS_TIMESTAMP, expectedValue.gpsTimestamp);
assertIntTag(exifInterface, ExifInterface.TAG_IMAGE_LENGTH, expectedValue.imageLength);
assertIntTag(exifInterface, ExifInterface.TAG_IMAGE_WIDTH, expectedValue.imageWidth);
assertStringTag(exifInterface, ExifInterface.TAG_ISO_SPEED_RATINGS, expectedValue.iso);
assertIntTag(exifInterface, ExifInterface.TAG_ORIENTATION, expectedValue.orientation);
assertIntTag(exifInterface, ExifInterface.TAG_WHITE_BALANCE, expectedValue.whiteBalance);
}
private void testExifInterfaceCommon(File imageFile, ExpectedValue expectedValue)
throws IOException {
String verboseTag = imageFile.getName();
// Creates via path.
ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath());
compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
// Creates from an asset file.
InputStream in = null;
try {
in = mContext.getAssets().open(imageFile.getName());
exifInterface = new ExifInterface(in);
compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
} finally {
IoUtils.closeQuietly(in);
}
// Creates via InputStream.
in = null;
try {
in = new BufferedInputStream(new FileInputStream(imageFile.getAbsolutePath()));
exifInterface = new ExifInterface(in);
compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
} finally {
IoUtils.closeQuietly(in);
}
// Creates via FileDescriptor.
FileDescriptor fd = null;
try {
fd = Os.open(imageFile.getAbsolutePath(), OsConstants.O_RDONLY, 0600);
exifInterface = new ExifInterface(fd);
compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
} catch (ErrnoException e) {
throw e.rethrowAsIOException();
} finally {
IoUtils.closeQuietly(fd);
}
}
private void testSaveAttributes_withFileName(File imageFile, ExpectedValue expectedValue)
throws IOException {
String verboseTag = imageFile.getName();
ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath());
exifInterface.saveAttributes();
exifInterface = new ExifInterface(imageFile.getAbsolutePath());
compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
// Test for modifying one attribute.
String backupValue = exifInterface.getAttribute(ExifInterface.TAG_MAKE);
exifInterface.setAttribute(ExifInterface.TAG_MAKE, "abc");
exifInterface.saveAttributes();
exifInterface = new ExifInterface(imageFile.getAbsolutePath());
assertEquals("abc", exifInterface.getAttribute(ExifInterface.TAG_MAKE));
// Restore the backup value.
exifInterface.setAttribute(ExifInterface.TAG_MAKE, backupValue);
exifInterface.saveAttributes();
exifInterface = new ExifInterface(imageFile.getAbsolutePath());
compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
}
private void testSaveAttributes_withFileDescriptor(File imageFile, ExpectedValue expectedValue)
throws IOException {
String verboseTag = imageFile.getName();
FileDescriptor fd = null;
try {
fd = Os.open(imageFile.getAbsolutePath(), OsConstants.O_RDWR, 0600);
ExifInterface exifInterface = new ExifInterface(fd);
exifInterface.saveAttributes();
Os.lseek(fd, 0, OsConstants.SEEK_SET);
exifInterface = new ExifInterface(fd);
compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
// Test for modifying one attribute.
String backupValue = exifInterface.getAttribute(ExifInterface.TAG_MAKE);
exifInterface.setAttribute(ExifInterface.TAG_MAKE, "abc");
exifInterface.saveAttributes();
Os.lseek(fd, 0, OsConstants.SEEK_SET);
exifInterface = new ExifInterface(fd);
assertEquals("abc", exifInterface.getAttribute(ExifInterface.TAG_MAKE));
// Restore the backup value.
exifInterface.setAttribute(ExifInterface.TAG_MAKE, backupValue);
exifInterface.saveAttributes();
Os.lseek(fd, 0, OsConstants.SEEK_SET);
exifInterface = new ExifInterface(fd);
compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
} catch (ErrnoException e) {
throw e.rethrowAsIOException();
} finally {
IoUtils.closeQuietly(fd);
}
}
private void testSaveAttributes_withInputStream(File imageFile, ExpectedValue expectedValue)
throws IOException {
InputStream in = null;
try {
in = getContext().getAssets().open(imageFile.getName());
ExifInterface exifInterface = new ExifInterface(in);
exifInterface.saveAttributes();
} catch (UnsupportedOperationException e) {
// Expected. saveAttributes is not supported with an ExifInterface object which was
// created with InputStream.
return;
} finally {
IoUtils.closeQuietly(in);
}
fail("Should not reach here!");
}
private void testExifInterfaceForJpeg(String fileName, int typedArrayResourceId)
throws IOException {
ExpectedValue expectedValue = new ExpectedValue(
getContext().getResources().obtainTypedArray(typedArrayResourceId));
File imageFile = new File(Environment.getExternalStorageDirectory(), fileName);
// Test for reading from various inputs.
testExifInterfaceCommon(imageFile, expectedValue);
// Test for saving attributes.
testSaveAttributes_withFileName(imageFile, expectedValue);
testSaveAttributes_withFileDescriptor(imageFile, expectedValue);
testSaveAttributes_withInputStream(imageFile, expectedValue);
}
private void testExifInterfaceForRaw(String fileName, int typedArrayResourceId)
throws IOException {
ExpectedValue expectedValue = new ExpectedValue(
getContext().getResources().obtainTypedArray(typedArrayResourceId));
File imageFile = new File(Environment.getExternalStorageDirectory(), fileName);
// Test for reading from various inputs.
testExifInterfaceCommon(imageFile, expectedValue);
// Since ExifInterface does not support for saving attributes for RAW files, do not test
// about writing back in here.
}
public void testReadExifDataFromExifByteOrderIIJpeg() throws Throwable {
testExifInterfaceForJpeg(EXIF_BYTE_ORDER_II_JPEG, R.array.exifbyteorderii_jpg);
}
public void testReadExifDataFromExifByteOrderMMJpeg() throws Throwable {
testExifInterfaceForJpeg(EXIF_BYTE_ORDER_MM_JPEG, R.array.exifbyteordermm_jpg);
}
public void testReadExifDataFromLgG4Iso800Dng() throws Throwable {
testExifInterfaceForRaw(LG_G4_ISO_800_DNG, R.array.lg_g4_iso_800_dng);
}
public void testDoNotFailOnCorruptedImage() throws Throwable {
// To keep the compatibility with old versions of ExifInterface, even on a corrupted image,
// it shouldn't raise any exceptions except an IOException when unable to open a file.
byte[] bytes = new byte[1024];
try {
new ExifInterface(new ByteArrayInputStream(bytes));
// Always success
} catch (IOException e) {
fail("Should not reach here!");
}
}
public void testReadExifDataFromVolantisJpg() throws Throwable {
// Test if it is possible to parse the volantis generated JPEG smoothly.
testExifInterfaceForJpeg(VOLANTIS_JPEG, R.array.volantis_jpg);
}
}