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