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 getString(TypedArray typedArray, int index)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 ExpectedValue(TypedArray typedArray)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 setUp()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 tearDown()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 printExifTagsAndValues(String fileName, ExifInterface exifInterface)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 assertIntTag(ExifInterface exifInterface, String tag, int expectedValue)240 private void assertIntTag(ExifInterface exifInterface, String tag, int expectedValue) { 241 int intValue = exifInterface.getAttributeInt(tag, 0); 242 assertEquals(expectedValue, intValue); 243 } 244 assertDoubleTag(ExifInterface exifInterface, String tag, float expectedValue)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 assertStringTag(ExifInterface exifInterface, String tag, String expectedValue)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 compareWithExpectedValue(ExifInterface exifInterface, ExpectedValue expectedValue, String verboseTag)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 testExifInterfaceCommon(File imageFile, ExpectedValue expectedValue)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 testSaveAttributes_withFileName(File imageFile, ExpectedValue expectedValue)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 testSaveAttributes_withFileDescriptor(File imageFile, ExpectedValue expectedValue)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 testSaveAttributes_withInputStream(File imageFile, ExpectedValue expectedValue)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 testExifInterfaceForJpeg(String fileName, int typedArrayResourceId)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 testExifInterfaceForRaw(String fileName, int typedArrayResourceId)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 testReadExifDataFromExifByteOrderIIJpeg()456 public void testReadExifDataFromExifByteOrderIIJpeg() throws Throwable { 457 testExifInterfaceForJpeg(EXIF_BYTE_ORDER_II_JPEG, R.array.exifbyteorderii_jpg); 458 } 459 testReadExifDataFromExifByteOrderMMJpeg()460 public void testReadExifDataFromExifByteOrderMMJpeg() throws Throwable { 461 testExifInterfaceForJpeg(EXIF_BYTE_ORDER_MM_JPEG, R.array.exifbyteordermm_jpg); 462 } 463 testReadExifDataFromLgG4Iso800Dng()464 public void testReadExifDataFromLgG4Iso800Dng() throws Throwable { 465 testExifInterfaceForRaw(LG_G4_ISO_800_DNG, R.array.lg_g4_iso_800_dng); 466 } 467 testDoNotFailOnCorruptedImage()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 testReadExifDataFromVolantisJpg()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