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