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