Home | History | Annotate | Download | only in unit
      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 com.android.mediaframeworktest.unit;
     18 
     19 import com.android.mediaframeworktest.R;
     20 
     21 import android.content.res.TypedArray;
     22 import android.graphics.Bitmap;
     23 import android.graphics.BitmapFactory;
     24 import android.media.ExifInterface;
     25 import android.os.Environment;
     26 import android.test.AndroidTestCase;
     27 import android.util.Log;
     28 import android.system.ErrnoException;
     29 import android.system.Os;
     30 import android.system.OsConstants;
     31 
     32 import java.io.BufferedInputStream;
     33 import java.io.ByteArrayInputStream;
     34 import java.io.File;
     35 import java.io.FileDescriptor;
     36 import java.io.FileInputStream;
     37 import java.io.FileOutputStream;
     38 import java.io.InputStream;
     39 import java.io.IOException;
     40 
     41 import libcore.io.IoUtils;
     42 import libcore.io.Streams;
     43 
     44 public 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.getThumbnailBytes();
    200             if (thumbnailBytes != null) {
    201                 Log.v(TAG, fileName + " Thumbnail size = " + thumbnailBytes.length);
    202                 Bitmap bitmap = exifInterface.getThumbnailBitmap();
    203                 if (bitmap == null) {
    204                     Log.e(TAG, fileName + " Corrupted thumbnail!");
    205                 } else {
    206                     Log.v(TAG, fileName + " Thumbnail size: " + bitmap.getWidth() + ", "
    207                             + bitmap.getHeight());
    208                 }
    209             } else {
    210                 Log.e(TAG, fileName + " Unexpected result: No thumbnails were found. "
    211                         + "A thumbnail is expected.");
    212             }
    213         } else {
    214             if (exifInterface.getThumbnail() != null) {
    215                 Log.e(TAG, fileName + " Unexpected result: A thumbnail was found. "
    216                         + "No thumbnail is expected.");
    217             } else {
    218                 Log.v(TAG, fileName + " No thumbnail");
    219             }
    220         }
    221 
    222         // Prints GPS information.
    223         Log.v(TAG, fileName + " Altitude = " + exifInterface.getAltitude(.0));
    224 
    225         float[] latLong = new float[2];
    226         if (exifInterface.getLatLong(latLong)) {
    227             Log.v(TAG, fileName + " Latitude = " + latLong[0]);
    228             Log.v(TAG, fileName + " Longitude = " + latLong[1]);
    229         } else {
    230             Log.v(TAG, fileName + " No latlong data");
    231         }
    232 
    233         // Prints values.
    234         for (String tagKey : EXIF_TAGS) {
    235             String tagValue = exifInterface.getAttribute(tagKey);
    236             Log.v(TAG, fileName + " Key{" + tagKey + "} = '" + tagValue + "'");
    237         }
    238     }
    239 
    240     private void assertIntTag(ExifInterface exifInterface, String tag, int expectedValue) {
    241         int intValue = exifInterface.getAttributeInt(tag, 0);
    242         assertEquals(expectedValue, intValue);
    243     }
    244 
    245     private void assertDoubleTag(ExifInterface exifInterface, String tag, float expectedValue) {
    246         double doubleValue = exifInterface.getAttributeDouble(tag, 0.0);
    247         assertEquals(expectedValue, doubleValue, DIFFERENCE_TOLERANCE);
    248     }
    249 
    250     private void assertStringTag(ExifInterface exifInterface, String tag, String expectedValue) {
    251         String stringValue = exifInterface.getAttribute(tag);
    252         if (stringValue != null) {
    253             stringValue = stringValue.trim();
    254         }
    255         stringValue = (stringValue == "") ? null : stringValue;
    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.getThumbnailBytes();
    269             assertNotNull(thumbnailBytes);
    270             Bitmap thumbnailBitmap = exifInterface.getThumbnailBitmap();
    271             assertNotNull(thumbnailBitmap);
    272             assertEquals(expectedValue.thumbnailWidth, thumbnailBitmap.getWidth());
    273             assertEquals(expectedValue.thumbnailHeight, thumbnailBitmap.getHeight());
    274         } else {
    275             assertNull(exifInterface.getThumbnail());
    276         }
    277 
    278         // Checks GPS information.
    279         float[] latLong = new float[2];
    280         assertEquals(expectedValue.hasLatLong, exifInterface.getLatLong(latLong));
    281         if (expectedValue.hasLatLong) {
    282             assertEquals(expectedValue.latitude, latLong[0], DIFFERENCE_TOLERANCE);
    283             assertEquals(expectedValue.longitude, latLong[1], DIFFERENCE_TOLERANCE);
    284         }
    285         assertEquals(expectedValue.altitude, exifInterface.getAltitude(.0), DIFFERENCE_TOLERANCE);
    286 
    287         // Checks values.
    288         assertStringTag(exifInterface, ExifInterface.TAG_MAKE, expectedValue.make);
    289         assertStringTag(exifInterface, ExifInterface.TAG_MODEL, expectedValue.model);
    290         assertDoubleTag(exifInterface, ExifInterface.TAG_F_NUMBER, expectedValue.fNumber);
    291         assertStringTag(exifInterface, ExifInterface.TAG_DATETIME, expectedValue.datetime);
    292         assertDoubleTag(exifInterface, ExifInterface.TAG_EXPOSURE_TIME, expectedValue.exposureTime);
    293         assertDoubleTag(exifInterface, ExifInterface.TAG_FLASH, expectedValue.flash);
    294         assertStringTag(exifInterface, ExifInterface.TAG_FOCAL_LENGTH, expectedValue.focalLength);
    295         assertStringTag(exifInterface, ExifInterface.TAG_GPS_ALTITUDE, expectedValue.gpsAltitude);
    296         assertStringTag(exifInterface, ExifInterface.TAG_GPS_ALTITUDE_REF,
    297                 expectedValue.gpsAltitudeRef);
    298         assertStringTag(exifInterface, ExifInterface.TAG_GPS_DATESTAMP, expectedValue.gpsDatestamp);
    299         assertStringTag(exifInterface, ExifInterface.TAG_GPS_LATITUDE, expectedValue.gpsLatitude);
    300         assertStringTag(exifInterface, ExifInterface.TAG_GPS_LATITUDE_REF,
    301                 expectedValue.gpsLatitudeRef);
    302         assertStringTag(exifInterface, ExifInterface.TAG_GPS_LONGITUDE, expectedValue.gpsLongitude);
    303         assertStringTag(exifInterface, ExifInterface.TAG_GPS_LONGITUDE_REF,
    304                 expectedValue.gpsLongitudeRef);
    305         assertStringTag(exifInterface, ExifInterface.TAG_GPS_PROCESSING_METHOD,
    306                 expectedValue.gpsProcessingMethod);
    307         assertStringTag(exifInterface, ExifInterface.TAG_GPS_TIMESTAMP, expectedValue.gpsTimestamp);
    308         assertIntTag(exifInterface, ExifInterface.TAG_IMAGE_LENGTH, expectedValue.imageLength);
    309         assertIntTag(exifInterface, ExifInterface.TAG_IMAGE_WIDTH, expectedValue.imageWidth);
    310         assertStringTag(exifInterface, ExifInterface.TAG_ISO_SPEED_RATINGS, expectedValue.iso);
    311         assertIntTag(exifInterface, ExifInterface.TAG_ORIENTATION, expectedValue.orientation);
    312         assertIntTag(exifInterface, ExifInterface.TAG_WHITE_BALANCE, expectedValue.whiteBalance);
    313     }
    314 
    315     private void testExifInterfaceCommon(File imageFile, ExpectedValue expectedValue)
    316             throws IOException {
    317         String verboseTag = imageFile.getName();
    318 
    319         // Creates via path.
    320         ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath());
    321         compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
    322 
    323         // Creates from an asset file.
    324         InputStream in = null;
    325         try {
    326             in = mContext.getAssets().open(imageFile.getName());
    327             exifInterface = new ExifInterface(in);
    328             compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
    329         } finally {
    330             IoUtils.closeQuietly(in);
    331         }
    332 
    333         // Creates via InputStream.
    334         in = null;
    335         try {
    336             in = new BufferedInputStream(new FileInputStream(imageFile.getAbsolutePath()));
    337             exifInterface = new ExifInterface(in);
    338             compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
    339         } finally {
    340             IoUtils.closeQuietly(in);
    341         }
    342 
    343         // Creates via FileDescriptor.
    344         FileDescriptor fd = null;
    345         try {
    346             fd = Os.open(imageFile.getAbsolutePath(), OsConstants.O_RDONLY, 0600);
    347             exifInterface = new ExifInterface(fd);
    348             compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
    349         } catch (ErrnoException e) {
    350             throw e.rethrowAsIOException();
    351         } finally {
    352             IoUtils.closeQuietly(fd);
    353         }
    354     }
    355 
    356     private void testSaveAttributes_withFileName(File imageFile, ExpectedValue expectedValue)
    357             throws IOException {
    358         String verboseTag = imageFile.getName();
    359 
    360         ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath());
    361         exifInterface.saveAttributes();
    362         exifInterface = new ExifInterface(imageFile.getAbsolutePath());
    363         compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
    364 
    365         // Test for modifying one attribute.
    366         String backupValue = exifInterface.getAttribute(ExifInterface.TAG_MAKE);
    367         exifInterface.setAttribute(ExifInterface.TAG_MAKE, "abc");
    368         exifInterface.saveAttributes();
    369         exifInterface = new ExifInterface(imageFile.getAbsolutePath());
    370         assertEquals("abc", exifInterface.getAttribute(ExifInterface.TAG_MAKE));
    371         // Restore the backup value.
    372         exifInterface.setAttribute(ExifInterface.TAG_MAKE, backupValue);
    373         exifInterface.saveAttributes();
    374         exifInterface = new ExifInterface(imageFile.getAbsolutePath());
    375         compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
    376     }
    377 
    378     private void testSaveAttributes_withFileDescriptor(File imageFile, ExpectedValue expectedValue)
    379             throws IOException {
    380         String verboseTag = imageFile.getName();
    381 
    382         FileDescriptor fd = null;
    383         try {
    384             fd = Os.open(imageFile.getAbsolutePath(), OsConstants.O_RDWR, 0600);
    385             ExifInterface exifInterface = new ExifInterface(fd);
    386             exifInterface.saveAttributes();
    387             Os.lseek(fd, 0, OsConstants.SEEK_SET);
    388             exifInterface = new ExifInterface(fd);
    389             compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
    390 
    391             // Test for modifying one attribute.
    392             String backupValue = exifInterface.getAttribute(ExifInterface.TAG_MAKE);
    393             exifInterface.setAttribute(ExifInterface.TAG_MAKE, "abc");
    394             exifInterface.saveAttributes();
    395             Os.lseek(fd, 0, OsConstants.SEEK_SET);
    396             exifInterface = new ExifInterface(fd);
    397             assertEquals("abc", exifInterface.getAttribute(ExifInterface.TAG_MAKE));
    398             // Restore the backup value.
    399             exifInterface.setAttribute(ExifInterface.TAG_MAKE, backupValue);
    400             exifInterface.saveAttributes();
    401             Os.lseek(fd, 0, OsConstants.SEEK_SET);
    402             exifInterface = new ExifInterface(fd);
    403             compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
    404         } catch (ErrnoException e) {
    405             throw e.rethrowAsIOException();
    406         } finally {
    407             IoUtils.closeQuietly(fd);
    408         }
    409     }
    410 
    411     private void testSaveAttributes_withInputStream(File imageFile, ExpectedValue expectedValue)
    412             throws IOException {
    413         InputStream in = null;
    414         try {
    415             in = getContext().getAssets().open(imageFile.getName());
    416             ExifInterface exifInterface = new ExifInterface(in);
    417             exifInterface.saveAttributes();
    418         } catch (IOException e) {
    419             // Expected. saveAttributes is not supported with an ExifInterface object which was
    420             // created with InputStream.
    421             return;
    422         } finally {
    423             IoUtils.closeQuietly(in);
    424         }
    425         fail("Should not reach here!");
    426     }
    427 
    428     private void testExifInterfaceForJpeg(String fileName, int typedArrayResourceId)
    429             throws IOException {
    430         ExpectedValue expectedValue = new ExpectedValue(
    431                 getContext().getResources().obtainTypedArray(typedArrayResourceId));
    432         File imageFile = new File(Environment.getExternalStorageDirectory(), fileName);
    433 
    434         // Test for reading from various inputs.
    435         testExifInterfaceCommon(imageFile, expectedValue);
    436 
    437         // Test for saving attributes.
    438         testSaveAttributes_withFileName(imageFile, expectedValue);
    439         testSaveAttributes_withFileDescriptor(imageFile, expectedValue);
    440         testSaveAttributes_withInputStream(imageFile, expectedValue);
    441     }
    442 
    443     private void testExifInterfaceForRaw(String fileName, int typedArrayResourceId)
    444             throws IOException {
    445         ExpectedValue expectedValue = new ExpectedValue(
    446                 getContext().getResources().obtainTypedArray(typedArrayResourceId));
    447         File imageFile = new File(Environment.getExternalStorageDirectory(), fileName);
    448 
    449         // Test for reading from various inputs.
    450         testExifInterfaceCommon(imageFile, expectedValue);
    451 
    452         // Since ExifInterface does not support for saving attributes for RAW files, do not test
    453         // about writing back in here.
    454     }
    455 
    456     public void testReadExifDataFromExifByteOrderIIJpeg() throws Throwable {
    457         testExifInterfaceForJpeg(EXIF_BYTE_ORDER_II_JPEG, R.array.exifbyteorderii_jpg);
    458     }
    459 
    460     public void testReadExifDataFromExifByteOrderMMJpeg() throws Throwable {
    461         testExifInterfaceForJpeg(EXIF_BYTE_ORDER_MM_JPEG, R.array.exifbyteordermm_jpg);
    462     }
    463 
    464     public void testReadExifDataFromLgG4Iso800Dng() throws Throwable {
    465         testExifInterfaceForRaw(LG_G4_ISO_800_DNG, R.array.lg_g4_iso_800_dng);
    466     }
    467 
    468     public void testDoNotFailOnCorruptedImage() throws Throwable {
    469         // To keep the compatibility with old versions of ExifInterface, even on a corrupted image,
    470         // it shouldn't raise any exceptions except an IOException when unable to open a file.
    471         byte[] bytes = new byte[1024];
    472         try {
    473             new ExifInterface(new ByteArrayInputStream(bytes));
    474             // Always success
    475         } catch (IOException e) {
    476             fail("Should not reach here!");
    477         }
    478     }
    479 
    480     public void testReadExifDataFromVolantisJpg() throws Throwable {
    481         // Test if it is possible to parse the volantis generated JPEG smoothly.
    482         testExifInterfaceForJpeg(VOLANTIS_JPEG, R.array.volantis_jpg);
    483     }
    484 }
    485