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