diff options
Diffstat (limited to 'android/support/media/ExifInterfaceTest.java')
-rw-r--r-- | android/support/media/ExifInterfaceTest.java | 898 |
1 files changed, 898 insertions, 0 deletions
diff --git a/android/support/media/ExifInterfaceTest.java b/android/support/media/ExifInterfaceTest.java new file mode 100644 index 00000000..f811d1a7 --- /dev/null +++ b/android/support/media/ExifInterfaceTest.java @@ -0,0 +1,898 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.support.media; + +import static android.support.test.InstrumentationRegistry.getContext; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; + +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.location.Location; +import android.os.Environment; +import android.support.exifinterface.test.R; +import android.support.test.filters.LargeTest; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.util.Log; +import android.util.Pair; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.Closeable; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +/** + * Test {@link ExifInterface}. + */ +@RunWith(AndroidJUnit4.class) +public class ExifInterfaceTest { + private static final String TAG = ExifInterface.class.getSimpleName(); + private static final boolean VERBOSE = false; // lots of logging + private static final double DIFFERENCE_TOLERANCE = .001; + + 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 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}; + private static final String[] IMAGE_FILENAMES = new String[] { + EXIF_BYTE_ORDER_II_JPEG, EXIF_BYTE_ORDER_MM_JPEG, LG_G4_ISO_800_DNG}; + + private static final String TEST_TEMP_FILE_NAME = "testImage"; + private static final double DELTA = 1e-8; + // We translate double to rational in a 1/10000 precision. + private static final double RATIONAL_DELTA = 0.0001; + private static final int TEST_LAT_LONG_VALUES_ARRAY_LENGTH = 8; + private static final int TEST_NUMBER_OF_CORRUPTED_IMAGE_STREAMS = 30; + private static final double[] TEST_LATITUDE_VALID_VALUES = new double[] + {0, 45, 90, -60, 0.00000001, -89.999999999, 14.2465923626, -68.3434534737}; + private static final double[] TEST_LONGITUDE_VALID_VALUES = new double[] + {0, -45, 90, -120, 180, 0.00000001, -179.99999999999, -58.57834236352}; + private static final double[] TEST_LATITUDE_INVALID_VALUES = new double[] + {Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, 90.0000000001, + 263.34763236326, -1e5, 347.32525, -176.346347754}; + private static final double[] TEST_LONGITUDE_INVALID_VALUES = new double[] + {Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, 180.0000000001, + 263.34763236326, -1e10, 347.325252623, -4000.346323236}; + private static final double[] TEST_ALTITUDE_VALUES = new double[] + {0, -2000, 10000, -355.99999999999, 18.02038}; + private static final int[][] TEST_ROTATION_STATE_MACHINE = { + {ExifInterface.ORIENTATION_UNDEFINED, -90, ExifInterface.ORIENTATION_UNDEFINED}, + {ExifInterface.ORIENTATION_UNDEFINED, 0, ExifInterface.ORIENTATION_UNDEFINED}, + {ExifInterface.ORIENTATION_UNDEFINED, 90, ExifInterface.ORIENTATION_UNDEFINED}, + {ExifInterface.ORIENTATION_UNDEFINED, 180, ExifInterface.ORIENTATION_UNDEFINED}, + {ExifInterface.ORIENTATION_UNDEFINED, 270, ExifInterface.ORIENTATION_UNDEFINED}, + {ExifInterface.ORIENTATION_UNDEFINED, 540, ExifInterface.ORIENTATION_UNDEFINED}, + {ExifInterface.ORIENTATION_NORMAL, -90, ExifInterface.ORIENTATION_ROTATE_270}, + {ExifInterface.ORIENTATION_NORMAL, 0, ExifInterface.ORIENTATION_NORMAL}, + {ExifInterface.ORIENTATION_NORMAL, 90, ExifInterface.ORIENTATION_ROTATE_90}, + {ExifInterface.ORIENTATION_NORMAL, 180, ExifInterface.ORIENTATION_ROTATE_180}, + {ExifInterface.ORIENTATION_NORMAL, 270, ExifInterface.ORIENTATION_ROTATE_270}, + {ExifInterface.ORIENTATION_NORMAL, 540, ExifInterface.ORIENTATION_ROTATE_180}, + {ExifInterface.ORIENTATION_ROTATE_90, -90, ExifInterface.ORIENTATION_NORMAL}, + {ExifInterface.ORIENTATION_ROTATE_90, 0, ExifInterface.ORIENTATION_ROTATE_90}, + {ExifInterface.ORIENTATION_ROTATE_90, 90, ExifInterface.ORIENTATION_ROTATE_180}, + {ExifInterface.ORIENTATION_ROTATE_90, 180 , ExifInterface.ORIENTATION_ROTATE_270}, + {ExifInterface.ORIENTATION_ROTATE_90, 270, ExifInterface.ORIENTATION_NORMAL}, + {ExifInterface.ORIENTATION_ROTATE_90, 540, ExifInterface.ORIENTATION_ROTATE_270}, + {ExifInterface.ORIENTATION_ROTATE_180, -90, ExifInterface.ORIENTATION_ROTATE_90}, + {ExifInterface.ORIENTATION_ROTATE_180, 0, ExifInterface.ORIENTATION_ROTATE_180}, + {ExifInterface.ORIENTATION_ROTATE_180, 90, ExifInterface.ORIENTATION_ROTATE_270}, + {ExifInterface.ORIENTATION_ROTATE_180, 180, ExifInterface.ORIENTATION_NORMAL}, + {ExifInterface.ORIENTATION_ROTATE_180, 270, ExifInterface.ORIENTATION_ROTATE_90}, + {ExifInterface.ORIENTATION_ROTATE_180, 540, ExifInterface.ORIENTATION_NORMAL}, + {ExifInterface.ORIENTATION_ROTATE_270, -90, ExifInterface.ORIENTATION_ROTATE_180}, + {ExifInterface.ORIENTATION_ROTATE_270, 0, ExifInterface.ORIENTATION_ROTATE_270}, + {ExifInterface.ORIENTATION_ROTATE_270, 90, ExifInterface.ORIENTATION_NORMAL}, + {ExifInterface.ORIENTATION_ROTATE_270, 180, ExifInterface.ORIENTATION_ROTATE_90}, + {ExifInterface.ORIENTATION_ROTATE_270, 270, ExifInterface.ORIENTATION_ROTATE_180}, + {ExifInterface.ORIENTATION_ROTATE_270, 540, ExifInterface.ORIENTATION_ROTATE_90}, + {ExifInterface.ORIENTATION_FLIP_VERTICAL, -90, ExifInterface.ORIENTATION_TRANSVERSE}, + {ExifInterface.ORIENTATION_FLIP_VERTICAL, 0, ExifInterface.ORIENTATION_FLIP_VERTICAL}, + {ExifInterface.ORIENTATION_FLIP_VERTICAL, 90, ExifInterface.ORIENTATION_TRANSPOSE}, + {ExifInterface.ORIENTATION_FLIP_VERTICAL, 180, + ExifInterface.ORIENTATION_FLIP_HORIZONTAL}, + {ExifInterface.ORIENTATION_FLIP_VERTICAL, 270, ExifInterface.ORIENTATION_TRANSVERSE}, + {ExifInterface.ORIENTATION_FLIP_VERTICAL, 540, + ExifInterface.ORIENTATION_FLIP_HORIZONTAL}, + {ExifInterface.ORIENTATION_FLIP_HORIZONTAL, -90, ExifInterface.ORIENTATION_TRANSPOSE}, + {ExifInterface.ORIENTATION_FLIP_HORIZONTAL, 0, + ExifInterface.ORIENTATION_FLIP_HORIZONTAL}, + {ExifInterface.ORIENTATION_FLIP_HORIZONTAL, 90, ExifInterface.ORIENTATION_TRANSVERSE}, + {ExifInterface.ORIENTATION_FLIP_HORIZONTAL, 180, + ExifInterface.ORIENTATION_FLIP_VERTICAL}, + {ExifInterface.ORIENTATION_FLIP_HORIZONTAL, 270, ExifInterface.ORIENTATION_TRANSPOSE}, + {ExifInterface.ORIENTATION_FLIP_HORIZONTAL, 540, + ExifInterface.ORIENTATION_FLIP_VERTICAL}, + {ExifInterface.ORIENTATION_TRANSPOSE, -90, ExifInterface.ORIENTATION_FLIP_VERTICAL}, + {ExifInterface.ORIENTATION_TRANSPOSE, 0, ExifInterface.ORIENTATION_TRANSPOSE}, + {ExifInterface.ORIENTATION_TRANSPOSE, 90, ExifInterface.ORIENTATION_FLIP_HORIZONTAL}, + {ExifInterface.ORIENTATION_TRANSPOSE, 180, ExifInterface.ORIENTATION_TRANSVERSE}, + {ExifInterface.ORIENTATION_TRANSPOSE, 270, ExifInterface.ORIENTATION_FLIP_VERTICAL}, + {ExifInterface.ORIENTATION_TRANSPOSE, 540, ExifInterface.ORIENTATION_TRANSVERSE}, + {ExifInterface.ORIENTATION_TRANSVERSE, -90, ExifInterface.ORIENTATION_FLIP_HORIZONTAL}, + {ExifInterface.ORIENTATION_TRANSVERSE, 0, ExifInterface.ORIENTATION_TRANSVERSE}, + {ExifInterface.ORIENTATION_TRANSVERSE, 90, ExifInterface.ORIENTATION_FLIP_VERTICAL}, + {ExifInterface.ORIENTATION_TRANSVERSE, 180, ExifInterface.ORIENTATION_TRANSPOSE}, + {ExifInterface.ORIENTATION_TRANSVERSE, 270, ExifInterface.ORIENTATION_FLIP_HORIZONTAL}, + {ExifInterface.ORIENTATION_TRANSVERSE, 540, ExifInterface.ORIENTATION_TRANSPOSE}, + }; + private static final int[][] TEST_FLIP_VERTICALLY_STATE_MACHINE = { + {ExifInterface.ORIENTATION_UNDEFINED, ExifInterface.ORIENTATION_UNDEFINED}, + {ExifInterface.ORIENTATION_NORMAL, ExifInterface.ORIENTATION_FLIP_VERTICAL}, + {ExifInterface.ORIENTATION_ROTATE_90, ExifInterface.ORIENTATION_TRANSVERSE}, + {ExifInterface.ORIENTATION_ROTATE_180, ExifInterface.ORIENTATION_FLIP_HORIZONTAL}, + {ExifInterface.ORIENTATION_ROTATE_270, ExifInterface.ORIENTATION_TRANSPOSE}, + {ExifInterface.ORIENTATION_FLIP_VERTICAL, ExifInterface.ORIENTATION_NORMAL}, + {ExifInterface.ORIENTATION_FLIP_HORIZONTAL, ExifInterface.ORIENTATION_ROTATE_180}, + {ExifInterface.ORIENTATION_TRANSPOSE, ExifInterface.ORIENTATION_ROTATE_270}, + {ExifInterface.ORIENTATION_TRANSVERSE, ExifInterface.ORIENTATION_ROTATE_90} + }; + private static final int[][] TEST_FLIP_HORIZONTALLY_STATE_MACHINE = { + {ExifInterface.ORIENTATION_UNDEFINED, ExifInterface.ORIENTATION_UNDEFINED}, + {ExifInterface.ORIENTATION_NORMAL, ExifInterface.ORIENTATION_FLIP_HORIZONTAL}, + {ExifInterface.ORIENTATION_ROTATE_90, ExifInterface.ORIENTATION_TRANSPOSE}, + {ExifInterface.ORIENTATION_ROTATE_180, ExifInterface.ORIENTATION_FLIP_VERTICAL}, + {ExifInterface.ORIENTATION_ROTATE_270, ExifInterface.ORIENTATION_TRANSVERSE}, + {ExifInterface.ORIENTATION_FLIP_VERTICAL, ExifInterface.ORIENTATION_ROTATE_180}, + {ExifInterface.ORIENTATION_FLIP_HORIZONTAL, ExifInterface.ORIENTATION_NORMAL}, + {ExifInterface.ORIENTATION_TRANSPOSE, ExifInterface.ORIENTATION_ROTATE_90}, + {ExifInterface.ORIENTATION_TRANSVERSE, ExifInterface.ORIENTATION_ROTATE_270} + }; + private static final HashMap<Integer, Pair> FLIP_STATE_AND_ROTATION_DEGREES = new HashMap<>(); + static { + FLIP_STATE_AND_ROTATION_DEGREES.put( + ExifInterface.ORIENTATION_UNDEFINED, new Pair(false, 0)); + FLIP_STATE_AND_ROTATION_DEGREES.put( + ExifInterface.ORIENTATION_NORMAL, new Pair(false, 0)); + FLIP_STATE_AND_ROTATION_DEGREES.put( + ExifInterface.ORIENTATION_ROTATE_90, new Pair(false, 90)); + FLIP_STATE_AND_ROTATION_DEGREES.put( + ExifInterface.ORIENTATION_ROTATE_180, new Pair(false, 180)); + FLIP_STATE_AND_ROTATION_DEGREES.put( + ExifInterface.ORIENTATION_ROTATE_270, new Pair(false, 270)); + FLIP_STATE_AND_ROTATION_DEGREES.put( + ExifInterface.ORIENTATION_FLIP_HORIZONTAL, new Pair(true, 0)); + FLIP_STATE_AND_ROTATION_DEGREES.put( + ExifInterface.ORIENTATION_TRANSVERSE, new Pair(true, 90)); + FLIP_STATE_AND_ROTATION_DEGREES.put( + ExifInterface.ORIENTATION_FLIP_VERTICAL, new Pair(true, 180)); + FLIP_STATE_AND_ROTATION_DEGREES.put( + ExifInterface.ORIENTATION_TRANSPOSE, new Pair(true, 270)); + } + + private static final String[] EXIF_TAGS = { + ExifInterface.TAG_MAKE, + ExifInterface.TAG_MODEL, + ExifInterface.TAG_F_NUMBER, + ExifInterface.TAG_DATETIME_ORIGINAL, + 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_PHOTOGRAPHIC_SENSITIVITY, + 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 aperture; + public final String dateTimeOriginal; + 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(); + } + + 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); + aperture = typedArray.getFloat(9, 0f); + dateTimeOriginal = 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(); + } + } + + @Before + public void setUp() throws Exception { + for (int i = 0; i < IMAGE_RESOURCES.length; ++i) { + String outputPath = + new File(Environment.getExternalStorageDirectory(), IMAGE_FILENAMES[i]) + .getAbsolutePath(); + + InputStream inputStream = null; + FileOutputStream outputStream = null; + try { + inputStream = getContext().getResources().openRawResource(IMAGE_RESOURCES[i]); + outputStream = new FileOutputStream(outputPath); + copy(inputStream, outputStream); + } finally { + closeQuietly(inputStream); + closeQuietly(outputStream); + } + } + } + + @After + public 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(); + } + } + } + + @Test + @LargeTest + public void testReadExifDataFromExifByteOrderIIJpeg() throws Throwable { + testExifInterfaceForJpeg(EXIF_BYTE_ORDER_II_JPEG, R.array.exifbyteorderii_jpg); + } + + @Test + @LargeTest + public void testReadExifDataFromExifByteOrderMMJpeg() throws Throwable { + testExifInterfaceForJpeg(EXIF_BYTE_ORDER_MM_JPEG, R.array.exifbyteordermm_jpg); + } + + @Test + @LargeTest + public void testReadExifDataFromLgG4Iso800Dng() throws Throwable { + testExifInterfaceForRaw(LG_G4_ISO_800_DNG, R.array.lg_g4_iso_800_dng); + } + + @Test + @LargeTest + public void testDoNotFailOnCorruptedImage() throws Throwable { + // ExifInterface shouldn't raise any exceptions except an IOException when unable to open + // a file, even with a corrupted image. Generates randomly corrupted image stream for + // testing. Uses Epoch date count as random seed so that we can reproduce a broken test. + long seed = System.currentTimeMillis() / (86400 * 1000); + Log.d(TAG, "testDoNotFailOnCorruptedImage random seed: " + seed); + Random random = new Random(seed); + byte[] bytes = new byte[8096]; + ByteBuffer buffer = ByteBuffer.wrap(bytes); + for (int i = 0; i < TEST_NUMBER_OF_CORRUPTED_IMAGE_STREAMS; i++) { + buffer.clear(); + random.nextBytes(bytes); + if (!randomlyCorrupted(random)) { + buffer.put(ExifInterface.JPEG_SIGNATURE); + } + if (!randomlyCorrupted(random)) { + buffer.put(ExifInterface.MARKER_APP1); + } + buffer.putShort((short) (random.nextInt(100) + 300)); + if (!randomlyCorrupted(random)) { + buffer.put(ExifInterface.IDENTIFIER_EXIF_APP1); + } + if (!randomlyCorrupted(random)) { + buffer.putShort(ExifInterface.BYTE_ALIGN_MM); + } + if (!randomlyCorrupted(random)) { + buffer.put((byte) 0); + buffer.put(ExifInterface.START_CODE); + } + buffer.putInt(8); + + // Primary Tags + int numberOfDirectory = random.nextInt(8) + 1; + if (!randomlyCorrupted(random)) { + buffer.putShort((short) numberOfDirectory); + } + for (int j = 0; j < numberOfDirectory; j++) { + generateRandomExifTag(buffer, ExifInterface.IFD_TYPE_PRIMARY, random); + } + if (!randomlyCorrupted(random)) { + buffer.putInt(buffer.position() - 8); + } + + // Thumbnail Tags + numberOfDirectory = random.nextInt(8) + 1; + if (!randomlyCorrupted(random)) { + buffer.putShort((short) numberOfDirectory); + } + for (int j = 0; j < numberOfDirectory; j++) { + generateRandomExifTag(buffer, ExifInterface.IFD_TYPE_THUMBNAIL, random); + } + if (!randomlyCorrupted(random)) { + buffer.putInt(buffer.position() - 8); + } + + // Preview Tags + numberOfDirectory = random.nextInt(8) + 1; + if (!randomlyCorrupted(random)) { + buffer.putShort((short) numberOfDirectory); + } + for (int j = 0; j < numberOfDirectory; j++) { + generateRandomExifTag(buffer, ExifInterface.IFD_TYPE_PREVIEW, random); + } + if (!randomlyCorrupted(random)) { + buffer.putInt(buffer.position() - 8); + } + + if (!randomlyCorrupted(random)) { + buffer.put(ExifInterface.MARKER); + } + if (!randomlyCorrupted(random)) { + buffer.put(ExifInterface.MARKER_EOI); + } + + try { + new ExifInterface(new ByteArrayInputStream(bytes)); + // Always success + } catch (IOException e) { + fail("Should not reach here!"); + } + } + } + + @Test + @SmallTest + public void testSetGpsInfo() throws IOException { + final String provider = "ExifInterfaceTest"; + final long timestamp = System.currentTimeMillis(); + final float speedInMeterPerSec = 36.627533f; + Location location = new Location(provider); + location.setLatitude(TEST_LATITUDE_VALID_VALUES[TEST_LATITUDE_VALID_VALUES.length - 1]); + location.setLongitude(TEST_LONGITUDE_VALID_VALUES[TEST_LONGITUDE_VALID_VALUES.length - 1]); + location.setAltitude(TEST_ALTITUDE_VALUES[TEST_ALTITUDE_VALUES.length - 1]); + location.setSpeed(speedInMeterPerSec); + location.setTime(timestamp); + ExifInterface exif = createTestExifInterface(); + exif.setGpsInfo(location); + + double[] latLong = exif.getLatLong(); + assertNotNull(latLong); + assertEquals(TEST_LATITUDE_VALID_VALUES[TEST_LATITUDE_VALID_VALUES.length - 1], + latLong[0], DELTA); + assertEquals(TEST_LONGITUDE_VALID_VALUES[TEST_LONGITUDE_VALID_VALUES.length - 1], + latLong[1], DELTA); + assertEquals(TEST_ALTITUDE_VALUES[TEST_ALTITUDE_VALUES.length - 1], exif.getAltitude(0), + RATIONAL_DELTA); + assertEquals("K", exif.getAttribute(ExifInterface.TAG_GPS_SPEED_REF)); + assertEquals(speedInMeterPerSec, exif.getAttributeDouble(ExifInterface.TAG_GPS_SPEED, 0.0) + * 1000 / TimeUnit.HOURS.toSeconds(1), RATIONAL_DELTA); + assertEquals(provider, exif.getAttribute(ExifInterface.TAG_GPS_PROCESSING_METHOD)); + // GPS time's precision is secs. + assertEquals(TimeUnit.MILLISECONDS.toSeconds(timestamp), + TimeUnit.MILLISECONDS.toSeconds(exif.getGpsDateTime())); + } + + @Test + @SmallTest + public void testSetLatLong_withValidValues() throws IOException { + for (int i = 0; i < TEST_LAT_LONG_VALUES_ARRAY_LENGTH; i++) { + ExifInterface exif = createTestExifInterface(); + exif.setLatLong(TEST_LATITUDE_VALID_VALUES[i], TEST_LONGITUDE_VALID_VALUES[i]); + + double[] latLong = exif.getLatLong(); + assertNotNull(latLong); + assertEquals(TEST_LATITUDE_VALID_VALUES[i], latLong[0], DELTA); + assertEquals(TEST_LONGITUDE_VALID_VALUES[i], latLong[1], DELTA); + } + } + + @Test + @SmallTest + public void testSetLatLong_withInvalidLatitude() throws IOException { + for (int i = 0; i < TEST_LAT_LONG_VALUES_ARRAY_LENGTH; i++) { + ExifInterface exif = createTestExifInterface(); + try { + exif.setLatLong(TEST_LATITUDE_INVALID_VALUES[i], TEST_LONGITUDE_VALID_VALUES[i]); + fail(); + } catch (IllegalArgumentException e) { + // expected + } + assertNull(exif.getLatLong()); + assertLatLongValuesAreNotSet(exif); + } + } + + @Test + @SmallTest + public void testSetLatLong_withInvalidLongitude() throws IOException { + for (int i = 0; i < TEST_LAT_LONG_VALUES_ARRAY_LENGTH; i++) { + ExifInterface exif = createTestExifInterface(); + try { + exif.setLatLong(TEST_LATITUDE_VALID_VALUES[i], TEST_LONGITUDE_INVALID_VALUES[i]); + fail(); + } catch (IllegalArgumentException e) { + // expected + } + assertNull(exif.getLatLong()); + assertLatLongValuesAreNotSet(exif); + } + } + + @Test + @SmallTest + public void testSetAltitude() throws IOException { + for (int i = 0; i < TEST_ALTITUDE_VALUES.length; i++) { + ExifInterface exif = createTestExifInterface(); + exif.setAltitude(TEST_ALTITUDE_VALUES[i]); + assertEquals(TEST_ALTITUDE_VALUES[i], exif.getAltitude(Double.NaN), RATIONAL_DELTA); + } + } + + @Test + @SmallTest + public void testSetDateTime() throws IOException { + final String dateTimeValue = "2017:02:02 22:22:22"; + final String dateTimeOriginalValue = "2017:01:01 11:11:11"; + + File imageFile = new File( + Environment.getExternalStorageDirectory(), EXIF_BYTE_ORDER_II_JPEG); + ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath()); + exif.setAttribute(ExifInterface.TAG_DATETIME, dateTimeValue); + exif.setAttribute(ExifInterface.TAG_DATETIME_ORIGINAL, dateTimeOriginalValue); + exif.saveAttributes(); + + // Check that the DATETIME value is not overwritten by DATETIME_ORIGINAL's value. + exif = new ExifInterface(imageFile.getAbsolutePath()); + assertEquals(dateTimeValue, exif.getAttribute(ExifInterface.TAG_DATETIME)); + assertEquals(dateTimeOriginalValue, exif.getAttribute(ExifInterface.TAG_DATETIME_ORIGINAL)); + + // Now remove the DATETIME value. + exif.setAttribute(ExifInterface.TAG_DATETIME, null); + exif.saveAttributes(); + + // When the DATETIME has no value, then it should be set to DATETIME_ORIGINAL's value. + exif = new ExifInterface(imageFile.getAbsolutePath()); + assertEquals(dateTimeOriginalValue, exif.getAttribute(ExifInterface.TAG_DATETIME)); + + long currentTimeStamp = System.currentTimeMillis(); + exif.setDateTime(currentTimeStamp); + exif.saveAttributes(); + exif = new ExifInterface(imageFile.getAbsolutePath()); + assertEquals(currentTimeStamp, exif.getDateTime()); + } + + @Test + @SmallTest + public void testRotation() throws IOException { + File imageFile = new File( + Environment.getExternalStorageDirectory(), EXIF_BYTE_ORDER_II_JPEG); + ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath()); + + int num; + // Test flip vertically. + for (num = 0; num < TEST_FLIP_VERTICALLY_STATE_MACHINE.length; num++) { + exif.setAttribute(ExifInterface.TAG_ORIENTATION, + Integer.toString(TEST_FLIP_VERTICALLY_STATE_MACHINE[num][0])); + exif.flipVertically(); + exif.saveAttributes(); + exif = new ExifInterface(imageFile.getAbsolutePath()); + assertIntTag(exif, ExifInterface.TAG_ORIENTATION, + TEST_FLIP_VERTICALLY_STATE_MACHINE[num][1]); + + } + + // Test flip horizontally. + for (num = 0; num < TEST_FLIP_VERTICALLY_STATE_MACHINE.length; num++) { + exif.setAttribute(ExifInterface.TAG_ORIENTATION, + Integer.toString(TEST_FLIP_HORIZONTALLY_STATE_MACHINE[num][0])); + exif.flipHorizontally(); + exif.saveAttributes(); + exif = new ExifInterface(imageFile.getAbsolutePath()); + assertIntTag(exif, ExifInterface.TAG_ORIENTATION, + TEST_FLIP_HORIZONTALLY_STATE_MACHINE[num][1]); + + } + + // Test rotate by degrees + exif.setAttribute(ExifInterface.TAG_ORIENTATION, + Integer.toString(ExifInterface.ORIENTATION_NORMAL)); + try { + exif.rotate(108); + fail("Rotate with 108 degree should throw IllegalArgumentException"); + } catch (IllegalArgumentException e) { + // Success + } + + for (num = 0; num < TEST_ROTATION_STATE_MACHINE.length; num++) { + exif.setAttribute(ExifInterface.TAG_ORIENTATION, + Integer.toString(TEST_ROTATION_STATE_MACHINE[num][0])); + exif.rotate(TEST_ROTATION_STATE_MACHINE[num][1]); + exif.saveAttributes(); + exif = new ExifInterface(imageFile.getAbsolutePath()); + assertIntTag(exif, ExifInterface.TAG_ORIENTATION, TEST_ROTATION_STATE_MACHINE[num][2]); + } + + // Test get flip state and rotation degrees. + for (Integer key : FLIP_STATE_AND_ROTATION_DEGREES.keySet()) { + exif.setAttribute(ExifInterface.TAG_ORIENTATION, key.toString()); + exif.saveAttributes(); + exif = new ExifInterface(imageFile.getAbsolutePath()); + assertEquals(FLIP_STATE_AND_ROTATION_DEGREES.get(key).first, exif.isFlipped()); + assertEquals(FLIP_STATE_AND_ROTATION_DEGREES.get(key).second, + exif.getRotationDegrees()); + } + + // Test reset the rotation. + exif.setAttribute(ExifInterface.TAG_ORIENTATION, + Integer.toString(ExifInterface.ORIENTATION_FLIP_HORIZONTAL)); + exif.resetOrientation(); + exif.saveAttributes(); + exif = new ExifInterface(imageFile.getAbsolutePath()); + assertIntTag(exif, ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); + + } + + @Test + @SmallTest + public void testInterchangeabilityBetweenTwoIsoSpeedTags() throws IOException { + // Tests that two tags TAG_ISO_SPEED_RATINGS and TAG_PHOTOGRAPHIC_SENSITIVITY can be used + // interchangeably. + final String oldTag = ExifInterface.TAG_ISO_SPEED_RATINGS; + final String newTag = ExifInterface.TAG_PHOTOGRAPHIC_SENSITIVITY; + final String isoValue = "50"; + + ExifInterface exif = createTestExifInterface(); + exif.setAttribute(oldTag, isoValue); + assertEquals(isoValue, exif.getAttribute(oldTag)); + assertEquals(isoValue, exif.getAttribute(newTag)); + + exif = createTestExifInterface(); + exif.setAttribute(newTag, isoValue); + assertEquals(isoValue, exif.getAttribute(oldTag)); + assertEquals(isoValue, exif.getAttribute(newTag)); + } + + private void printExifTagsAndValues(String fileName, ExifInterface exifInterface) { + // Prints thumbnail information. + if (exifInterface.hasThumbnail()) { + byte[] thumbnailBytes = exifInterface.getThumbnailBytes(); + if (thumbnailBytes != null) { + Log.v(TAG, fileName + " Thumbnail size = " + thumbnailBytes.length); + Bitmap bitmap = exifInterface.getThumbnailBitmap(); + 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.getThumbnailBytes() != 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)); + + double[] latLong = exifInterface.getLatLong(); + if (latLong != null) { + 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 assertFloatTag(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(); + } + stringValue = ("".equals(stringValue)) ? null : stringValue; + + 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.getThumbnailBytes(); + assertNotNull(thumbnailBytes); + Bitmap thumbnailBitmap = exifInterface.getThumbnailBitmap(); + assertNotNull(thumbnailBitmap); + assertEquals(expectedValue.thumbnailWidth, thumbnailBitmap.getWidth()); + assertEquals(expectedValue.thumbnailHeight, thumbnailBitmap.getHeight()); + } else { + assertNull(exifInterface.getThumbnail()); + } + + // Checks GPS information. + double[] latLong = exifInterface.getLatLong(); + assertEquals(expectedValue.hasLatLong, latLong != null); + 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); + assertFloatTag(exifInterface, ExifInterface.TAG_F_NUMBER, expectedValue.aperture); + assertStringTag(exifInterface, ExifInterface.TAG_DATETIME_ORIGINAL, + expectedValue.dateTimeOriginal); + assertFloatTag(exifInterface, ExifInterface.TAG_EXPOSURE_TIME, expectedValue.exposureTime); + assertFloatTag(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_PHOTOGRAPHIC_SENSITIVITY, + expectedValue.iso); + assertIntTag(exifInterface, ExifInterface.TAG_ORIENTATION, expectedValue.orientation); + assertIntTag(exifInterface, ExifInterface.TAG_WHITE_BALANCE, expectedValue.whiteBalance); + } + + private void testExifInterfaceCommon(String fileName, ExpectedValue expectedValue) + throws IOException { + File imageFile = new File(Environment.getExternalStorageDirectory(), fileName); + String verboseTag = imageFile.getName(); + + // Creates via path. + ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath()); + assertNotNull(exifInterface); + compareWithExpectedValue(exifInterface, expectedValue, verboseTag); + + InputStream in = null; + // Creates via InputStream. + try { + in = new BufferedInputStream(new FileInputStream(imageFile.getAbsolutePath())); + exifInterface = new ExifInterface(in); + compareWithExpectedValue(exifInterface, expectedValue, verboseTag); + } finally { + closeQuietly(in); + } + } + + private void testSaveAttributes_withFileName(String fileName, ExpectedValue expectedValue) + throws IOException { + File imageFile = new File(Environment.getExternalStorageDirectory(), fileName); + 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 testExifInterfaceForJpeg(String fileName, int typedArrayResourceId) + throws IOException { + ExpectedValue expectedValue = new ExpectedValue( + getContext().getResources().obtainTypedArray(typedArrayResourceId)); + + // Test for reading from external data storage. + testExifInterfaceCommon(fileName, expectedValue); + + // Test for saving attributes. + testSaveAttributes_withFileName(fileName, expectedValue); + } + + private void testExifInterfaceForRaw(String fileName, int typedArrayResourceId) + throws IOException { + ExpectedValue expectedValue = new ExpectedValue( + getContext().getResources().obtainTypedArray(typedArrayResourceId)); + + // Test for reading from external data storage. + testExifInterfaceCommon(fileName, expectedValue); + + // Since ExifInterface does not support for saving attributes for RAW files, do not test + // about writing back in here. + } + + private void generateRandomExifTag(ByteBuffer buffer, int ifdType, Random random) { + ExifInterface.ExifTag[] tagGroup = ExifInterface.EXIF_TAGS[ifdType]; + ExifInterface.ExifTag tag = tagGroup[random.nextInt(tagGroup.length)]; + if (!randomlyCorrupted(random)) { + buffer.putShort((short) tag.number); + } + int dataFormat = random.nextInt(ExifInterface.IFD_FORMAT_NAMES.length); + if (!randomlyCorrupted(random)) { + buffer.putShort((short) dataFormat); + } + buffer.putInt(1); + int dataLength = ExifInterface.IFD_FORMAT_BYTES_PER_FORMAT[dataFormat]; + if (dataLength > 4) { + buffer.putShort((short) random.nextInt(8096 - dataLength)); + buffer.position(buffer.position() + 2); + } else { + buffer.position(buffer.position() + 4); + } + } + + private boolean randomlyCorrupted(Random random) { + // Corrupts somewhere in a possibility of 1/500. + return random.nextInt(500) == 0; + } + + private void closeQuietly(Closeable closeable) { + if (closeable != null) { + try { + closeable.close(); + } catch (RuntimeException rethrown) { + throw rethrown; + } catch (Exception ignored) { + } + } + } + + private int copy(InputStream in, OutputStream out) throws IOException { + int total = 0; + byte[] buffer = new byte[8192]; + int c; + while ((c = in.read(buffer)) != -1) { + total += c; + out.write(buffer, 0, c); + } + return total; + } + + private void assertLatLongValuesAreNotSet(ExifInterface exif) { + assertNull(exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE)); + assertNull(exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF)); + assertNull(exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE)); + assertNull(exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF)); + } + + private ExifInterface createTestExifInterface() throws IOException { + File image = File.createTempFile(TEST_TEMP_FILE_NAME, ".jpg"); + image.deleteOnExit(); + return new ExifInterface(image.getAbsolutePath()); + } +} |