[go: nahoru, domu]

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