• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.media.cts;
18 
19 import android.content.res.TypedArray;
20 import android.graphics.Bitmap;
21 import android.media.ExifInterface;
22 import android.os.Environment;
23 import android.os.FileUtils;
24 import android.platform.test.annotations.AppModeFull;
25 import android.system.ErrnoException;
26 import android.system.Os;
27 import android.system.OsConstants;
28 import android.test.AndroidTestCase;
29 import android.util.Log;
30 
31 import libcore.io.IoUtils;
32 
33 import java.io.BufferedInputStream;
34 import java.io.ByteArrayInputStream;
35 import java.io.File;
36 import java.io.FileDescriptor;
37 import java.io.FileInputStream;
38 import java.io.FileOutputStream;
39 import java.io.IOException;
40 import java.io.InputStream;
41 import java.io.OutputStream;
42 import java.nio.charset.StandardCharsets;
43 
44 @AppModeFull(reason = "Instant apps cannot access the SD card")
45 public class ExifInterfaceTest extends AndroidTestCase {
46     private static final String TAG = ExifInterface.class.getSimpleName();
47     private static final boolean VERBOSE = false;  // lots of logging
48 
49     private static final double DIFFERENCE_TOLERANCE = .001;
50 
51     private static final String EXTERNAL_BASE_DIRECTORY = "/test/images/";
52 
53     // This base directory is needed for the files listed below.
54     // These files will be available for download in Android O release.
55     // Link: https://source.android.com/compatibility/cts/downloads.html#cts-media-files
56     private static final String EXIF_BYTE_ORDER_II_JPEG = "image_exif_byte_order_ii.jpg";
57     private static final String EXIF_BYTE_ORDER_MM_JPEG = "image_exif_byte_order_mm.jpg";
58     private static final String LG_G4_ISO_800_DNG = "lg_g4_iso_800.dng";
59     private static final String LG_G4_ISO_800_JPG = "lg_g4_iso_800.jpg";
60     private static final String SONY_RX_100_ARW = "sony_rx_100.arw";
61     private static final String CANON_G7X_CR2 = "canon_g7x.cr2";
62     private static final String FUJI_X20_RAF = "fuji_x20.raf";
63     private static final String NIKON_1AW1_NEF = "nikon_1aw1.nef";
64     private static final String NIKON_P330_NRW = "nikon_p330.nrw";
65     private static final String OLYMPUS_E_PL3_ORF = "olympus_e_pl3.orf";
66     private static final String PANASONIC_GM5_RW2 = "panasonic_gm5.rw2";
67     private static final String PENTAX_K5_PEF = "pentax_k5.pef";
68     private static final String SAMSUNG_NX3000_SRW = "samsung_nx3000.srw";
69     private static final String VOLANTIS_JPEG = "volantis.jpg";
70 
71     private static final String[] EXIF_TAGS = {
72             ExifInterface.TAG_MAKE,
73             ExifInterface.TAG_MODEL,
74             ExifInterface.TAG_F_NUMBER,
75             ExifInterface.TAG_DATETIME_ORIGINAL,
76             ExifInterface.TAG_EXPOSURE_TIME,
77             ExifInterface.TAG_FLASH,
78             ExifInterface.TAG_FOCAL_LENGTH,
79             ExifInterface.TAG_GPS_ALTITUDE,
80             ExifInterface.TAG_GPS_ALTITUDE_REF,
81             ExifInterface.TAG_GPS_DATESTAMP,
82             ExifInterface.TAG_GPS_LATITUDE,
83             ExifInterface.TAG_GPS_LATITUDE_REF,
84             ExifInterface.TAG_GPS_LONGITUDE,
85             ExifInterface.TAG_GPS_LONGITUDE_REF,
86             ExifInterface.TAG_GPS_PROCESSING_METHOD,
87             ExifInterface.TAG_GPS_TIMESTAMP,
88             ExifInterface.TAG_IMAGE_LENGTH,
89             ExifInterface.TAG_IMAGE_WIDTH,
90             ExifInterface.TAG_ISO_SPEED_RATINGS,
91             ExifInterface.TAG_ORIENTATION,
92             ExifInterface.TAG_WHITE_BALANCE
93     };
94 
95     private static class ExpectedValue {
96         // Thumbnail information.
97         public final boolean hasThumbnail;
98         public final int thumbnailWidth;
99         public final int thumbnailHeight;
100         public final boolean isThumbnailCompressed;
101         public final int thumbnailOffset;
102         public final int thumbnailLength;
103 
104         // GPS information.
105         public final boolean hasLatLong;
106         public final float latitude;
107         public final int latitudeOffset;
108         public final int latitudeLength;
109         public final float longitude;
110         public final float altitude;
111 
112         // Values.
113         public final String make;
114         public final String model;
115         public final float aperture;
116         public final String dateTimeOriginal;
117         public final float exposureTime;
118         public final float flash;
119         public final String focalLength;
120         public final String gpsAltitude;
121         public final String gpsAltitudeRef;
122         public final String gpsDatestamp;
123         public final String gpsLatitude;
124         public final String gpsLatitudeRef;
125         public final String gpsLongitude;
126         public final String gpsLongitudeRef;
127         public final String gpsProcessingMethod;
128         public final String gpsTimestamp;
129         public final int imageLength;
130         public final int imageWidth;
131         public final String iso;
132         public final int orientation;
133         public final int whiteBalance;
134 
135         // XMP information.
136         public final boolean hasXmp;
137         public final int xmpOffset;
138         public final int xmpLength;
139 
getString(TypedArray typedArray, int index)140         private static String getString(TypedArray typedArray, int index) {
141             String stringValue = typedArray.getString(index);
142             if (stringValue == null || stringValue.equals("")) {
143                 return null;
144             }
145             return stringValue.trim();
146         }
147 
ExpectedValue(TypedArray typedArray)148         public ExpectedValue(TypedArray typedArray) {
149             int index = 0;
150 
151             // Reads thumbnail information.
152             hasThumbnail = typedArray.getBoolean(index++, false);
153             thumbnailOffset = typedArray.getInt(index++, -1);
154             thumbnailLength = typedArray.getInt(index++, -1);
155             thumbnailWidth = typedArray.getInt(index++, 0);
156             thumbnailHeight = typedArray.getInt(index++, 0);
157             isThumbnailCompressed = typedArray.getBoolean(index++, false);
158 
159             // Reads GPS information.
160             hasLatLong = typedArray.getBoolean(index++, false);
161             latitudeOffset = typedArray.getInt(index++, -1);
162             latitudeLength = typedArray.getInt(index++, -1);
163             latitude = typedArray.getFloat(index++, 0f);
164             longitude = typedArray.getFloat(index++, 0f);
165             altitude = typedArray.getFloat(index++, 0f);
166 
167             // Reads values.
168             make = getString(typedArray, index++);
169             model = getString(typedArray, index++);
170             aperture = typedArray.getFloat(index++, 0f);
171             dateTimeOriginal = getString(typedArray, index++);
172             exposureTime = typedArray.getFloat(index++, 0f);
173             flash = typedArray.getFloat(index++, 0f);
174             focalLength = getString(typedArray, index++);
175             gpsAltitude = getString(typedArray, index++);
176             gpsAltitudeRef = getString(typedArray, index++);
177             gpsDatestamp = getString(typedArray, index++);
178             gpsLatitude = getString(typedArray, index++);
179             gpsLatitudeRef = getString(typedArray, index++);
180             gpsLongitude = getString(typedArray, index++);
181             gpsLongitudeRef = getString(typedArray, index++);
182             gpsProcessingMethod = getString(typedArray, index++);
183             gpsTimestamp = getString(typedArray, index++);
184             imageLength = typedArray.getInt(index++, 0);
185             imageWidth = typedArray.getInt(index++, 0);
186             iso = getString(typedArray, index++);
187             orientation = typedArray.getInt(index++, 0);
188             whiteBalance = typedArray.getInt(index++, 0);
189 
190             // Reads XMP information.
191             hasXmp = typedArray.getBoolean(index++, false);
192             xmpOffset = typedArray.getInt(index++, 0);
193             xmpLength = typedArray.getInt(index++, 0);
194 
195             typedArray.recycle();
196         }
197     }
198 
printExifTagsAndValues(String fileName, ExifInterface exifInterface)199     private void printExifTagsAndValues(String fileName, ExifInterface exifInterface) {
200         // Prints thumbnail information.
201         if (exifInterface.hasThumbnail()) {
202             byte[] thumbnailBytes = exifInterface.getThumbnailBytes();
203             if (thumbnailBytes != null) {
204                 Log.v(TAG, fileName + " Thumbnail size = " + thumbnailBytes.length);
205                 Bitmap bitmap = exifInterface.getThumbnailBitmap();
206                 if (bitmap == null) {
207                     Log.e(TAG, fileName + " Corrupted thumbnail!");
208                 } else {
209                     Log.v(TAG, fileName + " Thumbnail size: " + bitmap.getWidth() + ", "
210                             + bitmap.getHeight());
211                 }
212             } else {
213                 Log.e(TAG, fileName + " Unexpected result: No thumbnails were found. "
214                         + "A thumbnail is expected.");
215             }
216         } else {
217             if (exifInterface.getThumbnailBytes() != null) {
218                 Log.e(TAG, fileName + " Unexpected result: A thumbnail was found. "
219                         + "No thumbnail is expected.");
220             } else {
221                 Log.v(TAG, fileName + " No thumbnail");
222             }
223         }
224 
225         // Prints GPS information.
226         Log.v(TAG, fileName + " Altitude = " + exifInterface.getAltitude(.0));
227 
228         float[] latLong = new float[2];
229         if (exifInterface.getLatLong(latLong)) {
230             Log.v(TAG, fileName + " Latitude = " + latLong[0]);
231             Log.v(TAG, fileName + " Longitude = " + latLong[1]);
232         } else {
233             Log.v(TAG, fileName + " No latlong data");
234         }
235 
236         // Prints values.
237         for (String tagKey : EXIF_TAGS) {
238             String tagValue = exifInterface.getAttribute(tagKey);
239             Log.v(TAG, fileName + " Key{" + tagKey + "} = '" + tagValue + "'");
240         }
241     }
242 
assertIntTag(ExifInterface exifInterface, String tag, int expectedValue)243     private void assertIntTag(ExifInterface exifInterface, String tag, int expectedValue) {
244         int intValue = exifInterface.getAttributeInt(tag, 0);
245         assertEquals(expectedValue, intValue);
246     }
247 
assertFloatTag(ExifInterface exifInterface, String tag, float expectedValue)248     private void assertFloatTag(ExifInterface exifInterface, String tag, float expectedValue) {
249         double doubleValue = exifInterface.getAttributeDouble(tag, 0.0);
250         assertEquals(expectedValue, doubleValue, DIFFERENCE_TOLERANCE);
251     }
252 
assertStringTag(ExifInterface exifInterface, String tag, String expectedValue)253     private void assertStringTag(ExifInterface exifInterface, String tag, String expectedValue) {
254         String stringValue = exifInterface.getAttribute(tag);
255         if (stringValue != null) {
256             stringValue = stringValue.trim();
257         }
258         stringValue = (stringValue == "") ? null : stringValue;
259 
260         assertEquals(expectedValue, stringValue);
261     }
262 
compareWithExpectedValue(ExifInterface exifInterface, ExpectedValue expectedValue, String verboseTag, boolean assertRanges)263     private void compareWithExpectedValue(ExifInterface exifInterface,
264             ExpectedValue expectedValue, String verboseTag, boolean assertRanges) {
265         if (VERBOSE) {
266             printExifTagsAndValues(verboseTag, exifInterface);
267         }
268         // Checks a thumbnail image.
269         assertEquals(expectedValue.hasThumbnail, exifInterface.hasThumbnail());
270         if (expectedValue.hasThumbnail) {
271             assertNotNull(exifInterface.getThumbnailRange());
272             if (assertRanges) {
273                 final long[] thumbnailRange = exifInterface.getThumbnailRange();
274                 assertEquals(expectedValue.thumbnailOffset, thumbnailRange[0]);
275                 assertEquals(expectedValue.thumbnailLength, thumbnailRange[1]);
276             }
277             byte[] thumbnailBytes = exifInterface.getThumbnailBytes();
278             assertNotNull(thumbnailBytes);
279             Bitmap thumbnailBitmap = exifInterface.getThumbnailBitmap();
280             assertNotNull(thumbnailBitmap);
281             assertEquals(expectedValue.thumbnailWidth, thumbnailBitmap.getWidth());
282             assertEquals(expectedValue.thumbnailHeight, thumbnailBitmap.getHeight());
283             assertEquals(expectedValue.isThumbnailCompressed,
284                     exifInterface.isThumbnailCompressed());
285         } else {
286             assertNull(exifInterface.getThumbnailRange());
287             assertNull(exifInterface.getThumbnail());
288         }
289 
290         // Checks GPS information.
291         float[] latLong = new float[2];
292         assertEquals(expectedValue.hasLatLong, exifInterface.getLatLong(latLong));
293         if (expectedValue.hasLatLong) {
294             assertNotNull(exifInterface.getAttributeRange(ExifInterface.TAG_GPS_LATITUDE));
295             if (assertRanges) {
296                 final long[] latitudeRange = exifInterface
297                         .getAttributeRange(ExifInterface.TAG_GPS_LATITUDE);
298                 assertEquals(expectedValue.latitudeOffset, latitudeRange[0]);
299                 assertEquals(expectedValue.latitudeLength, latitudeRange[1]);
300             }
301             assertEquals(expectedValue.latitude, latLong[0], DIFFERENCE_TOLERANCE);
302             assertEquals(expectedValue.longitude, latLong[1], DIFFERENCE_TOLERANCE);
303             assertTrue(exifInterface.hasAttribute(ExifInterface.TAG_GPS_LATITUDE));
304             assertTrue(exifInterface.hasAttribute(ExifInterface.TAG_GPS_LONGITUDE));
305         } else {
306             assertNull(exifInterface.getAttributeRange(ExifInterface.TAG_GPS_LATITUDE));
307             assertFalse(exifInterface.hasAttribute(ExifInterface.TAG_GPS_LATITUDE));
308             assertFalse(exifInterface.hasAttribute(ExifInterface.TAG_GPS_LONGITUDE));
309         }
310         assertEquals(expectedValue.altitude, exifInterface.getAltitude(.0), DIFFERENCE_TOLERANCE);
311 
312         // Checks values.
313         assertStringTag(exifInterface, ExifInterface.TAG_MAKE, expectedValue.make);
314         assertStringTag(exifInterface, ExifInterface.TAG_MODEL, expectedValue.model);
315         assertFloatTag(exifInterface, ExifInterface.TAG_F_NUMBER, expectedValue.aperture);
316         assertStringTag(exifInterface, ExifInterface.TAG_DATETIME_ORIGINAL,
317                 expectedValue.dateTimeOriginal);
318         assertFloatTag(exifInterface, ExifInterface.TAG_EXPOSURE_TIME, expectedValue.exposureTime);
319         assertFloatTag(exifInterface, ExifInterface.TAG_FLASH, expectedValue.flash);
320         assertStringTag(exifInterface, ExifInterface.TAG_FOCAL_LENGTH, expectedValue.focalLength);
321         assertStringTag(exifInterface, ExifInterface.TAG_GPS_ALTITUDE, expectedValue.gpsAltitude);
322         assertStringTag(exifInterface, ExifInterface.TAG_GPS_ALTITUDE_REF,
323                 expectedValue.gpsAltitudeRef);
324         assertStringTag(exifInterface, ExifInterface.TAG_GPS_DATESTAMP, expectedValue.gpsDatestamp);
325         assertStringTag(exifInterface, ExifInterface.TAG_GPS_LATITUDE, expectedValue.gpsLatitude);
326         assertStringTag(exifInterface, ExifInterface.TAG_GPS_LATITUDE_REF,
327                 expectedValue.gpsLatitudeRef);
328         assertStringTag(exifInterface, ExifInterface.TAG_GPS_LONGITUDE, expectedValue.gpsLongitude);
329         assertStringTag(exifInterface, ExifInterface.TAG_GPS_LONGITUDE_REF,
330                 expectedValue.gpsLongitudeRef);
331         assertStringTag(exifInterface, ExifInterface.TAG_GPS_PROCESSING_METHOD,
332                 expectedValue.gpsProcessingMethod);
333         assertStringTag(exifInterface, ExifInterface.TAG_GPS_TIMESTAMP, expectedValue.gpsTimestamp);
334         assertIntTag(exifInterface, ExifInterface.TAG_IMAGE_LENGTH, expectedValue.imageLength);
335         assertIntTag(exifInterface, ExifInterface.TAG_IMAGE_WIDTH, expectedValue.imageWidth);
336         assertStringTag(exifInterface, ExifInterface.TAG_ISO_SPEED_RATINGS, expectedValue.iso);
337         assertIntTag(exifInterface, ExifInterface.TAG_ORIENTATION, expectedValue.orientation);
338         assertIntTag(exifInterface, ExifInterface.TAG_WHITE_BALANCE, expectedValue.whiteBalance);
339 
340         if (expectedValue.hasXmp) {
341             assertNotNull(exifInterface.getAttributeRange(ExifInterface.TAG_XMP));
342             if (assertRanges) {
343                 final long[] xmpRange = exifInterface.getAttributeRange(ExifInterface.TAG_XMP);
344                 assertEquals(expectedValue.xmpOffset, xmpRange[0]);
345                 assertEquals(expectedValue.xmpLength, xmpRange[1]);
346             }
347             final String xmp = new String(exifInterface.getAttributeBytes(ExifInterface.TAG_XMP),
348                     StandardCharsets.UTF_8);
349             // We're only interested in confirming that we were able to extract
350             // valid XMP data, which must always include this XML tag; a full
351             // XMP parser is beyond the scope of ExifInterface. See XMP
352             // Specification Part 1, Section C.2.2 for additional details.
353             if (!xmp.contains("<rdf:RDF")) {
354                 fail("Invalid XMP: " + xmp);
355             }
356         } else {
357             assertNull(exifInterface.getAttributeRange(ExifInterface.TAG_XMP));
358         }
359     }
360 
testExifInterfaceCommon(String fileName, ExpectedValue expectedValue)361     private void testExifInterfaceCommon(String fileName, ExpectedValue expectedValue)
362             throws IOException {
363         File imageFile = new File(Environment.getExternalStorageDirectory(), fileName);
364         String verboseTag = imageFile.getName();
365 
366         // Creates via path.
367         ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath());
368         assertNotNull(exifInterface);
369         compareWithExpectedValue(exifInterface, expectedValue, verboseTag, true);
370 
371         // Creates via file.
372         exifInterface = new ExifInterface(imageFile);
373         compareWithExpectedValue(exifInterface, expectedValue, verboseTag, true);
374 
375         InputStream in = null;
376         // Creates via InputStream.
377         try {
378             in = new BufferedInputStream(new FileInputStream(imageFile.getAbsolutePath()));
379             exifInterface = new ExifInterface(in);
380             compareWithExpectedValue(exifInterface, expectedValue, verboseTag, true);
381         } finally {
382             IoUtils.closeQuietly(in);
383         }
384 
385         // Creates via FileDescriptor.
386         FileDescriptor fd = null;
387         try {
388             fd = Os.open(imageFile.getAbsolutePath(), OsConstants.O_RDONLY, 0600);
389             exifInterface = new ExifInterface(fd);
390             compareWithExpectedValue(exifInterface, expectedValue, verboseTag, true);
391         } catch (ErrnoException e) {
392             throw e.rethrowAsIOException();
393         } finally {
394             IoUtils.closeQuietly(fd);
395         }
396     }
397 
testSaveAttributes_withFileName(String fileName, ExpectedValue expectedValue)398     private void testSaveAttributes_withFileName(String fileName, ExpectedValue expectedValue)
399             throws IOException {
400         File srcFile = new File(Environment.getExternalStorageDirectory(), fileName);
401         File imageFile = clone(srcFile);
402         String verboseTag = imageFile.getName();
403 
404         ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath());
405         exifInterface.saveAttributes();
406         exifInterface = new ExifInterface(imageFile.getAbsolutePath());
407         compareWithExpectedValue(exifInterface, expectedValue, verboseTag, false);
408 
409         // Test for modifying one attribute.
410         String backupValue = exifInterface.getAttribute(ExifInterface.TAG_MAKE);
411         exifInterface.setAttribute(ExifInterface.TAG_MAKE, "abc");
412         exifInterface.saveAttributes();
413         exifInterface = new ExifInterface(imageFile.getAbsolutePath());
414         assertEquals("abc", exifInterface.getAttribute(ExifInterface.TAG_MAKE));
415         // Restore the backup value.
416         exifInterface.setAttribute(ExifInterface.TAG_MAKE, backupValue);
417         exifInterface.saveAttributes();
418         exifInterface = new ExifInterface(imageFile.getAbsolutePath());
419         compareWithExpectedValue(exifInterface, expectedValue, verboseTag, false);
420     }
421 
testSaveAttributes_withFileDescriptor(String fileName, ExpectedValue expectedValue)422     private void testSaveAttributes_withFileDescriptor(String fileName, ExpectedValue expectedValue)
423             throws IOException {
424         File srcFile = new File(Environment.getExternalStorageDirectory(), fileName);
425         File imageFile = clone(srcFile);
426         String verboseTag = imageFile.getName();
427 
428         FileDescriptor fd = null;
429         try {
430             fd = Os.open(imageFile.getAbsolutePath(), OsConstants.O_RDWR, 0600);
431             ExifInterface exifInterface = new ExifInterface(fd);
432             exifInterface.saveAttributes();
433             Os.lseek(fd, 0, OsConstants.SEEK_SET);
434             exifInterface = new ExifInterface(fd);
435             compareWithExpectedValue(exifInterface, expectedValue, verboseTag, false);
436 
437             // Test for modifying one attribute.
438             String backupValue = exifInterface.getAttribute(ExifInterface.TAG_MAKE);
439             exifInterface.setAttribute(ExifInterface.TAG_MAKE, "abc");
440             exifInterface.saveAttributes();
441             Os.lseek(fd, 0, OsConstants.SEEK_SET);
442             exifInterface = new ExifInterface(fd);
443             assertEquals("abc", exifInterface.getAttribute(ExifInterface.TAG_MAKE));
444             // Restore the backup value.
445             exifInterface.setAttribute(ExifInterface.TAG_MAKE, backupValue);
446             exifInterface.saveAttributes();
447             Os.lseek(fd, 0, OsConstants.SEEK_SET);
448             exifInterface = new ExifInterface(fd);
449             compareWithExpectedValue(exifInterface, expectedValue, verboseTag, false);
450         } catch (ErrnoException e) {
451             throw e.rethrowAsIOException();
452         } finally {
453             IoUtils.closeQuietly(fd);
454         }
455     }
456 
testExifInterfaceForJpeg(String fileName, int typedArrayResourceId)457     private void testExifInterfaceForJpeg(String fileName, int typedArrayResourceId)
458             throws IOException {
459         ExpectedValue expectedValue = new ExpectedValue(
460                 getContext().getResources().obtainTypedArray(typedArrayResourceId));
461 
462         // Test for reading from external data storage.
463         fileName = EXTERNAL_BASE_DIRECTORY + fileName;
464         testExifInterfaceCommon(fileName, expectedValue);
465 
466         // Test for saving attributes.
467         testSaveAttributes_withFileName(fileName, expectedValue);
468         testSaveAttributes_withFileDescriptor(fileName, expectedValue);
469     }
470 
testExifInterfaceForRaw(String fileName, int typedArrayResourceId)471     private void testExifInterfaceForRaw(String fileName, int typedArrayResourceId)
472             throws IOException {
473         ExpectedValue expectedValue = new ExpectedValue(
474                 getContext().getResources().obtainTypedArray(typedArrayResourceId));
475 
476         // Test for reading from external data storage.
477         fileName = EXTERNAL_BASE_DIRECTORY + fileName;
478         testExifInterfaceCommon(fileName, expectedValue);
479 
480         // Since ExifInterface does not support for saving attributes for RAW files, do not test
481         // about writing back in here.
482     }
483 
testReadExifDataFromExifByteOrderIIJpeg()484     public void testReadExifDataFromExifByteOrderIIJpeg() throws Throwable {
485         testExifInterfaceForJpeg(EXIF_BYTE_ORDER_II_JPEG, R.array.exifbyteorderii_jpg);
486     }
487 
testReadExifDataFromExifByteOrderMMJpeg()488     public void testReadExifDataFromExifByteOrderMMJpeg() throws Throwable {
489         testExifInterfaceForJpeg(EXIF_BYTE_ORDER_MM_JPEG, R.array.exifbyteordermm_jpg);
490     }
491 
testReadExifDataFromLgG4Iso800Dng()492     public void testReadExifDataFromLgG4Iso800Dng() throws Throwable {
493         testExifInterfaceForRaw(LG_G4_ISO_800_DNG, R.array.lg_g4_iso_800_dng);
494     }
495 
testReadExifDataFromLgG4Iso800Jpg()496     public void testReadExifDataFromLgG4Iso800Jpg() throws Throwable {
497         stageFile(R.raw.lg_g4_iso_800, new File(Environment.getExternalStorageDirectory(),
498                 EXTERNAL_BASE_DIRECTORY + LG_G4_ISO_800_JPG));
499         testExifInterfaceForJpeg(LG_G4_ISO_800_JPG, R.array.lg_g4_iso_800_jpg);
500     }
501 
testDoNotFailOnCorruptedImage()502     public void testDoNotFailOnCorruptedImage() throws Throwable {
503         // To keep the compatibility with old versions of ExifInterface, even on a corrupted image,
504         // it shouldn't raise any exceptions except an IOException when unable to open a file.
505         byte[] bytes = new byte[1024];
506         try {
507             new ExifInterface(new ByteArrayInputStream(bytes));
508             // Always success
509         } catch (IOException e) {
510             fail("Should not reach here!");
511         }
512     }
513 
testReadExifDataFromVolantisJpg()514     public void testReadExifDataFromVolantisJpg() throws Throwable {
515         // Test if it is possible to parse the volantis generated JPEG smoothly.
516         testExifInterfaceForJpeg(VOLANTIS_JPEG, R.array.volantis_jpg);
517     }
518 
testReadExifDataFromSonyRX100Arw()519     public void testReadExifDataFromSonyRX100Arw() throws Throwable {
520         testExifInterfaceForRaw(SONY_RX_100_ARW, R.array.sony_rx_100_arw);
521     }
522 
testReadExifDataFromCanonG7XCr2()523     public void testReadExifDataFromCanonG7XCr2() throws Throwable {
524         testExifInterfaceForRaw(CANON_G7X_CR2, R.array.canon_g7x_cr2);
525     }
526 
testReadExifDataFromFujiX20Raf()527     public void testReadExifDataFromFujiX20Raf() throws Throwable {
528         testExifInterfaceForRaw(FUJI_X20_RAF, R.array.fuji_x20_raf);
529     }
530 
testReadExifDataFromNikon1AW1Nef()531     public void testReadExifDataFromNikon1AW1Nef() throws Throwable {
532         testExifInterfaceForRaw(NIKON_1AW1_NEF, R.array.nikon_1aw1_nef);
533     }
534 
testReadExifDataFromNikonP330Nrw()535     public void testReadExifDataFromNikonP330Nrw() throws Throwable {
536         testExifInterfaceForRaw(NIKON_P330_NRW, R.array.nikon_p330_nrw);
537     }
538 
testReadExifDataFromOlympusEPL3Orf()539     public void testReadExifDataFromOlympusEPL3Orf() throws Throwable {
540         testExifInterfaceForRaw(OLYMPUS_E_PL3_ORF, R.array.olympus_e_pl3_orf);
541     }
542 
testReadExifDataFromPanasonicGM5Rw2()543     public void testReadExifDataFromPanasonicGM5Rw2() throws Throwable {
544         testExifInterfaceForRaw(PANASONIC_GM5_RW2, R.array.panasonic_gm5_rw2);
545     }
546 
testReadExifDataFromPentaxK5Pef()547     public void testReadExifDataFromPentaxK5Pef() throws Throwable {
548         testExifInterfaceForRaw(PENTAX_K5_PEF, R.array.pentax_k5_pef);
549     }
550 
testReadExifDataFromSamsungNX3000Srw()551     public void testReadExifDataFromSamsungNX3000Srw() throws Throwable {
552         testExifInterfaceForRaw(SAMSUNG_NX3000_SRW, R.array.samsung_nx3000_srw);
553     }
554 
testSetDateTime()555     public void testSetDateTime() throws IOException {
556         final String dateTimeValue = "2017:02:02 22:22:22";
557         final String dateTimeOriginalValue = "2017:01:01 11:11:11";
558 
559         File srcFile = new File(Environment.getExternalStorageDirectory(),
560                 EXTERNAL_BASE_DIRECTORY + EXIF_BYTE_ORDER_II_JPEG);
561         File imageFile = clone(srcFile);
562 
563         FileUtils.copyFileOrThrow(srcFile, imageFile);
564         ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath());
565         exif.setAttribute(ExifInterface.TAG_DATETIME, dateTimeValue);
566         exif.setAttribute(ExifInterface.TAG_DATETIME_ORIGINAL, dateTimeOriginalValue);
567         exif.saveAttributes();
568 
569         // Check that the DATETIME value is not overwritten by DATETIME_ORIGINAL's value.
570         exif = new ExifInterface(imageFile.getAbsolutePath());
571         assertEquals(dateTimeValue, exif.getAttribute(ExifInterface.TAG_DATETIME));
572         assertEquals(dateTimeOriginalValue, exif.getAttribute(ExifInterface.TAG_DATETIME_ORIGINAL));
573 
574         // Now remove the DATETIME value.
575         exif.setAttribute(ExifInterface.TAG_DATETIME, null);
576         exif.saveAttributes();
577 
578         // When the DATETIME has no value, then it should be set to DATETIME_ORIGINAL's value.
579         exif = new ExifInterface(imageFile.getAbsolutePath());
580         assertEquals(dateTimeOriginalValue, exif.getAttribute(ExifInterface.TAG_DATETIME));
581         imageFile.delete();
582     }
583 
stageFile(int resId, File file)584     private void stageFile(int resId, File file) throws IOException {
585         try (InputStream source = getContext().getResources().openRawResource(resId);
586                 OutputStream target = new FileOutputStream(file)) {
587             FileUtils.copy(source, target);
588         }
589     }
590 
clone(File original)591     private static File clone(File original) throws IOException {
592         final File cloned = new File(original.getParentFile(),
593                 "cts_" + System.nanoTime() + "_" + original.getName());
594         FileUtils.copyFileOrThrow(original, cloned);
595         return cloned;
596     }
597 }
598