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 static android.media.ExifInterface.TAG_SUBJECT_AREA;
20 
21 import android.content.res.TypedArray;
22 import android.graphics.Bitmap;
23 import android.graphics.BitmapFactory;
24 import android.media.ExifInterface;
25 import android.os.FileUtils;
26 import android.os.StrictMode;
27 import android.platform.test.annotations.AppModeFull;
28 import android.system.ErrnoException;
29 import android.system.Os;
30 import android.system.OsConstants;
31 import android.test.AndroidTestCase;
32 import android.util.Log;
33 
34 import libcore.io.IoUtils;
35 
36 import java.io.BufferedInputStream;
37 import java.io.ByteArrayInputStream;
38 import java.io.EOFException;
39 import java.io.File;
40 import java.io.FileDescriptor;
41 import java.io.FileInputStream;
42 import java.io.IOException;
43 import java.io.InputStream;
44 import java.nio.charset.StandardCharsets;
45 
46 @NonMediaMainlineTest
47 @AppModeFull(reason = "Instant apps cannot access the SD card")
48 public class ExifInterfaceTest extends AndroidTestCase {
49     private static final String TAG = ExifInterface.class.getSimpleName();
50     private static final boolean VERBOSE = false;  // lots of logging
51 
52     private static final double DIFFERENCE_TOLERANCE = .001;
53 
54     static final String mInpPrefix = WorkDir.getMediaDirString() + "images/";
55 
56     // This base directory is needed for the files listed below.
57     // These files will be available for download in Android O release.
58     // Link: https://source.android.com/compatibility/cts/downloads.html#cts-media-files
59     private static final String JPEG_WITH_EXIF_BYTE_ORDER_II = "image_exif_byte_order_ii.jpg";
60     private static final String JPEG_WITH_EXIF_BYTE_ORDER_MM = "image_exif_byte_order_mm.jpg";
61     private static final String DNG_WITH_EXIF_WITH_XMP = "lg_g4_iso_800.dng";
62     private static final String JPEG_WITH_EXIF_WITH_XMP = "lg_g4_iso_800.jpg";
63     private static final String ARW_SONY_RX_100 = "sony_rx_100.arw";
64     private static final String CR2_CANON_G7X = "canon_g7x.cr2";
65     private static final String RAF_FUJI_X20 = "fuji_x20.raf";
66     private static final String NEF_NIKON_1AW1 = "nikon_1aw1.nef";
67     private static final String NRW_NIKON_P330 = "nikon_p330.nrw";
68     private static final String ORF_OLYMPUS_E_PL3 = "olympus_e_pl3.orf";
69     private static final String RW2_PANASONIC_GM5 = "panasonic_gm5.rw2";
70     private static final String PEF_PENTAX_K5 = "pentax_k5.pef";
71     private static final String SRW_SAMSUNG_NX3000 = "samsung_nx3000.srw";
72     private static final String JPEG_VOLANTIS = "volantis.jpg";
73     private static final String WEBP_WITH_EXIF = "webp_with_exif.webp";
74     private static final String WEBP_WITHOUT_EXIF_WITH_ANIM_DATA =
75             "webp_with_anim_without_exif.webp";
76     private static final String WEBP_WITHOUT_EXIF = "webp_without_exif.webp";
77     private static final String WEBP_WITHOUT_EXIF_WITH_LOSSLESS_ENCODING =
78             "webp_lossless_without_exif.webp";
79     private static final String PNG_WITH_EXIF_BYTE_ORDER_II = "png_with_exif_byte_order_ii.png";
80     private static final String PNG_WITHOUT_EXIF = "png_without_exif.png";
81     private static final String JPEG_WITH_DATETIME_TAG = "jpeg_with_datetime_tag.jpg";
82 
83     private static final String[] EXIF_TAGS = {
84             ExifInterface.TAG_MAKE,
85             ExifInterface.TAG_MODEL,
86             ExifInterface.TAG_F_NUMBER,
87             ExifInterface.TAG_DATETIME_ORIGINAL,
88             ExifInterface.TAG_EXPOSURE_TIME,
89             ExifInterface.TAG_FLASH,
90             ExifInterface.TAG_FOCAL_LENGTH,
91             ExifInterface.TAG_GPS_ALTITUDE,
92             ExifInterface.TAG_GPS_ALTITUDE_REF,
93             ExifInterface.TAG_GPS_DATESTAMP,
94             ExifInterface.TAG_GPS_LATITUDE,
95             ExifInterface.TAG_GPS_LATITUDE_REF,
96             ExifInterface.TAG_GPS_LONGITUDE,
97             ExifInterface.TAG_GPS_LONGITUDE_REF,
98             ExifInterface.TAG_GPS_PROCESSING_METHOD,
99             ExifInterface.TAG_GPS_TIMESTAMP,
100             ExifInterface.TAG_IMAGE_LENGTH,
101             ExifInterface.TAG_IMAGE_WIDTH,
102             ExifInterface.TAG_ISO_SPEED_RATINGS,
103             ExifInterface.TAG_ORIENTATION,
104             ExifInterface.TAG_WHITE_BALANCE
105     };
106 
107     private static class ExpectedValue {
108         // Thumbnail information.
109         public final boolean hasThumbnail;
110         public final int thumbnailWidth;
111         public final int thumbnailHeight;
112         public final boolean isThumbnailCompressed;
113         public final int thumbnailOffset;
114         public final int thumbnailLength;
115 
116         // GPS information.
117         public final boolean hasLatLong;
118         public final float latitude;
119         public final int latitudeOffset;
120         public final int latitudeLength;
121         public final float longitude;
122         public final float altitude;
123 
124         // Make information
125         public final boolean hasMake;
126         public final int makeOffset;
127         public final int makeLength;
128         public final String make;
129 
130         // Values.
131         public final String model;
132         public final float aperture;
133         public final String dateTimeOriginal;
134         public final float exposureTime;
135         public final float flash;
136         public final String focalLength;
137         public final String gpsAltitude;
138         public final String gpsAltitudeRef;
139         public final String gpsDatestamp;
140         public final String gpsLatitude;
141         public final String gpsLatitudeRef;
142         public final String gpsLongitude;
143         public final String gpsLongitudeRef;
144         public final String gpsProcessingMethod;
145         public final String gpsTimestamp;
146         public final int imageLength;
147         public final int imageWidth;
148         public final String iso;
149         public final int orientation;
150         public final int whiteBalance;
151 
152         // XMP information.
153         public final boolean hasXmp;
154         public final int xmpOffset;
155         public final int xmpLength;
156 
getString(TypedArray typedArray, int index)157         private static String getString(TypedArray typedArray, int index) {
158             String stringValue = typedArray.getString(index);
159             if (stringValue == null || stringValue.equals("")) {
160                 return null;
161             }
162             return stringValue.trim();
163         }
164 
ExpectedValue(TypedArray typedArray)165         public ExpectedValue(TypedArray typedArray) {
166             int index = 0;
167 
168             // Reads thumbnail information.
169             hasThumbnail = typedArray.getBoolean(index++, false);
170             thumbnailOffset = typedArray.getInt(index++, -1);
171             thumbnailLength = typedArray.getInt(index++, -1);
172             thumbnailWidth = typedArray.getInt(index++, 0);
173             thumbnailHeight = typedArray.getInt(index++, 0);
174             isThumbnailCompressed = typedArray.getBoolean(index++, false);
175 
176             // Reads GPS information.
177             hasLatLong = typedArray.getBoolean(index++, false);
178             latitudeOffset = typedArray.getInt(index++, -1);
179             latitudeLength = typedArray.getInt(index++, -1);
180             latitude = typedArray.getFloat(index++, 0f);
181             longitude = typedArray.getFloat(index++, 0f);
182             altitude = typedArray.getFloat(index++, 0f);
183 
184             // Reads Make information.
185             hasMake = typedArray.getBoolean(index++, false);
186             makeOffset = typedArray.getInt(index++, -1);
187             makeLength = typedArray.getInt(index++, -1);
188             make = getString(typedArray, index++);
189 
190             // Reads values.
191             model = getString(typedArray, index++);
192             aperture = typedArray.getFloat(index++, 0f);
193             dateTimeOriginal = getString(typedArray, index++);
194             exposureTime = typedArray.getFloat(index++, 0f);
195             flash = typedArray.getFloat(index++, 0f);
196             focalLength = getString(typedArray, index++);
197             gpsAltitude = getString(typedArray, index++);
198             gpsAltitudeRef = getString(typedArray, index++);
199             gpsDatestamp = getString(typedArray, index++);
200             gpsLatitude = getString(typedArray, index++);
201             gpsLatitudeRef = getString(typedArray, index++);
202             gpsLongitude = getString(typedArray, index++);
203             gpsLongitudeRef = getString(typedArray, index++);
204             gpsProcessingMethod = getString(typedArray, index++);
205             gpsTimestamp = getString(typedArray, index++);
206             imageLength = typedArray.getInt(index++, 0);
207             imageWidth = typedArray.getInt(index++, 0);
208             iso = getString(typedArray, index++);
209             orientation = typedArray.getInt(index++, 0);
210             whiteBalance = typedArray.getInt(index++, 0);
211 
212             // Reads XMP information.
213             hasXmp = typedArray.getBoolean(index++, false);
214             xmpOffset = typedArray.getInt(index++, 0);
215             xmpLength = typedArray.getInt(index++, 0);
216 
217             typedArray.recycle();
218         }
219     }
220 
printExifTagsAndValues(String fileName, ExifInterface exifInterface)221     private void printExifTagsAndValues(String fileName, ExifInterface exifInterface) {
222         // Prints thumbnail information.
223         if (exifInterface.hasThumbnail()) {
224             byte[] thumbnailBytes = exifInterface.getThumbnailBytes();
225             if (thumbnailBytes != null) {
226                 Log.v(TAG, fileName + " Thumbnail size = " + thumbnailBytes.length);
227                 Bitmap bitmap = exifInterface.getThumbnailBitmap();
228                 if (bitmap == null) {
229                     Log.e(TAG, fileName + " Corrupted thumbnail!");
230                 } else {
231                     Log.v(TAG, fileName + " Thumbnail size: " + bitmap.getWidth() + ", "
232                             + bitmap.getHeight());
233                 }
234             } else {
235                 Log.e(TAG, fileName + " Unexpected result: No thumbnails were found. "
236                         + "A thumbnail is expected.");
237             }
238         } else {
239             if (exifInterface.getThumbnailBytes() != null) {
240                 Log.e(TAG, fileName + " Unexpected result: A thumbnail was found. "
241                         + "No thumbnail is expected.");
242             } else {
243                 Log.v(TAG, fileName + " No thumbnail");
244             }
245         }
246 
247         // Prints GPS information.
248         Log.v(TAG, fileName + " Altitude = " + exifInterface.getAltitude(.0));
249 
250         float[] latLong = new float[2];
251         if (exifInterface.getLatLong(latLong)) {
252             Log.v(TAG, fileName + " Latitude = " + latLong[0]);
253             Log.v(TAG, fileName + " Longitude = " + latLong[1]);
254         } else {
255             Log.v(TAG, fileName + " No latlong data");
256         }
257 
258         // Prints values.
259         for (String tagKey : EXIF_TAGS) {
260             String tagValue = exifInterface.getAttribute(tagKey);
261             Log.v(TAG, fileName + " Key{" + tagKey + "} = '" + tagValue + "'");
262         }
263     }
264 
assertIntTag(ExifInterface exifInterface, String tag, int expectedValue)265     private void assertIntTag(ExifInterface exifInterface, String tag, int expectedValue) {
266         int intValue = exifInterface.getAttributeInt(tag, 0);
267         assertEquals(expectedValue, intValue);
268     }
269 
assertFloatTag(ExifInterface exifInterface, String tag, float expectedValue)270     private void assertFloatTag(ExifInterface exifInterface, String tag, float expectedValue) {
271         double doubleValue = exifInterface.getAttributeDouble(tag, 0.0);
272         assertEquals(expectedValue, doubleValue, DIFFERENCE_TOLERANCE);
273     }
274 
assertStringTag(ExifInterface exifInterface, String tag, String expectedValue)275     private void assertStringTag(ExifInterface exifInterface, String tag, String expectedValue) {
276         String stringValue = exifInterface.getAttribute(tag);
277         if (stringValue != null) {
278             stringValue = stringValue.trim();
279         }
280         stringValue = (stringValue == "") ? null : stringValue;
281 
282         assertEquals(expectedValue, stringValue);
283     }
284 
compareWithExpectedValue(ExifInterface exifInterface, ExpectedValue expectedValue, String verboseTag, boolean assertRanges)285     private void compareWithExpectedValue(ExifInterface exifInterface,
286             ExpectedValue expectedValue, String verboseTag, boolean assertRanges) {
287         if (VERBOSE) {
288             printExifTagsAndValues(verboseTag, exifInterface);
289         }
290         // Checks a thumbnail image.
291         assertEquals(expectedValue.hasThumbnail, exifInterface.hasThumbnail());
292         if (expectedValue.hasThumbnail) {
293             assertNotNull(exifInterface.getThumbnailRange());
294             if (assertRanges) {
295                 final long[] thumbnailRange = exifInterface.getThumbnailRange();
296                 assertEquals(expectedValue.thumbnailOffset, thumbnailRange[0]);
297                 assertEquals(expectedValue.thumbnailLength, thumbnailRange[1]);
298             }
299             testThumbnail(expectedValue, exifInterface);
300         } else {
301             assertNull(exifInterface.getThumbnailRange());
302             assertNull(exifInterface.getThumbnail());
303             assertNull(exifInterface.getThumbnailBitmap());
304             assertFalse(exifInterface.isThumbnailCompressed());
305         }
306 
307         // Checks GPS information.
308         float[] latLong = new float[2];
309         assertEquals(expectedValue.hasLatLong, exifInterface.getLatLong(latLong));
310         if (expectedValue.hasLatLong) {
311             assertNotNull(exifInterface.getAttributeRange(ExifInterface.TAG_GPS_LATITUDE));
312             if (assertRanges) {
313                 final long[] latitudeRange = exifInterface
314                         .getAttributeRange(ExifInterface.TAG_GPS_LATITUDE);
315                 assertEquals(expectedValue.latitudeOffset, latitudeRange[0]);
316                 assertEquals(expectedValue.latitudeLength, latitudeRange[1]);
317             }
318             assertEquals(expectedValue.latitude, latLong[0], DIFFERENCE_TOLERANCE);
319             assertEquals(expectedValue.longitude, latLong[1], DIFFERENCE_TOLERANCE);
320             assertTrue(exifInterface.hasAttribute(ExifInterface.TAG_GPS_LATITUDE));
321             assertTrue(exifInterface.hasAttribute(ExifInterface.TAG_GPS_LONGITUDE));
322         } else {
323             assertNull(exifInterface.getAttributeRange(ExifInterface.TAG_GPS_LATITUDE));
324             assertFalse(exifInterface.hasAttribute(ExifInterface.TAG_GPS_LATITUDE));
325             assertFalse(exifInterface.hasAttribute(ExifInterface.TAG_GPS_LONGITUDE));
326         }
327         assertEquals(expectedValue.altitude, exifInterface.getAltitude(.0), DIFFERENCE_TOLERANCE);
328 
329         // Checks Make information.
330         String make = exifInterface.getAttribute(ExifInterface.TAG_MAKE);
331         assertEquals(expectedValue.hasMake, make != null);
332         if (expectedValue.hasMake) {
333             assertNotNull(exifInterface.getAttributeRange(ExifInterface.TAG_MAKE));
334             if (assertRanges) {
335                 final long[] makeRange = exifInterface
336                         .getAttributeRange(ExifInterface.TAG_MAKE);
337                 assertEquals(expectedValue.makeOffset, makeRange[0]);
338                 assertEquals(expectedValue.makeLength, makeRange[1]);
339             }
340             assertEquals(expectedValue.make, make.trim());
341         } else {
342             assertNull(exifInterface.getAttributeRange(ExifInterface.TAG_MAKE));
343             assertFalse(exifInterface.hasAttribute(ExifInterface.TAG_MAKE));
344         }
345 
346         // Checks values.
347         assertStringTag(exifInterface, ExifInterface.TAG_MAKE, expectedValue.make);
348         assertStringTag(exifInterface, ExifInterface.TAG_MODEL, expectedValue.model);
349         assertFloatTag(exifInterface, ExifInterface.TAG_F_NUMBER, expectedValue.aperture);
350         assertStringTag(exifInterface, ExifInterface.TAG_DATETIME_ORIGINAL,
351                 expectedValue.dateTimeOriginal);
352         assertFloatTag(exifInterface, ExifInterface.TAG_EXPOSURE_TIME, expectedValue.exposureTime);
353         assertFloatTag(exifInterface, ExifInterface.TAG_FLASH, expectedValue.flash);
354         assertStringTag(exifInterface, ExifInterface.TAG_FOCAL_LENGTH, expectedValue.focalLength);
355         assertStringTag(exifInterface, ExifInterface.TAG_GPS_ALTITUDE, expectedValue.gpsAltitude);
356         assertStringTag(exifInterface, ExifInterface.TAG_GPS_ALTITUDE_REF,
357                 expectedValue.gpsAltitudeRef);
358         assertStringTag(exifInterface, ExifInterface.TAG_GPS_DATESTAMP, expectedValue.gpsDatestamp);
359         assertStringTag(exifInterface, ExifInterface.TAG_GPS_LATITUDE, expectedValue.gpsLatitude);
360         assertStringTag(exifInterface, ExifInterface.TAG_GPS_LATITUDE_REF,
361                 expectedValue.gpsLatitudeRef);
362         assertStringTag(exifInterface, ExifInterface.TAG_GPS_LONGITUDE, expectedValue.gpsLongitude);
363         assertStringTag(exifInterface, ExifInterface.TAG_GPS_LONGITUDE_REF,
364                 expectedValue.gpsLongitudeRef);
365         assertStringTag(exifInterface, ExifInterface.TAG_GPS_PROCESSING_METHOD,
366                 expectedValue.gpsProcessingMethod);
367         assertStringTag(exifInterface, ExifInterface.TAG_GPS_TIMESTAMP, expectedValue.gpsTimestamp);
368         assertIntTag(exifInterface, ExifInterface.TAG_IMAGE_LENGTH, expectedValue.imageLength);
369         assertIntTag(exifInterface, ExifInterface.TAG_IMAGE_WIDTH, expectedValue.imageWidth);
370         assertStringTag(exifInterface, ExifInterface.TAG_ISO_SPEED_RATINGS, expectedValue.iso);
371         assertIntTag(exifInterface, ExifInterface.TAG_ORIENTATION, expectedValue.orientation);
372         assertIntTag(exifInterface, ExifInterface.TAG_WHITE_BALANCE, expectedValue.whiteBalance);
373 
374         if (expectedValue.hasXmp) {
375             assertNotNull(exifInterface.getAttributeRange(ExifInterface.TAG_XMP));
376             if (assertRanges) {
377                 final long[] xmpRange = exifInterface.getAttributeRange(ExifInterface.TAG_XMP);
378                 assertEquals(expectedValue.xmpOffset, xmpRange[0]);
379                 assertEquals(expectedValue.xmpLength, xmpRange[1]);
380             }
381             final String xmp = new String(exifInterface.getAttributeBytes(ExifInterface.TAG_XMP),
382                     StandardCharsets.UTF_8);
383             // We're only interested in confirming that we were able to extract
384             // valid XMP data, which must always include this XML tag; a full
385             // XMP parser is beyond the scope of ExifInterface. See XMP
386             // Specification Part 1, Section C.2.2 for additional details.
387             if (!xmp.contains("<rdf:RDF")) {
388                 fail("Invalid XMP: " + xmp);
389             }
390         } else {
391             assertNull(exifInterface.getAttributeRange(ExifInterface.TAG_XMP));
392         }
393     }
394 
readFromStandaloneDataWithExif(String fileName, int typedArrayResourceId)395     private void readFromStandaloneDataWithExif(String fileName, int typedArrayResourceId)
396             throws IOException {
397         ExpectedValue expectedValue = new ExpectedValue(
398                 getContext().getResources().obtainTypedArray(typedArrayResourceId));
399 
400         Preconditions.assertTestFileExists(mInpPrefix + fileName);
401         File imageFile = new File(mInpPrefix, fileName);
402         String verboseTag = imageFile.getName();
403 
404         FileInputStream fis = new FileInputStream(imageFile);
405         // Skip the following marker bytes (0xff, 0xd8, 0xff, 0xe1)
406         fis.skip(4);
407         // Read the value of the length of the exif data
408         short length = readShort(fis);
409         byte[] exifBytes = new byte[length];
410         fis.read(exifBytes);
411 
412         ByteArrayInputStream bin = new ByteArrayInputStream(exifBytes);
413         ExifInterface exifInterface =
414                 new ExifInterface(bin, ExifInterface.STREAM_TYPE_EXIF_DATA_ONLY);
415         compareWithExpectedValue(exifInterface, expectedValue, verboseTag, true);
416     }
417 
testExifInterfaceCommon(String fileName, ExpectedValue expectedValue)418     private void testExifInterfaceCommon(String fileName, ExpectedValue expectedValue)
419             throws IOException {
420         File imageFile = new File(mInpPrefix, fileName);
421         Preconditions.assertTestFileExists(mInpPrefix + fileName);
422         String verboseTag = imageFile.getName();
423 
424         // Creates via path.
425         ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath());
426         assertNotNull(exifInterface);
427         compareWithExpectedValue(exifInterface, expectedValue, verboseTag, true);
428 
429         // Creates via file.
430         exifInterface = new ExifInterface(imageFile);
431         compareWithExpectedValue(exifInterface, expectedValue, verboseTag, true);
432 
433         InputStream in = null;
434         // Creates via InputStream.
435         try {
436             in = new BufferedInputStream(new FileInputStream(imageFile.getAbsolutePath()));
437             exifInterface = new ExifInterface(in);
438             compareWithExpectedValue(exifInterface, expectedValue, verboseTag, true);
439         } finally {
440             IoUtils.closeQuietly(in);
441         }
442 
443         // Creates via FileDescriptor.
444         FileDescriptor fd = null;
445         try {
446             fd = Os.open(imageFile.getAbsolutePath(), OsConstants.O_RDONLY, 0600);
447             exifInterface = new ExifInterface(fd);
448             compareWithExpectedValue(exifInterface, expectedValue, verboseTag, true);
449         } catch (ErrnoException e) {
450             throw e.rethrowAsIOException();
451         } finally {
452             IoUtils.closeQuietly(fd);
453         }
454     }
455 
testExifInterfaceRange(String fileName, ExpectedValue expectedValue)456     private void testExifInterfaceRange(String fileName, ExpectedValue expectedValue)
457             throws IOException {
458         Preconditions.assertTestFileExists(mInpPrefix + fileName);
459         File imageFile = new File(mInpPrefix, fileName);
460         InputStream in = null;
461         try {
462             in = new BufferedInputStream(new FileInputStream(imageFile.getAbsolutePath()));
463             if (expectedValue.hasThumbnail) {
464                 in.skip(expectedValue.thumbnailOffset);
465                 byte[] thumbnailBytes = new byte[expectedValue.thumbnailLength];
466                 if (in.read(thumbnailBytes) != expectedValue.thumbnailLength) {
467                     throw new IOException("Failed to read the expected thumbnail length");
468                 }
469                 // TODO: Need a way to check uncompressed thumbnail file
470                 if (expectedValue.isThumbnailCompressed) {
471                     Bitmap thumbnailBitmap = BitmapFactory.decodeByteArray(thumbnailBytes, 0,
472                             thumbnailBytes.length);
473                     assertNotNull(thumbnailBitmap);
474                     assertEquals(expectedValue.thumbnailWidth, thumbnailBitmap.getWidth());
475                     assertEquals(expectedValue.thumbnailHeight, thumbnailBitmap.getHeight());
476                 }
477             }
478             // TODO: Creating a new input stream is a temporary
479             //  workaround for BufferedInputStream#mark/reset not working properly for
480             //  LG_G4_ISO_800_DNG. Need to investigate cause.
481             in = new BufferedInputStream(new FileInputStream(imageFile.getAbsolutePath()));
482             if (expectedValue.hasMake) {
483                 in.skip(expectedValue.makeOffset);
484                 byte[] makeBytes = new byte[expectedValue.makeLength];
485                 if (in.read(makeBytes) != expectedValue.makeLength) {
486                     throw new IOException("Failed to read the expected make length");
487                 }
488                 String makeString = new String(makeBytes);
489                 // Remove null bytes
490                 makeString = makeString.replaceAll("\u0000.*", "");
491                 assertEquals(expectedValue.make, makeString.trim());
492             }
493             in = new BufferedInputStream(new FileInputStream(imageFile.getAbsolutePath()));
494             if (expectedValue.hasXmp) {
495                 in.skip(expectedValue.xmpOffset);
496                 byte[] identifierBytes = new byte[expectedValue.xmpLength];
497                 if (in.read(identifierBytes) != expectedValue.xmpLength) {
498                     throw new IOException("Failed to read the expected xmp length");
499                 }
500                 final String xmpIdentifier = "<?xpacket begin=";
501                 assertTrue(new String(identifierBytes, StandardCharsets.UTF_8)
502                         .startsWith(xmpIdentifier));
503             }
504             // TODO: Add code for retrieving raw latitude data using offset and length
505         } finally {
506             IoUtils.closeQuietly(in);
507         }
508     }
509 
writeToFilesWithExif(String fileName, int typedArrayResourceId)510     private void writeToFilesWithExif(String fileName, int typedArrayResourceId)
511             throws IOException {
512         ExpectedValue expectedValue = new ExpectedValue(
513                 getContext().getResources().obtainTypedArray(typedArrayResourceId));
514 
515         Preconditions.assertTestFileExists(mInpPrefix + fileName);
516         File srcFile = new File(mInpPrefix, fileName);
517         File imageFile = clone(srcFile);
518         String verboseTag = imageFile.getName();
519 
520         ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath());
521         exifInterface.saveAttributes();
522         exifInterface = new ExifInterface(imageFile.getAbsolutePath());
523         compareWithExpectedValue(exifInterface, expectedValue, verboseTag, false);
524 
525         // Test for modifying one attribute.
526         String backupValue = exifInterface.getAttribute(ExifInterface.TAG_MAKE);
527         exifInterface.setAttribute(ExifInterface.TAG_MAKE, "abc");
528         exifInterface.saveAttributes();
529         // Check if thumbnail offset and length are properly updated without parsing the data again.
530         if (expectedValue.hasThumbnail) {
531             testThumbnail(expectedValue, exifInterface);
532         }
533         exifInterface = new ExifInterface(imageFile.getAbsolutePath());
534         assertEquals("abc", exifInterface.getAttribute(ExifInterface.TAG_MAKE));
535         // Check if thumbnail bytes can be retrieved from the new thumbnail range.
536         if (expectedValue.hasThumbnail) {
537             testThumbnail(expectedValue, exifInterface);
538         }
539 
540         // Restore the backup value.
541         exifInterface.setAttribute(ExifInterface.TAG_MAKE, backupValue);
542         exifInterface.saveAttributes();
543         exifInterface = new ExifInterface(imageFile.getAbsolutePath());
544         compareWithExpectedValue(exifInterface, expectedValue, verboseTag, false);
545 
546         FileDescriptor fd = null;
547         try {
548             fd = Os.open(imageFile.getAbsolutePath(), OsConstants.O_RDWR, 0600);
549             exifInterface = new ExifInterface(fd);
550             exifInterface.saveAttributes();
551             Os.lseek(fd, 0, OsConstants.SEEK_SET);
552             exifInterface = new ExifInterface(fd);
553             compareWithExpectedValue(exifInterface, expectedValue, verboseTag, false);
554 
555             // Test for modifying one attribute.
556             backupValue = exifInterface.getAttribute(ExifInterface.TAG_MAKE);
557             exifInterface.setAttribute(ExifInterface.TAG_MAKE, "abc");
558             exifInterface.saveAttributes();
559             // Check if thumbnail offset and length are properly updated without parsing the data
560             // again.
561             if (expectedValue.hasThumbnail) {
562                 testThumbnail(expectedValue, exifInterface);
563             }
564             Os.lseek(fd, 0, OsConstants.SEEK_SET);
565             exifInterface = new ExifInterface(fd);
566             assertEquals("abc", exifInterface.getAttribute(ExifInterface.TAG_MAKE));
567             // Check if thumbnail bytes can be retrieved from the new thumbnail range.
568             if (expectedValue.hasThumbnail) {
569                 testThumbnail(expectedValue, exifInterface);
570             }
571 
572             // Restore the backup value.
573             exifInterface.setAttribute(ExifInterface.TAG_MAKE, backupValue);
574             exifInterface.saveAttributes();
575             Os.lseek(fd, 0, OsConstants.SEEK_SET);
576             exifInterface = new ExifInterface(fd);
577             compareWithExpectedValue(exifInterface, expectedValue, verboseTag, false);
578         } catch (ErrnoException e) {
579             throw e.rethrowAsIOException();
580         } finally {
581             IoUtils.closeQuietly(fd);
582         }
583         imageFile.delete();
584     }
585 
readFromFilesWithExif(String fileName, int typedArrayResourceId)586     private void readFromFilesWithExif(String fileName, int typedArrayResourceId)
587             throws IOException {
588         ExpectedValue expectedValue = new ExpectedValue(
589                 getContext().getResources().obtainTypedArray(typedArrayResourceId));
590 
591         testExifInterfaceCommon(fileName, expectedValue);
592 
593         // Test for checking expected range by retrieving raw data with given offset and length.
594         testExifInterfaceRange(fileName, expectedValue);
595     }
596 
writeToFilesWithoutExif(String fileName)597     private void writeToFilesWithoutExif(String fileName) throws IOException {
598         // Test for reading from external data storage.
599         Preconditions.assertTestFileExists(mInpPrefix + fileName);
600         File imageFile = clone(new File(mInpPrefix, fileName));
601 
602         ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath());
603         exifInterface.setAttribute(ExifInterface.TAG_MAKE, "abc");
604         exifInterface.saveAttributes();
605 
606         exifInterface = new ExifInterface(imageFile.getAbsolutePath());
607         String make = exifInterface.getAttribute(ExifInterface.TAG_MAKE);
608         assertEquals("abc", make);
609         imageFile.delete();
610     }
611 
testThumbnail(ExpectedValue expectedValue, ExifInterface exifInterface)612     private void testThumbnail(ExpectedValue expectedValue, ExifInterface exifInterface) {
613         byte[] thumbnailBytes = exifInterface.getThumbnailBytes();
614         assertNotNull(thumbnailBytes);
615 
616         // Note: NEF file (nikon_1aw1.nef) contains uncompressed thumbnail.
617         Bitmap thumbnailBitmap = exifInterface.getThumbnailBitmap();
618         assertNotNull(thumbnailBitmap);
619         assertEquals(expectedValue.thumbnailWidth, thumbnailBitmap.getWidth());
620         assertEquals(expectedValue.thumbnailHeight, thumbnailBitmap.getHeight());
621         assertEquals(expectedValue.isThumbnailCompressed, exifInterface.isThumbnailCompressed());
622     }
623 
624     @Override
setUp()625     protected void setUp() throws Exception {
626         super.setUp();
627 
628         StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
629                 .detectUnbufferedIo()
630                 .penaltyDeath()
631                 .build());
632     }
633 
testReadExifDataFromExifByteOrderIIJpeg()634     public void testReadExifDataFromExifByteOrderIIJpeg() throws Throwable {
635         readFromFilesWithExif(JPEG_WITH_EXIF_BYTE_ORDER_II, R.array.jpeg_with_exif_byte_order_ii);
636         writeToFilesWithExif(JPEG_WITH_EXIF_BYTE_ORDER_II, R.array.jpeg_with_exif_byte_order_ii);
637     }
638 
testReadExifDataFromExifByteOrderMMJpeg()639     public void testReadExifDataFromExifByteOrderMMJpeg() throws Throwable {
640         readFromFilesWithExif(JPEG_WITH_EXIF_BYTE_ORDER_MM, R.array.jpeg_with_exif_byte_order_mm);
641         writeToFilesWithExif(JPEG_WITH_EXIF_BYTE_ORDER_MM, R.array.jpeg_with_exif_byte_order_mm);
642     }
643 
testReadExifDataFromLgG4Iso800Dng()644     public void testReadExifDataFromLgG4Iso800Dng() throws Throwable {
645         readFromFilesWithExif(DNG_WITH_EXIF_WITH_XMP, R.array.dng_with_exif_with_xmp);
646     }
647 
testReadExifDataFromLgG4Iso800Jpg()648     public void testReadExifDataFromLgG4Iso800Jpg() throws Throwable {
649         readFromFilesWithExif(JPEG_WITH_EXIF_WITH_XMP, R.array.jpeg_with_exif_with_xmp);
650         writeToFilesWithExif(JPEG_WITH_EXIF_WITH_XMP, R.array.jpeg_with_exif_with_xmp);
651     }
652 
testDoNotFailOnCorruptedImage()653     public void testDoNotFailOnCorruptedImage() throws Throwable {
654         // To keep the compatibility with old versions of ExifInterface, even on a corrupted image,
655         // it shouldn't raise any exceptions except an IOException when unable to open a file.
656         byte[] bytes = new byte[1024];
657         try {
658             new ExifInterface(new ByteArrayInputStream(bytes));
659             // Always success
660         } catch (IOException e) {
661             fail("Should not reach here!");
662         }
663     }
664 
testReadExifDataFromVolantisJpg()665     public void testReadExifDataFromVolantisJpg() throws Throwable {
666         // Test if it is possible to parse the volantis generated JPEG smoothly.
667         readFromFilesWithExif(JPEG_VOLANTIS, R.array.volantis_jpg);
668         writeToFilesWithExif(JPEG_VOLANTIS, R.array.volantis_jpg);
669     }
670 
testReadExifDataFromSonyRX100Arw()671     public void testReadExifDataFromSonyRX100Arw() throws Throwable {
672         readFromFilesWithExif(ARW_SONY_RX_100, R.array.sony_rx_100_arw);
673     }
674 
testReadExifDataFromCanonG7XCr2()675     public void testReadExifDataFromCanonG7XCr2() throws Throwable {
676         readFromFilesWithExif(CR2_CANON_G7X, R.array.canon_g7x_cr2);
677     }
678 
testReadExifDataFromFujiX20Raf()679     public void testReadExifDataFromFujiX20Raf() throws Throwable {
680         readFromFilesWithExif(RAF_FUJI_X20, R.array.fuji_x20_raf);
681     }
682 
testReadExifDataFromNikon1AW1Nef()683     public void testReadExifDataFromNikon1AW1Nef() throws Throwable {
684         readFromFilesWithExif(NEF_NIKON_1AW1, R.array.nikon_1aw1_nef);
685     }
686 
testReadExifDataFromNikonP330Nrw()687     public void testReadExifDataFromNikonP330Nrw() throws Throwable {
688         readFromFilesWithExif(NRW_NIKON_P330, R.array.nikon_p330_nrw);
689     }
690 
testReadExifDataFromOlympusEPL3Orf()691     public void testReadExifDataFromOlympusEPL3Orf() throws Throwable {
692         readFromFilesWithExif(ORF_OLYMPUS_E_PL3, R.array.olympus_e_pl3_orf);
693     }
694 
testReadExifDataFromPanasonicGM5Rw2()695     public void testReadExifDataFromPanasonicGM5Rw2() throws Throwable {
696         readFromFilesWithExif(RW2_PANASONIC_GM5, R.array.panasonic_gm5_rw2);
697     }
698 
testReadExifDataFromPentaxK5Pef()699     public void testReadExifDataFromPentaxK5Pef() throws Throwable {
700         readFromFilesWithExif(PEF_PENTAX_K5, R.array.pentax_k5_pef);
701     }
702 
testReadExifDataFromSamsungNX3000Srw()703     public void testReadExifDataFromSamsungNX3000Srw() throws Throwable {
704         readFromFilesWithExif(SRW_SAMSUNG_NX3000, R.array.samsung_nx3000_srw);
705     }
706 
testPngFiles()707     public void testPngFiles() throws Throwable {
708         readFromFilesWithExif(PNG_WITH_EXIF_BYTE_ORDER_II, R.array.png_with_exif_byte_order_ii);
709         writeToFilesWithoutExif(PNG_WITHOUT_EXIF);
710     }
711 
testStandaloneData()712     public void testStandaloneData() throws Throwable {
713         readFromStandaloneDataWithExif(JPEG_WITH_EXIF_BYTE_ORDER_II,
714                 R.array.standalone_data_with_exif_byte_order_ii);
715         readFromStandaloneDataWithExif(JPEG_WITH_EXIF_BYTE_ORDER_MM,
716                 R.array.standalone_data_with_exif_byte_order_mm);
717     }
718 
testWebpFiles()719     public void testWebpFiles() throws Throwable {
720         readFromFilesWithExif(WEBP_WITH_EXIF, R.array.webp_with_exif);
721         writeToFilesWithExif(WEBP_WITH_EXIF, R.array.webp_with_exif);
722         writeToFilesWithoutExif(WEBP_WITHOUT_EXIF_WITH_ANIM_DATA);
723         writeToFilesWithoutExif(WEBP_WITHOUT_EXIF);
724         writeToFilesWithoutExif(WEBP_WITHOUT_EXIF_WITH_LOSSLESS_ENCODING);
725     }
726 
testGetSetDateTime()727     public void testGetSetDateTime() throws Throwable {
728         final long expectedDatetimeValue = 1454059947000L;
729         final String dateTimeValue = "2017:02:02 22:22:22";
730         final String dateTimeOriginalValue = "2017:01:01 11:11:11";
731 
732         Preconditions.assertTestFileExists(mInpPrefix + JPEG_WITH_DATETIME_TAG);
733         File srcFile = new File(mInpPrefix, JPEG_WITH_DATETIME_TAG);
734         File imageFile = clone(srcFile);
735 
736         ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath());
737         assertEquals(expectedDatetimeValue, exif.getDateTime());
738         assertEquals(expectedDatetimeValue, exif.getDateTimeOriginal());
739         assertEquals(expectedDatetimeValue, exif.getDateTimeDigitized());
740         assertEquals(expectedDatetimeValue, exif.getGpsDateTime());
741 
742         exif.setAttribute(ExifInterface.TAG_DATETIME, dateTimeValue);
743         exif.setAttribute(ExifInterface.TAG_DATETIME_ORIGINAL, dateTimeOriginalValue);
744         exif.saveAttributes();
745 
746         // Check that the DATETIME value is not overwritten by DATETIME_ORIGINAL's value.
747         exif = new ExifInterface(imageFile.getAbsolutePath());
748         assertEquals(dateTimeValue, exif.getAttribute(ExifInterface.TAG_DATETIME));
749         assertEquals(dateTimeOriginalValue, exif.getAttribute(ExifInterface.TAG_DATETIME_ORIGINAL));
750 
751         // Now remove the DATETIME value.
752         exif.setAttribute(ExifInterface.TAG_DATETIME, null);
753         exif.saveAttributes();
754 
755         // When the DATETIME has no value, then it should be set to DATETIME_ORIGINAL's value.
756         exif = new ExifInterface(imageFile.getAbsolutePath());
757         assertEquals(dateTimeOriginalValue, exif.getAttribute(ExifInterface.TAG_DATETIME));
758         imageFile.delete();
759     }
760 
testIsSupportedMimeType()761     public void testIsSupportedMimeType() {
762         try {
763             ExifInterface.isSupportedMimeType(null);
764             fail();
765         } catch (NullPointerException e) {
766             // expected
767         }
768         assertTrue(ExifInterface.isSupportedMimeType("image/jpeg"));
769         assertTrue(ExifInterface.isSupportedMimeType("image/x-adobe-dng"));
770         assertTrue(ExifInterface.isSupportedMimeType("image/x-canon-cr2"));
771         assertTrue(ExifInterface.isSupportedMimeType("image/x-nikon-nef"));
772         assertTrue(ExifInterface.isSupportedMimeType("image/x-nikon-nrw"));
773         assertTrue(ExifInterface.isSupportedMimeType("image/x-sony-arw"));
774         assertTrue(ExifInterface.isSupportedMimeType("image/x-panasonic-rw2"));
775         assertTrue(ExifInterface.isSupportedMimeType("image/x-olympus-orf"));
776         assertTrue(ExifInterface.isSupportedMimeType("image/x-pentax-pef"));
777         assertTrue(ExifInterface.isSupportedMimeType("image/x-samsung-srw"));
778         assertTrue(ExifInterface.isSupportedMimeType("image/x-fuji-raf"));
779         assertTrue(ExifInterface.isSupportedMimeType("image/heic"));
780         assertTrue(ExifInterface.isSupportedMimeType("image/heif"));
781         assertTrue(ExifInterface.isSupportedMimeType("image/png"));
782         assertTrue(ExifInterface.isSupportedMimeType("image/webp"));
783         assertFalse(ExifInterface.isSupportedMimeType("image/gif"));
784     }
785 
testSetAttribute()786     public void testSetAttribute() throws Throwable {
787         Preconditions.assertTestFileExists(mInpPrefix + JPEG_WITH_EXIF_BYTE_ORDER_MM);
788         File srcFile = new File(mInpPrefix, JPEG_WITH_EXIF_BYTE_ORDER_MM);
789         File imageFile = clone(srcFile);
790 
791         ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath());
792         try {
793             exif.setAttribute(null, null);
794             fail();
795         } catch (NullPointerException e) {
796             // expected
797         }
798 
799         // Test setting tag to null
800         assertNotNull(exif.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP));
801         exif.setAttribute(ExifInterface.TAG_GPS_TIMESTAMP, null);
802         assertNull(exif.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP));
803 
804         // Test tags that are converted to rational values for compatibility:
805         // 1. GpsTimeStamp tag will be converted to rational in setAttribute and converted back to
806         // timestamp format in getAttribute.
807         String validGpsTimeStamp = "11:11:11";
808         exif.setAttribute(ExifInterface.TAG_GPS_TIMESTAMP, validGpsTimeStamp);
809         assertEquals(validGpsTimeStamp, exif.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP));
810         // Check that invalid format is not set
811         String invalidGpsTimeStamp = "11:11:11:11";
812         exif.setAttribute(ExifInterface.TAG_GPS_TIMESTAMP, invalidGpsTimeStamp);
813         assertEquals(validGpsTimeStamp, exif.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP));
814 
815         // 2. FNumber tag will be converted to rational in setAttribute and converted back to
816         // double value in getAttribute
817         String validFNumber = "2.4";
818         exif.setAttribute(ExifInterface.TAG_F_NUMBER, validFNumber);
819         assertEquals(validFNumber, exif.getAttribute(ExifInterface.TAG_F_NUMBER));
820         // Check that invalid format is not set
821         String invalidFNumber = "invalid format";
822         exif.setAttribute(ExifInterface.TAG_F_NUMBER, invalidFNumber);
823         assertEquals(validFNumber, exif.getAttribute(ExifInterface.TAG_F_NUMBER));
824 
825         // Test writing different types of formats:
826         // 1. Byte format tag
827         String gpsVersionId = "2.3.0.0";
828         exif.setAttribute(ExifInterface.TAG_GPS_VERSION_ID, gpsVersionId);
829         byte[] setGpsVersionIdBytes =
830                 exif.getAttribute(ExifInterface.TAG_GPS_VERSION_ID).getBytes();
831         for (int i = 0; i < setGpsVersionIdBytes.length; i++) {
832             assertEquals(gpsVersionId.getBytes()[i], setGpsVersionIdBytes[i]);
833         }
834         // Test TAG_GPS_ALTITUDE_REF, which is an exceptional case since the only valid values are
835         // "0" and "1".
836         String gpsAltitudeRef = "1";
837         exif.setAttribute(ExifInterface.TAG_GPS_ALTITUDE_REF, gpsAltitudeRef);
838         assertEquals(gpsAltitudeRef.getBytes()[0],
839                 exif.getAttribute(ExifInterface.TAG_GPS_ALTITUDE_REF).getBytes()[0]);
840 
841         // 2. String format tag
842         String makeValue = "MakeTest";
843         exif.setAttribute(ExifInterface.TAG_MAKE, makeValue);
844         assertEquals(makeValue, exif.getAttribute(ExifInterface.TAG_MAKE));
845         // Check that the following values are not parsed as rational values
846         String makeValueWithOneSlash = "Make/Test";
847         exif.setAttribute(ExifInterface.TAG_MAKE, makeValueWithOneSlash);
848         assertEquals(makeValueWithOneSlash, exif.getAttribute(ExifInterface.TAG_MAKE));
849         String makeValueWithTwoSlashes = "Make/Test/Test";
850         exif.setAttribute(ExifInterface.TAG_MAKE, makeValueWithTwoSlashes);
851         assertEquals(makeValueWithTwoSlashes, exif.getAttribute(ExifInterface.TAG_MAKE));
852         // When a value has a comma, it should be parsed as a string if any of the values before or
853         // after the comma is a string.
854         int defaultValue = -1;
855         String makeValueWithCommaType1 = "Make,2";
856         exif.setAttribute(ExifInterface.TAG_MAKE, makeValueWithCommaType1);
857         assertEquals(makeValueWithCommaType1, exif.getAttribute(ExifInterface.TAG_MAKE));
858         // Make sure that it's not stored as an integer value.
859         assertEquals(defaultValue, exif.getAttributeInt(ExifInterface.TAG_MAKE, defaultValue));
860         String makeValueWithCommaType2 = "2,Make";
861         exif.setAttribute(ExifInterface.TAG_MAKE, makeValueWithCommaType2);
862         assertEquals(makeValueWithCommaType2, exif.getAttribute(ExifInterface.TAG_MAKE));
863         // Make sure that it's not stored as an integer value.
864         assertEquals(defaultValue, exif.getAttributeInt(ExifInterface.TAG_MAKE, defaultValue));
865 
866         // 3. Unsigned short format tag
867         String isoSpeedRatings = "800";
868         exif.setAttribute(ExifInterface.TAG_ISO_SPEED_RATINGS, isoSpeedRatings);
869         assertEquals(isoSpeedRatings, exif.getAttribute(ExifInterface.TAG_ISO_SPEED_RATINGS));
870         // When a value has multiple components, all of them should be of the format that the tag
871         // supports. Thus, the following values (SHORT,LONG) should not be set since TAG_COMPRESSION
872         // only allows short values.
873         assertNull(exif.getAttribute(ExifInterface.TAG_COMPRESSION));
874         String invalidMultipleComponentsValueType1 = "1,65536";
875         exif.setAttribute(ExifInterface.TAG_COMPRESSION, invalidMultipleComponentsValueType1);
876         assertNull(exif.getAttribute(ExifInterface.TAG_COMPRESSION));
877         String invalidMultipleComponentsValueType2 = "65536,1";
878         exif.setAttribute(ExifInterface.TAG_COMPRESSION, invalidMultipleComponentsValueType2);
879         assertNull(exif.getAttribute(ExifInterface.TAG_COMPRESSION));
880 
881         // 4. Unsigned long format tag
882         String validImageWidthValue = "65536"; // max unsigned short value + 1
883         exif.setAttribute(ExifInterface.TAG_IMAGE_WIDTH, validImageWidthValue);
884         assertEquals(validImageWidthValue, exif.getAttribute(ExifInterface.TAG_IMAGE_WIDTH));
885         String invalidImageWidthValue = "-65536";
886         exif.setAttribute(ExifInterface.TAG_IMAGE_WIDTH, invalidImageWidthValue);
887         assertEquals(validImageWidthValue, exif.getAttribute(ExifInterface.TAG_IMAGE_WIDTH));
888 
889         // 5. Unsigned rational format tag
890         String exposureTime = "1/8";
891         exif.setAttribute(ExifInterface.TAG_APERTURE_VALUE, exposureTime);
892         assertEquals(exposureTime, exif.getAttribute(ExifInterface.TAG_APERTURE_VALUE));
893 
894         // 6. Signed rational format tag
895         String brightnessValue = "-220/100";
896         exif.setAttribute(ExifInterface.TAG_BRIGHTNESS_VALUE, brightnessValue);
897         assertEquals(brightnessValue, exif.getAttribute(ExifInterface.TAG_BRIGHTNESS_VALUE));
898 
899         // 7. Undefined format tag
900         String userComment = "UserCommentTest";
901         exif.setAttribute(ExifInterface.TAG_USER_COMMENT, userComment);
902         assertEquals(userComment, exif.getAttribute(ExifInterface.TAG_USER_COMMENT));
903 
904         imageFile.delete();
905     }
906 
testGetAttributeForNullAndNonExistentTag()907     public void testGetAttributeForNullAndNonExistentTag() throws Throwable {
908         // JPEG_WITH_EXIF_BYTE_ORDER_MM does not have a value for TAG_SUBJECT_AREA tag.
909         Preconditions.assertTestFileExists(mInpPrefix + JPEG_WITH_EXIF_BYTE_ORDER_MM);
910         File srcFile = new File(mInpPrefix, JPEG_WITH_EXIF_BYTE_ORDER_MM);
911         File imageFile = clone(srcFile);
912 
913         ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath());
914         try {
915             exif.getAttribute(null);
916             fail();
917         } catch (NullPointerException e) {
918             // expected
919         }
920         assertNull(exif.getAttribute(TAG_SUBJECT_AREA));
921 
922         int defaultValue = -1;
923         try {
924             exif.getAttributeInt(null, defaultValue);
925             fail();
926         } catch (NullPointerException e) {
927             // expected
928         }
929         assertEquals(defaultValue, exif.getAttributeInt(TAG_SUBJECT_AREA, defaultValue));
930 
931         try {
932             exif.getAttributeDouble(null, defaultValue);
933             fail();
934         } catch (NullPointerException e) {
935             // expected
936         }
937         assertEquals(defaultValue, exif.getAttributeInt(TAG_SUBJECT_AREA, defaultValue));
938 
939         try {
940             exif.getAttributeBytes(null);
941             fail();
942         } catch (NullPointerException e) {
943             // expected
944         }
945         assertNull(exif.getAttributeBytes(TAG_SUBJECT_AREA));
946     }
947 
clone(File original)948     private static File clone(File original) throws IOException {
949         final File cloned =
950                 File.createTempFile("cts_", +System.nanoTime() + "_" + original.getName());
951         FileUtils.copyFileOrThrow(original, cloned);
952         return cloned;
953     }
954 
readShort(InputStream is)955     private short readShort(InputStream is) throws IOException {
956         int ch1 = is.read();
957         int ch2 = is.read();
958         if ((ch1 | ch2) < 0) {
959             throw new EOFException();
960         }
961         return (short) ((ch1 << 8) + (ch2));
962     }
963 }
964