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