1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.media.cts;
18 
19 import android.content.res.TypedArray;
20 import android.graphics.Bitmap;
21 import android.graphics.BitmapFactory;
22 import android.media.ExifInterface;
23 import android.os.Environment;
24 import android.test.AndroidTestCase;
25 import android.util.Log;
26 import android.system.ErrnoException;
27 import android.system.Os;
28 import android.system.OsConstants;
29 
30 import java.io.BufferedInputStream;
31 import java.io.File;
32 import java.io.FileDescriptor;
33 import java.io.FileInputStream;
34 import java.io.FileOutputStream;
35 import java.io.InputStream;
36 import java.io.IOException;
37 import java.lang.reflect.Type;
38 
39 import libcore.io.IoUtils;
40 import libcore.io.Streams;
41 
42 public class ExifInterfaceTest extends AndroidTestCase {
43     private static final String TAG = ExifInterface.class.getSimpleName();
44     private static final boolean VERBOSE = false;  // lots of logging
45 
46     private static final double DIFFERENCE_TOLERANCE = .001;
47 
48     // List of files.
49     private static final String EXIF_BYTE_ORDER_II_JPEG = "image_exif_byte_order_ii.jpg";
50     private static final String EXIF_BYTE_ORDER_MM_JPEG = "image_exif_byte_order_mm.jpg";
51     private static final String LG_G4_ISO_800_DNG = "lg_g4_iso_800.dng";
52     private static final int[] IMAGE_RESOURCES = new int[] {
53             R.raw.image_exif_byte_order_ii,  R.raw.image_exif_byte_order_mm, R.raw.lg_g4_iso_800 };
54     private static final String[] IMAGE_FILENAMES = new String[] {
55             EXIF_BYTE_ORDER_II_JPEG, EXIF_BYTE_ORDER_MM_JPEG, LG_G4_ISO_800_DNG };
56 
57     private static final String[] EXIF_TAGS = {
58             ExifInterface.TAG_MAKE,
59             ExifInterface.TAG_MODEL,
60             ExifInterface.TAG_F_NUMBER,
61             ExifInterface.TAG_DATETIME,
62             ExifInterface.TAG_EXPOSURE_TIME,
63             ExifInterface.TAG_FLASH,
64             ExifInterface.TAG_FOCAL_LENGTH,
65             ExifInterface.TAG_GPS_ALTITUDE,
66             ExifInterface.TAG_GPS_ALTITUDE_REF,
67             ExifInterface.TAG_GPS_DATESTAMP,
68             ExifInterface.TAG_GPS_LATITUDE,
69             ExifInterface.TAG_GPS_LATITUDE_REF,
70             ExifInterface.TAG_GPS_LONGITUDE,
71             ExifInterface.TAG_GPS_LONGITUDE_REF,
72             ExifInterface.TAG_GPS_PROCESSING_METHOD,
73             ExifInterface.TAG_GPS_TIMESTAMP,
74             ExifInterface.TAG_IMAGE_LENGTH,
75             ExifInterface.TAG_IMAGE_WIDTH,
76             ExifInterface.TAG_ISO_SPEED_RATINGS,
77             ExifInterface.TAG_ORIENTATION,
78             ExifInterface.TAG_WHITE_BALANCE
79     };
80 
81     private static class ExpectedValue {
82         // Thumbnail information.
83         public final boolean hasThumbnail;
84         public final int thumbnailWidth;
85         public final int thumbnailHeight;
86 
87         // GPS information.
88         public final boolean hasLatLong;
89         public final float latitude;
90         public final float longitude;
91         public final float altitude;
92 
93         // Values.
94         public final String make;
95         public final String model;
96         public final float aperture;
97         public final String datetime;
98         public final float exposureTime;
99         public final float flash;
100         public final String focalLength;
101         public final String gpsAltitude;
102         public final String gpsAltitudeRef;
103         public final String gpsDatestamp;
104         public final String gpsLatitude;
105         public final String gpsLatitudeRef;
106         public final String gpsLongitude;
107         public final String gpsLongitudeRef;
108         public final String gpsProcessingMethod;
109         public final String gpsTimestamp;
110         public final int imageLength;
111         public final int imageWidth;
112         public final String iso;
113         public final int orientation;
114         public final int whiteBalance;
115 
getString(TypedArray typedArray, int index)116         private static String getString(TypedArray typedArray, int index) {
117             String stringValue = typedArray.getString(index);
118             if (stringValue == null || stringValue.equals("")) {
119                 return null;
120             }
121             return stringValue.trim();
122         }
123 
ExpectedValue(TypedArray typedArray)124         public ExpectedValue(TypedArray typedArray) {
125             // Reads thumbnail information.
126             hasThumbnail = typedArray.getBoolean(0, false);
127             thumbnailWidth = typedArray.getInt(1, 0);
128             thumbnailHeight = typedArray.getInt(2, 0);
129 
130             // Reads GPS information.
131             hasLatLong = typedArray.getBoolean(3, false);
132             latitude = typedArray.getFloat(4, 0f);
133             longitude = typedArray.getFloat(5, 0f);
134             altitude = typedArray.getFloat(6, 0f);
135 
136             // Reads values.
137             make = getString(typedArray, 7);
138             model = getString(typedArray, 8);
139             aperture = typedArray.getFloat(9, 0f);
140             datetime = getString(typedArray, 10);
141             exposureTime = typedArray.getFloat(11, 0f);
142             flash = typedArray.getFloat(12, 0f);
143             focalLength = getString(typedArray, 13);
144             gpsAltitude = getString(typedArray, 14);
145             gpsAltitudeRef = getString(typedArray, 15);
146             gpsDatestamp = getString(typedArray, 16);
147             gpsLatitude = getString(typedArray, 17);
148             gpsLatitudeRef = getString(typedArray, 18);
149             gpsLongitude = getString(typedArray, 19);
150             gpsLongitudeRef = getString(typedArray, 20);
151             gpsProcessingMethod = getString(typedArray, 21);
152             gpsTimestamp = getString(typedArray, 22);
153             imageLength = typedArray.getInt(23, 0);
154             imageWidth = typedArray.getInt(24, 0);
155             iso = getString(typedArray, 25);
156             orientation = typedArray.getInt(26, 0);
157             whiteBalance = typedArray.getInt(27, 0);
158 
159             typedArray.recycle();
160         }
161     }
162 
163     @Override
setUp()164     protected void setUp() throws Exception {
165         for (int i = 0; i < IMAGE_RESOURCES.length; ++i) {
166             String outputPath = new File(Environment.getExternalStorageDirectory(),
167                     IMAGE_FILENAMES[i]).getAbsolutePath();
168             try (InputStream inputStream = getContext().getResources().openRawResource(
169                     IMAGE_RESOURCES[i])) {
170                 try (FileOutputStream outputStream = new FileOutputStream(outputPath)) {
171                     Streams.copy(inputStream, outputStream);
172                 }
173             }
174         }
175         super.setUp();
176     }
177 
178     @Override
tearDown()179     protected void tearDown() throws Exception {
180         for (int i = 0; i < IMAGE_RESOURCES.length; ++i) {
181             String imageFilePath = new File(Environment.getExternalStorageDirectory(),
182                     IMAGE_FILENAMES[i]).getAbsolutePath();
183             File imageFile = new File(imageFilePath);
184             if (imageFile.exists()) {
185                 imageFile.delete();
186             }
187         }
188 
189         super.tearDown();
190     }
191 
testReadExifDataFromExifByteOrderIIJpeg()192     public void testReadExifDataFromExifByteOrderIIJpeg() throws Throwable {
193         testExifInterfaceForJpeg(EXIF_BYTE_ORDER_II_JPEG, R.array.exifbyteorderii_jpg);
194     }
195 
testReadExifDataFromExifByteOrderMMJpeg()196     public void testReadExifDataFromExifByteOrderMMJpeg() throws Throwable {
197         testExifInterfaceForJpeg(EXIF_BYTE_ORDER_MM_JPEG, R.array.exifbyteordermm_jpg);
198     }
199 
testReadExifDataFromLgG4Iso800Dng()200     public void testReadExifDataFromLgG4Iso800Dng() throws Throwable {
201         testExifInterfaceForRaw(LG_G4_ISO_800_DNG, R.array.lg_g4_iso_800_dng);
202     }
203 
printExifTagsAndValues(String fileName, ExifInterface exifInterface)204     private void printExifTagsAndValues(String fileName, ExifInterface exifInterface) {
205         // Prints thumbnail information.
206         if (exifInterface.hasThumbnail()) {
207             byte[] thumbnailBytes = exifInterface.getThumbnail();
208             if (thumbnailBytes != null) {
209                 Log.v(TAG, fileName + " Thumbnail size = " + thumbnailBytes.length);
210                 Bitmap bitmap = BitmapFactory.decodeByteArray(
211                         thumbnailBytes, 0, thumbnailBytes.length);
212                 if (bitmap == null) {
213                     Log.e(TAG, fileName + " Corrupted thumbnail!");
214                 } else {
215                     Log.v(TAG, fileName + " Thumbnail size: " + bitmap.getWidth() + ", "
216                             + bitmap.getHeight());
217                 }
218             } else {
219                 Log.e(TAG, fileName + " Unexpected result: No thumbnails were found. "
220                         + "A thumbnail is expected.");
221             }
222         } else {
223             if (exifInterface.getThumbnail() != null) {
224                 Log.e(TAG, fileName + " Unexpected result: A thumbnail was found. "
225                         + "No thumbnail is expected.");
226             } else {
227                 Log.v(TAG, fileName + " No thumbnail");
228             }
229         }
230 
231         // Prints GPS information.
232         Log.v(TAG, fileName + " Altitude = " + exifInterface.getAltitude(.0));
233 
234         float[] latLong = new float[2];
235         if (exifInterface.getLatLong(latLong)) {
236             Log.v(TAG, fileName + " Latitude = " + latLong[0]);
237             Log.v(TAG, fileName + " Longitude = " + latLong[1]);
238         } else {
239             Log.v(TAG, fileName + " No latlong data");
240         }
241 
242         // Prints values.
243         for (String tagKey : EXIF_TAGS) {
244             String tagValue = exifInterface.getAttribute(tagKey);
245             Log.v(TAG, fileName + " Key{" + tagKey + "} = '" + tagValue + "'");
246         }
247     }
248 
assertIntTag(ExifInterface exifInterface, String tag, int expectedValue)249     private void assertIntTag(ExifInterface exifInterface, String tag, int expectedValue) {
250         int intValue = exifInterface.getAttributeInt(tag, 0);
251         assertEquals(expectedValue, intValue);
252     }
253 
assertFloatTag(ExifInterface exifInterface, String tag, float expectedValue)254     private void assertFloatTag(ExifInterface exifInterface, String tag, float expectedValue) {
255         double doubleValue = exifInterface.getAttributeDouble(tag, 0.0);
256         assertEquals(expectedValue, doubleValue, DIFFERENCE_TOLERANCE);
257     }
258 
assertStringTag(ExifInterface exifInterface, String tag, String expectedValue)259     private void assertStringTag(ExifInterface exifInterface, String tag, String expectedValue) {
260         String stringValue = exifInterface.getAttribute(tag);
261         if (stringValue != null) {
262             stringValue = stringValue.trim();
263         }
264         assertEquals(expectedValue, stringValue);
265     }
266 
compareWithExpectedValue(ExifInterface exifInterface, ExpectedValue expectedValue, String verboseTag)267     private void compareWithExpectedValue(ExifInterface exifInterface,
268             ExpectedValue expectedValue, String verboseTag) {
269         if (VERBOSE) {
270             printExifTagsAndValues(verboseTag, exifInterface);
271         }
272         // Checks a thumbnail image.
273         assertEquals(expectedValue.hasThumbnail, exifInterface.hasThumbnail());
274         if (expectedValue.hasThumbnail) {
275             byte[] thumbnailBytes = exifInterface.getThumbnail();
276             assertNotNull(thumbnailBytes);
277             Bitmap thumbnailBitmap =
278                     BitmapFactory.decodeByteArray(thumbnailBytes, 0, thumbnailBytes.length);
279             assertNotNull(thumbnailBitmap);
280             assertEquals(expectedValue.thumbnailWidth, thumbnailBitmap.getWidth());
281             assertEquals(expectedValue.thumbnailHeight, thumbnailBitmap.getHeight());
282         } else {
283             assertNull(exifInterface.getThumbnail());
284         }
285 
286         // Checks GPS information.
287         float[] latLong = new float[2];
288         assertEquals(expectedValue.hasLatLong, exifInterface.getLatLong(latLong));
289         if (expectedValue.hasLatLong) {
290             assertEquals(expectedValue.latitude, latLong[0], DIFFERENCE_TOLERANCE);
291             assertEquals(expectedValue.longitude, latLong[1], DIFFERENCE_TOLERANCE);
292         }
293         assertEquals(expectedValue.altitude, exifInterface.getAltitude(.0), DIFFERENCE_TOLERANCE);
294 
295         // Checks values.
296         assertStringTag(exifInterface, ExifInterface.TAG_MAKE, expectedValue.make);
297         assertStringTag(exifInterface, ExifInterface.TAG_MODEL, expectedValue.model);
298         assertFloatTag(exifInterface, ExifInterface.TAG_F_NUMBER, expectedValue.aperture);
299         assertStringTag(exifInterface, ExifInterface.TAG_DATETIME, expectedValue.datetime);
300         assertFloatTag(exifInterface, ExifInterface.TAG_EXPOSURE_TIME, expectedValue.exposureTime);
301         assertFloatTag(exifInterface, ExifInterface.TAG_FLASH, expectedValue.flash);
302         assertStringTag(exifInterface, ExifInterface.TAG_FOCAL_LENGTH, expectedValue.focalLength);
303         assertStringTag(exifInterface, ExifInterface.TAG_GPS_ALTITUDE, expectedValue.gpsAltitude);
304         assertStringTag(exifInterface, ExifInterface.TAG_GPS_ALTITUDE_REF,
305                 expectedValue.gpsAltitudeRef);
306         assertStringTag(exifInterface, ExifInterface.TAG_GPS_DATESTAMP, expectedValue.gpsDatestamp);
307         assertStringTag(exifInterface, ExifInterface.TAG_GPS_LATITUDE, expectedValue.gpsLatitude);
308         assertStringTag(exifInterface, ExifInterface.TAG_GPS_LATITUDE_REF,
309                 expectedValue.gpsLatitudeRef);
310         assertStringTag(exifInterface, ExifInterface.TAG_GPS_LONGITUDE, expectedValue.gpsLongitude);
311         assertStringTag(exifInterface, ExifInterface.TAG_GPS_LONGITUDE_REF,
312                 expectedValue.gpsLongitudeRef);
313         assertStringTag(exifInterface, ExifInterface.TAG_GPS_PROCESSING_METHOD,
314                 expectedValue.gpsProcessingMethod);
315         assertStringTag(exifInterface, ExifInterface.TAG_GPS_TIMESTAMP, expectedValue.gpsTimestamp);
316         assertIntTag(exifInterface, ExifInterface.TAG_IMAGE_LENGTH, expectedValue.imageLength);
317         assertIntTag(exifInterface, ExifInterface.TAG_IMAGE_WIDTH, expectedValue.imageWidth);
318         assertStringTag(exifInterface, ExifInterface.TAG_ISO_SPEED_RATINGS, expectedValue.iso);
319         assertIntTag(exifInterface, ExifInterface.TAG_ORIENTATION, expectedValue.orientation);
320         assertIntTag(exifInterface, ExifInterface.TAG_WHITE_BALANCE, expectedValue.whiteBalance);
321     }
322 
testExifInterfaceCommon(File imageFile, ExpectedValue expectedValue)323     private void testExifInterfaceCommon(File imageFile, ExpectedValue expectedValue)
324             throws IOException {
325         String verboseTag = imageFile.getName();
326 
327         // Creates via path.
328         ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath());
329         assertNotNull(exifInterface);
330         compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
331 
332         // Creates from an asset file.
333         InputStream in = null;
334         try {
335             in = getContext().getAssets().open(imageFile.getName());
336             exifInterface = new ExifInterface(in);
337             compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
338         } finally {
339             IoUtils.closeQuietly(in);
340         }
341 
342         // Creates via InputStream.
343         in = null;
344         try {
345             in = new BufferedInputStream(new FileInputStream(imageFile.getAbsolutePath()));
346             exifInterface = new ExifInterface(in);
347             compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
348         } finally {
349             IoUtils.closeQuietly(in);
350         }
351 
352         // Creates via FileDescriptor.
353         FileDescriptor fd = null;
354         try {
355             fd = Os.open(imageFile.getAbsolutePath(), OsConstants.O_RDONLY, 0600);
356             exifInterface = new ExifInterface(fd);
357             compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
358         } catch (ErrnoException e) {
359             throw e.rethrowAsIOException();
360         } finally {
361             IoUtils.closeQuietly(fd);
362         }
363     }
364 
testSaveAttributes_withFileName(File imageFile, ExpectedValue expectedValue)365     private void testSaveAttributes_withFileName(File imageFile, ExpectedValue expectedValue)
366             throws IOException {
367         String verboseTag = imageFile.getName();
368 
369         ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath());
370         exifInterface.saveAttributes();
371         exifInterface = new ExifInterface(imageFile.getAbsolutePath());
372         compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
373 
374         // Test for modifying one attribute.
375         String backupValue = exifInterface.getAttribute(ExifInterface.TAG_MAKE);
376         exifInterface.setAttribute(ExifInterface.TAG_MAKE, "abc");
377         exifInterface.saveAttributes();
378         exifInterface = new ExifInterface(imageFile.getAbsolutePath());
379         assertEquals("abc", exifInterface.getAttribute(ExifInterface.TAG_MAKE));
380         // Restore the backup value.
381         exifInterface.setAttribute(ExifInterface.TAG_MAKE, backupValue);
382         exifInterface.saveAttributes();
383         exifInterface = new ExifInterface(imageFile.getAbsolutePath());
384         compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
385     }
386 
testSaveAttributes_withFileDescriptor(File imageFile, ExpectedValue expectedValue)387     private void testSaveAttributes_withFileDescriptor(File imageFile, ExpectedValue expectedValue)
388             throws IOException {
389         String verboseTag = imageFile.getName();
390 
391         FileDescriptor fd = null;
392         try {
393             fd = Os.open(imageFile.getAbsolutePath(), OsConstants.O_RDWR, 0600);
394             ExifInterface exifInterface = new ExifInterface(fd);
395             exifInterface.saveAttributes();
396             Os.lseek(fd, 0, OsConstants.SEEK_SET);
397             exifInterface = new ExifInterface(fd);
398             compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
399 
400             // Test for modifying one attribute.
401             String backupValue = exifInterface.getAttribute(ExifInterface.TAG_MAKE);
402             exifInterface.setAttribute(ExifInterface.TAG_MAKE, "abc");
403             exifInterface.saveAttributes();
404             Os.lseek(fd, 0, OsConstants.SEEK_SET);
405             exifInterface = new ExifInterface(fd);
406             assertEquals("abc", exifInterface.getAttribute(ExifInterface.TAG_MAKE));
407             // Restore the backup value.
408             exifInterface.setAttribute(ExifInterface.TAG_MAKE, backupValue);
409             exifInterface.saveAttributes();
410             Os.lseek(fd, 0, OsConstants.SEEK_SET);
411             exifInterface = new ExifInterface(fd);
412             compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
413         } catch (ErrnoException e) {
414             throw e.rethrowAsIOException();
415         } finally {
416             IoUtils.closeQuietly(fd);
417         }
418     }
419 
testSaveAttributes_withInputStream(File imageFile, ExpectedValue expectedValue)420     private void testSaveAttributes_withInputStream(File imageFile, ExpectedValue expectedValue)
421             throws IOException {
422         InputStream in = null;
423         try {
424             in = getContext().getAssets().open(imageFile.getName());
425             ExifInterface exifInterface = new ExifInterface(in);
426             exifInterface.saveAttributes();
427         } catch (UnsupportedOperationException e) {
428             // Expected. saveAttributes is not supported with an ExifInterface object which was
429             // created with InputStream.
430             return;
431         } finally {
432             IoUtils.closeQuietly(in);
433         }
434         fail("Should not reach here!");
435     }
436 
testExifInterfaceForJpeg(String fileName, int typedArrayResourceId)437     private void testExifInterfaceForJpeg(String fileName, int typedArrayResourceId)
438             throws IOException {
439         ExpectedValue expectedValue = new ExpectedValue(
440                 getContext().getResources().obtainTypedArray(typedArrayResourceId));
441         File imageFile = new File(Environment.getExternalStorageDirectory(), fileName);
442 
443         // Test for reading from various inputs.
444         testExifInterfaceCommon(imageFile, expectedValue);
445 
446         // Test for saving attributes.
447         testSaveAttributes_withFileName(imageFile, expectedValue);
448         testSaveAttributes_withFileDescriptor(imageFile, expectedValue);
449         testSaveAttributes_withInputStream(imageFile, expectedValue);
450     }
451 
testExifInterfaceForRaw(String fileName, int typedArrayResourceId)452     private void testExifInterfaceForRaw(String fileName, int typedArrayResourceId)
453             throws IOException {
454         ExpectedValue expectedValue = new ExpectedValue(
455                 getContext().getResources().obtainTypedArray(typedArrayResourceId));
456         File imageFile = new File(Environment.getExternalStorageDirectory(), fileName);
457 
458         // Test for reading from various inputs.
459         testExifInterfaceCommon(imageFile, expectedValue);
460 
461         // Since ExifInterface does not support for saving attributes for RAW files, do not test
462         // about writing back in here.
463     }
464 }
465