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 android.media.misc.cts; 18 19 import static android.media.ExifInterface.TAG_SUBJECT_AREA; 20 21 import static org.junit.Assert.assertEquals; 22 import static org.junit.Assert.assertFalse; 23 import static org.junit.Assert.assertNotNull; 24 import static org.junit.Assert.assertNull; 25 import static org.junit.Assert.assertTrue; 26 import static org.junit.Assert.fail; 27 28 import android.content.Context; 29 import android.content.res.TypedArray; 30 import android.graphics.Bitmap; 31 import android.graphics.BitmapFactory; 32 import android.media.ExifInterface; 33 import android.os.FileUtils; 34 import android.os.StrictMode; 35 import android.platform.test.annotations.AppModeFull; 36 import android.system.ErrnoException; 37 import android.system.Os; 38 import android.system.OsConstants; 39 import android.util.Log; 40 41 import androidx.test.ext.junit.runners.AndroidJUnit4; 42 import androidx.test.platform.app.InstrumentationRegistry; 43 44 import com.android.compatibility.common.util.FrameworkSpecificTest; 45 import com.android.compatibility.common.util.Preconditions; 46 47 import libcore.io.IoUtils; 48 49 import org.junit.Before; 50 import org.junit.Test; 51 import org.junit.runner.RunWith; 52 53 import java.io.BufferedInputStream; 54 import java.io.ByteArrayInputStream; 55 import java.io.EOFException; 56 import java.io.File; 57 import java.io.FileDescriptor; 58 import java.io.FileInputStream; 59 import java.io.IOException; 60 import java.io.InputStream; 61 import java.nio.charset.StandardCharsets; 62 import java.util.Objects; 63 64 @FrameworkSpecificTest 65 @AppModeFull(reason = "Instant apps cannot access the SD card") 66 @RunWith(AndroidJUnit4.class) 67 public class ExifInterfaceTest { 68 private static final String TAG = ExifInterface.class.getSimpleName(); 69 private static final boolean VERBOSE = false; // lots of logging 70 71 private static final double DIFFERENCE_TOLERANCE = .001; 72 73 static final String mInpPrefix = WorkDir.getMediaDirString() + "images/"; 74 75 // This base directory is needed for the files listed below. 76 // These files will be available for download in Android O release. 77 // Link: https://source.android.com/compatibility/cts/downloads.html#cts-media-files 78 private static final String JPEG_WITH_EXIF_BYTE_ORDER_II = "image_exif_byte_order_ii.jpg"; 79 private static final String JPEG_WITH_EXIF_BYTE_ORDER_MM = "image_exif_byte_order_mm.jpg"; 80 private static final String DNG_WITH_EXIF_WITH_XMP = "lg_g4_iso_800.dng"; 81 private static final String JPEG_WITH_EXIF_WITH_XMP = "lg_g4_iso_800.jpg"; 82 private static final String ARW_SONY_RX_100 = "sony_rx_100.arw"; 83 private static final String CR2_CANON_G7X = "canon_g7x.cr2"; 84 private static final String RAF_FUJI_X20 = "fuji_x20.raf"; 85 private static final String NEF_NIKON_1AW1 = "nikon_1aw1.nef"; 86 private static final String NRW_NIKON_P330 = "nikon_p330.nrw"; 87 private static final String ORF_OLYMPUS_E_PL3 = "olympus_e_pl3.orf"; 88 private static final String RW2_PANASONIC_GM5 = "panasonic_gm5.rw2"; 89 private static final String PEF_PENTAX_K5 = "pentax_k5.pef"; 90 private static final String SRW_SAMSUNG_NX3000 = "samsung_nx3000.srw"; 91 private static final String JPEG_VOLANTIS = "volantis.jpg"; 92 private static final String WEBP_WITH_EXIF = "webp_with_exif.webp"; 93 private static final String WEBP_WITHOUT_EXIF_WITH_ANIM_DATA = 94 "webp_with_anim_without_exif.webp"; 95 private static final String WEBP_WITHOUT_EXIF = "webp_without_exif.webp"; 96 private static final String WEBP_WITHOUT_EXIF_WITH_LOSSLESS_ENCODING = 97 "webp_lossless_without_exif.webp"; 98 private static final String PNG_WITH_EXIF_BYTE_ORDER_II = "png_with_exif_byte_order_ii.png"; 99 private static final String PNG_WITHOUT_EXIF = "png_without_exif.png"; 100 private static final String JPEG_WITH_DATETIME_TAG = "jpeg_with_datetime_tag.jpg"; 101 102 private static final String[] EXIF_TAGS = { 103 ExifInterface.TAG_MAKE, 104 ExifInterface.TAG_MODEL, 105 ExifInterface.TAG_F_NUMBER, 106 ExifInterface.TAG_DATETIME_ORIGINAL, 107 ExifInterface.TAG_EXPOSURE_TIME, 108 ExifInterface.TAG_FLASH, 109 ExifInterface.TAG_FOCAL_LENGTH, 110 ExifInterface.TAG_GPS_ALTITUDE, 111 ExifInterface.TAG_GPS_ALTITUDE_REF, 112 ExifInterface.TAG_GPS_DATESTAMP, 113 ExifInterface.TAG_GPS_LATITUDE, 114 ExifInterface.TAG_GPS_LATITUDE_REF, 115 ExifInterface.TAG_GPS_LONGITUDE, 116 ExifInterface.TAG_GPS_LONGITUDE_REF, 117 ExifInterface.TAG_GPS_PROCESSING_METHOD, 118 ExifInterface.TAG_GPS_TIMESTAMP, 119 ExifInterface.TAG_IMAGE_LENGTH, 120 ExifInterface.TAG_IMAGE_WIDTH, 121 ExifInterface.TAG_ISO_SPEED_RATINGS, 122 ExifInterface.TAG_ORIENTATION, 123 ExifInterface.TAG_WHITE_BALANCE 124 }; 125 126 private static class ExpectedValue { 127 // Thumbnail information. 128 public final boolean hasThumbnail; 129 public final int thumbnailWidth; 130 public final int thumbnailHeight; 131 public final boolean isThumbnailCompressed; 132 public final int thumbnailOffset; 133 public final int thumbnailLength; 134 135 // GPS information. 136 public final boolean hasLatLong; 137 public final float latitude; 138 public final int latitudeOffset; 139 public final int latitudeLength; 140 public final float longitude; 141 public final float altitude; 142 143 // Make information 144 public final boolean hasMake; 145 public final int makeOffset; 146 public final int makeLength; 147 public final String make; 148 149 // Values. 150 public final String model; 151 public final float aperture; 152 public final String dateTimeOriginal; 153 public final float exposureTime; 154 public final float flash; 155 public final String focalLength; 156 public final String gpsAltitude; 157 public final String gpsAltitudeRef; 158 public final String gpsDatestamp; 159 public final String gpsLatitude; 160 public final String gpsLatitudeRef; 161 public final String gpsLongitude; 162 public final String gpsLongitudeRef; 163 public final String gpsProcessingMethod; 164 public final String gpsTimestamp; 165 public final int imageLength; 166 public final int imageWidth; 167 public final String iso; 168 public final int orientation; 169 public final int whiteBalance; 170 171 // XMP information. 172 public final boolean hasXmp; 173 public final int xmpOffset; 174 public final int xmpLength; 175 getString(TypedArray typedArray, int index)176 private static String getString(TypedArray typedArray, int index) { 177 String stringValue = typedArray.getString(index); 178 if (stringValue == null || stringValue.equals("")) { 179 return null; 180 } 181 return stringValue.trim(); 182 } 183 ExpectedValue(TypedArray typedArray)184 public ExpectedValue(TypedArray typedArray) { 185 int index = 0; 186 187 // Reads thumbnail information. 188 hasThumbnail = typedArray.getBoolean(index++, false); 189 thumbnailOffset = typedArray.getInt(index++, -1); 190 thumbnailLength = typedArray.getInt(index++, -1); 191 thumbnailWidth = typedArray.getInt(index++, 0); 192 thumbnailHeight = typedArray.getInt(index++, 0); 193 isThumbnailCompressed = typedArray.getBoolean(index++, false); 194 195 // Reads GPS information. 196 hasLatLong = typedArray.getBoolean(index++, false); 197 latitudeOffset = typedArray.getInt(index++, -1); 198 latitudeLength = typedArray.getInt(index++, -1); 199 latitude = typedArray.getFloat(index++, 0f); 200 longitude = typedArray.getFloat(index++, 0f); 201 altitude = typedArray.getFloat(index++, 0f); 202 203 // Reads Make information. 204 hasMake = typedArray.getBoolean(index++, false); 205 makeOffset = typedArray.getInt(index++, -1); 206 makeLength = typedArray.getInt(index++, -1); 207 make = getString(typedArray, index++); 208 209 // Reads values. 210 model = getString(typedArray, index++); 211 aperture = typedArray.getFloat(index++, 0f); 212 dateTimeOriginal = getString(typedArray, index++); 213 exposureTime = typedArray.getFloat(index++, 0f); 214 flash = typedArray.getFloat(index++, 0f); 215 focalLength = getString(typedArray, index++); 216 gpsAltitude = getString(typedArray, index++); 217 gpsAltitudeRef = getString(typedArray, index++); 218 gpsDatestamp = getString(typedArray, index++); 219 gpsLatitude = getString(typedArray, index++); 220 gpsLatitudeRef = getString(typedArray, index++); 221 gpsLongitude = getString(typedArray, index++); 222 gpsLongitudeRef = getString(typedArray, index++); 223 gpsProcessingMethod = getString(typedArray, index++); 224 gpsTimestamp = getString(typedArray, index++); 225 imageLength = typedArray.getInt(index++, 0); 226 imageWidth = typedArray.getInt(index++, 0); 227 iso = getString(typedArray, index++); 228 orientation = typedArray.getInt(index++, 0); 229 whiteBalance = typedArray.getInt(index++, 0); 230 231 // Reads XMP information. 232 hasXmp = typedArray.getBoolean(index++, false); 233 xmpOffset = typedArray.getInt(index++, 0); 234 xmpLength = typedArray.getInt(index++, 0); 235 236 typedArray.recycle(); 237 } 238 } 239 getContext()240 private Context getContext() { 241 return InstrumentationRegistry.getInstrumentation().getContext(); 242 } 243 printExifTagsAndValues(String fileName, ExifInterface exifInterface)244 private void printExifTagsAndValues(String fileName, ExifInterface exifInterface) { 245 // Prints thumbnail information. 246 if (exifInterface.hasThumbnail()) { 247 byte[] thumbnailBytes = exifInterface.getThumbnailBytes(); 248 if (thumbnailBytes != null) { 249 Log.v(TAG, fileName + " Thumbnail size = " + thumbnailBytes.length); 250 Bitmap bitmap = exifInterface.getThumbnailBitmap(); 251 if (bitmap == null) { 252 Log.e(TAG, fileName + " Corrupted thumbnail!"); 253 } else { 254 Log.v(TAG, fileName + " Thumbnail size: " + bitmap.getWidth() + ", " 255 + bitmap.getHeight()); 256 } 257 } else { 258 Log.e(TAG, fileName + " Unexpected result: No thumbnails were found. " 259 + "A thumbnail is expected."); 260 } 261 } else { 262 if (exifInterface.getThumbnailBytes() != null) { 263 Log.e(TAG, fileName + " Unexpected result: A thumbnail was found. " 264 + "No thumbnail is expected."); 265 } else { 266 Log.v(TAG, fileName + " No thumbnail"); 267 } 268 } 269 270 // Prints GPS information. 271 Log.v(TAG, fileName + " Altitude = " + exifInterface.getAltitude(.0)); 272 273 float[] latLong = new float[2]; 274 if (exifInterface.getLatLong(latLong)) { 275 Log.v(TAG, fileName + " Latitude = " + latLong[0]); 276 Log.v(TAG, fileName + " Longitude = " + latLong[1]); 277 } else { 278 Log.v(TAG, fileName + " No latlong data"); 279 } 280 281 // Prints values. 282 for (String tagKey : EXIF_TAGS) { 283 String tagValue = exifInterface.getAttribute(tagKey); 284 Log.v(TAG, fileName + " Key{" + tagKey + "} = '" + tagValue + "'"); 285 } 286 } 287 assertIntTag(ExifInterface exifInterface, String tag, int expectedValue)288 private void assertIntTag(ExifInterface exifInterface, String tag, int expectedValue) { 289 int intValue = exifInterface.getAttributeInt(tag, 0); 290 assertEquals(expectedValue, intValue); 291 } 292 assertFloatTag(ExifInterface exifInterface, String tag, float expectedValue)293 private void assertFloatTag(ExifInterface exifInterface, String tag, float expectedValue) { 294 double doubleValue = exifInterface.getAttributeDouble(tag, 0.0); 295 assertEquals(expectedValue, doubleValue, DIFFERENCE_TOLERANCE); 296 } 297 assertStringTag(ExifInterface exifInterface, String tag, String expectedValue)298 private void assertStringTag(ExifInterface exifInterface, String tag, String expectedValue) { 299 String stringValue = exifInterface.getAttribute(tag); 300 if (stringValue != null) { 301 stringValue = stringValue.trim(); 302 } 303 stringValue = (stringValue == "") ? null : stringValue; 304 305 assertEquals(expectedValue, stringValue); 306 } 307 compareWithExpectedValue(ExifInterface exifInterface, ExpectedValue expectedValue, String verboseTag, boolean assertRanges)308 private void compareWithExpectedValue(ExifInterface exifInterface, 309 ExpectedValue expectedValue, String verboseTag, boolean assertRanges) { 310 if (VERBOSE) { 311 printExifTagsAndValues(verboseTag, exifInterface); 312 } 313 // Checks a thumbnail image. 314 assertEquals(expectedValue.hasThumbnail, exifInterface.hasThumbnail()); 315 if (expectedValue.hasThumbnail) { 316 assertNotNull(exifInterface.getThumbnailRange()); 317 if (assertRanges) { 318 final long[] thumbnailRange = exifInterface.getThumbnailRange(); 319 assertEquals(expectedValue.thumbnailOffset, thumbnailRange[0]); 320 assertEquals(expectedValue.thumbnailLength, thumbnailRange[1]); 321 } 322 testThumbnail(expectedValue, exifInterface); 323 } else { 324 assertNull(exifInterface.getThumbnailRange()); 325 assertNull(exifInterface.getThumbnail()); 326 assertNull(exifInterface.getThumbnailBitmap()); 327 assertFalse(exifInterface.isThumbnailCompressed()); 328 } 329 330 // Checks GPS information. 331 float[] latLong = new float[2]; 332 assertEquals(expectedValue.hasLatLong, exifInterface.getLatLong(latLong)); 333 if (expectedValue.hasLatLong) { 334 assertNotNull(exifInterface.getAttributeRange(ExifInterface.TAG_GPS_LATITUDE)); 335 if (assertRanges) { 336 final long[] latitudeRange = exifInterface 337 .getAttributeRange(ExifInterface.TAG_GPS_LATITUDE); 338 assertEquals(expectedValue.latitudeOffset, latitudeRange[0]); 339 assertEquals(expectedValue.latitudeLength, latitudeRange[1]); 340 } 341 assertEquals(expectedValue.latitude, latLong[0], DIFFERENCE_TOLERANCE); 342 assertEquals(expectedValue.longitude, latLong[1], DIFFERENCE_TOLERANCE); 343 assertTrue(exifInterface.hasAttribute(ExifInterface.TAG_GPS_LATITUDE)); 344 assertTrue(exifInterface.hasAttribute(ExifInterface.TAG_GPS_LONGITUDE)); 345 } else { 346 assertNull(exifInterface.getAttributeRange(ExifInterface.TAG_GPS_LATITUDE)); 347 assertFalse(exifInterface.hasAttribute(ExifInterface.TAG_GPS_LATITUDE)); 348 assertFalse(exifInterface.hasAttribute(ExifInterface.TAG_GPS_LONGITUDE)); 349 } 350 assertEquals(expectedValue.altitude, exifInterface.getAltitude(.0), DIFFERENCE_TOLERANCE); 351 352 // Checks Make information. 353 String make = exifInterface.getAttribute(ExifInterface.TAG_MAKE); 354 assertEquals(expectedValue.hasMake, make != null); 355 if (expectedValue.hasMake) { 356 assertNotNull(exifInterface.getAttributeRange(ExifInterface.TAG_MAKE)); 357 if (assertRanges) { 358 final long[] makeRange = exifInterface 359 .getAttributeRange(ExifInterface.TAG_MAKE); 360 assertEquals(expectedValue.makeOffset, makeRange[0]); 361 assertEquals(expectedValue.makeLength, makeRange[1]); 362 } 363 assertEquals(expectedValue.make, make.trim()); 364 } else { 365 assertNull(exifInterface.getAttributeRange(ExifInterface.TAG_MAKE)); 366 assertFalse(exifInterface.hasAttribute(ExifInterface.TAG_MAKE)); 367 } 368 369 // Checks values. 370 assertStringTag(exifInterface, ExifInterface.TAG_MAKE, expectedValue.make); 371 assertStringTag(exifInterface, ExifInterface.TAG_MODEL, expectedValue.model); 372 assertFloatTag(exifInterface, ExifInterface.TAG_F_NUMBER, expectedValue.aperture); 373 assertStringTag(exifInterface, ExifInterface.TAG_DATETIME_ORIGINAL, 374 expectedValue.dateTimeOriginal); 375 assertFloatTag(exifInterface, ExifInterface.TAG_EXPOSURE_TIME, expectedValue.exposureTime); 376 assertFloatTag(exifInterface, ExifInterface.TAG_FLASH, expectedValue.flash); 377 assertStringTag(exifInterface, ExifInterface.TAG_FOCAL_LENGTH, expectedValue.focalLength); 378 assertStringTag(exifInterface, ExifInterface.TAG_GPS_ALTITUDE, expectedValue.gpsAltitude); 379 assertStringTag(exifInterface, ExifInterface.TAG_GPS_ALTITUDE_REF, 380 expectedValue.gpsAltitudeRef); 381 assertStringTag(exifInterface, ExifInterface.TAG_GPS_DATESTAMP, expectedValue.gpsDatestamp); 382 assertStringTag(exifInterface, ExifInterface.TAG_GPS_LATITUDE, expectedValue.gpsLatitude); 383 assertStringTag(exifInterface, ExifInterface.TAG_GPS_LATITUDE_REF, 384 expectedValue.gpsLatitudeRef); 385 assertStringTag(exifInterface, ExifInterface.TAG_GPS_LONGITUDE, expectedValue.gpsLongitude); 386 assertStringTag(exifInterface, ExifInterface.TAG_GPS_LONGITUDE_REF, 387 expectedValue.gpsLongitudeRef); 388 assertStringTag(exifInterface, ExifInterface.TAG_GPS_PROCESSING_METHOD, 389 expectedValue.gpsProcessingMethod); 390 assertStringTag(exifInterface, ExifInterface.TAG_GPS_TIMESTAMP, expectedValue.gpsTimestamp); 391 assertIntTag(exifInterface, ExifInterface.TAG_IMAGE_LENGTH, expectedValue.imageLength); 392 assertIntTag(exifInterface, ExifInterface.TAG_IMAGE_WIDTH, expectedValue.imageWidth); 393 assertStringTag(exifInterface, ExifInterface.TAG_ISO_SPEED_RATINGS, expectedValue.iso); 394 assertIntTag(exifInterface, ExifInterface.TAG_ORIENTATION, expectedValue.orientation); 395 assertIntTag(exifInterface, ExifInterface.TAG_WHITE_BALANCE, expectedValue.whiteBalance); 396 397 if (expectedValue.hasXmp) { 398 assertNotNull(exifInterface.getAttributeRange(ExifInterface.TAG_XMP)); 399 if (assertRanges) { 400 final long[] xmpRange = exifInterface.getAttributeRange(ExifInterface.TAG_XMP); 401 assertEquals(expectedValue.xmpOffset, xmpRange[0]); 402 assertEquals(expectedValue.xmpLength, xmpRange[1]); 403 } 404 final String xmp = new String(exifInterface.getAttributeBytes(ExifInterface.TAG_XMP), 405 StandardCharsets.UTF_8); 406 // We're only interested in confirming that we were able to extract 407 // valid XMP data, which must always include this XML tag; a full 408 // XMP parser is beyond the scope of ExifInterface. See XMP 409 // Specification Part 1, Section C.2.2 for additional details. 410 if (!xmp.contains("<rdf:RDF")) { 411 fail("Invalid XMP: " + xmp); 412 } 413 } else { 414 assertNull(exifInterface.getAttributeRange(ExifInterface.TAG_XMP)); 415 } 416 } 417 readFromStandaloneDataWithExif(String fileName, int typedArrayResourceId)418 private void readFromStandaloneDataWithExif(String fileName, int typedArrayResourceId) 419 throws IOException { 420 ExpectedValue expectedValue = new ExpectedValue( 421 getContext().getResources().obtainTypedArray(typedArrayResourceId)); 422 423 Preconditions.assertTestFileExists(mInpPrefix + fileName); 424 File imageFile = new File(mInpPrefix, fileName); 425 String verboseTag = imageFile.getName(); 426 427 FileInputStream fis = new FileInputStream(imageFile); 428 // Skip the following marker bytes (0xff, 0xd8, 0xff, 0xe1) 429 fis.skip(4); 430 // Read the value of the length of the exif data 431 short length = readShort(fis); 432 byte[] exifBytes = new byte[length]; 433 fis.read(exifBytes); 434 435 ByteArrayInputStream bin = new ByteArrayInputStream(exifBytes); 436 ExifInterface exifInterface = 437 new ExifInterface(bin, ExifInterface.STREAM_TYPE_EXIF_DATA_ONLY); 438 compareWithExpectedValue(exifInterface, expectedValue, verboseTag, true); 439 } 440 testExifInterfaceCommon(String fileName, ExpectedValue expectedValue)441 private void testExifInterfaceCommon(String fileName, ExpectedValue expectedValue) 442 throws IOException { 443 File imageFile = new File(mInpPrefix, fileName); 444 Preconditions.assertTestFileExists(mInpPrefix + fileName); 445 String verboseTag = imageFile.getName(); 446 447 // Creates via path. 448 ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath()); 449 assertNotNull(exifInterface); 450 compareWithExpectedValue(exifInterface, expectedValue, verboseTag, true); 451 452 // Creates via file. 453 exifInterface = new ExifInterface(imageFile); 454 compareWithExpectedValue(exifInterface, expectedValue, verboseTag, true); 455 456 InputStream in = null; 457 // Creates via InputStream. 458 try { 459 in = new BufferedInputStream(new FileInputStream(imageFile.getAbsolutePath())); 460 exifInterface = new ExifInterface(in); 461 compareWithExpectedValue(exifInterface, expectedValue, verboseTag, true); 462 } finally { 463 IoUtils.closeQuietly(in); 464 } 465 466 // Creates via FileDescriptor. 467 FileDescriptor fd = null; 468 try { 469 fd = Os.open(imageFile.getAbsolutePath(), OsConstants.O_RDONLY, 0600); 470 exifInterface = new ExifInterface(fd); 471 compareWithExpectedValue(exifInterface, expectedValue, verboseTag, true); 472 } catch (ErrnoException e) { 473 throw e.rethrowAsIOException(); 474 } finally { 475 IoUtils.closeQuietly(fd); 476 } 477 } 478 testExifInterfaceRange(String fileName, ExpectedValue expectedValue)479 private void testExifInterfaceRange(String fileName, ExpectedValue expectedValue) 480 throws IOException { 481 Preconditions.assertTestFileExists(mInpPrefix + fileName); 482 File imageFile = new File(mInpPrefix, fileName); 483 InputStream in = null; 484 try { 485 in = new BufferedInputStream(new FileInputStream(imageFile.getAbsolutePath())); 486 if (expectedValue.hasThumbnail) { 487 in.skip(expectedValue.thumbnailOffset); 488 byte[] thumbnailBytes = new byte[expectedValue.thumbnailLength]; 489 if (in.read(thumbnailBytes) != expectedValue.thumbnailLength) { 490 throw new IOException("Failed to read the expected thumbnail length"); 491 } 492 // TODO: Need a way to check uncompressed thumbnail file 493 if (expectedValue.isThumbnailCompressed) { 494 Bitmap thumbnailBitmap = BitmapFactory.decodeByteArray(thumbnailBytes, 0, 495 thumbnailBytes.length); 496 assertNotNull(thumbnailBitmap); 497 assertEquals(expectedValue.thumbnailWidth, thumbnailBitmap.getWidth()); 498 assertEquals(expectedValue.thumbnailHeight, thumbnailBitmap.getHeight()); 499 } 500 } 501 // TODO: Creating a new input stream is a temporary 502 // workaround for BufferedInputStream#mark/reset not working properly for 503 // LG_G4_ISO_800_DNG. Need to investigate cause. 504 in = new BufferedInputStream(new FileInputStream(imageFile.getAbsolutePath())); 505 if (expectedValue.hasMake) { 506 in.skip(expectedValue.makeOffset); 507 byte[] makeBytes = new byte[expectedValue.makeLength]; 508 if (in.read(makeBytes) != expectedValue.makeLength) { 509 throw new IOException("Failed to read the expected make length"); 510 } 511 String makeString = new String(makeBytes); 512 // Remove null bytes 513 makeString = makeString.replaceAll("\u0000.*", ""); 514 assertEquals(expectedValue.make, makeString.trim()); 515 } 516 in = new BufferedInputStream(new FileInputStream(imageFile.getAbsolutePath())); 517 if (expectedValue.hasXmp) { 518 in.skip(expectedValue.xmpOffset); 519 byte[] identifierBytes = new byte[expectedValue.xmpLength]; 520 if (in.read(identifierBytes) != expectedValue.xmpLength) { 521 throw new IOException("Failed to read the expected xmp length"); 522 } 523 final String xmpIdentifier = "<?xpacket begin="; 524 assertTrue(new String(identifierBytes, StandardCharsets.UTF_8) 525 .startsWith(xmpIdentifier)); 526 } 527 // TODO: Add code for retrieving raw latitude data using offset and length 528 } finally { 529 IoUtils.closeQuietly(in); 530 } 531 } 532 writeToFilesWithExif(String fileName, int typedArrayResourceId)533 private void writeToFilesWithExif(String fileName, int typedArrayResourceId) 534 throws IOException { 535 ExpectedValue expectedValue = new ExpectedValue( 536 getContext().getResources().obtainTypedArray(typedArrayResourceId)); 537 538 Preconditions.assertTestFileExists(mInpPrefix + fileName); 539 File srcFile = new File(mInpPrefix, fileName); 540 File imageFile = clone(srcFile); 541 String verboseTag = imageFile.getName(); 542 543 ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath()); 544 exifInterface.saveAttributes(); 545 exifInterface = new ExifInterface(imageFile.getAbsolutePath()); 546 compareWithExpectedValue(exifInterface, expectedValue, verboseTag, false); 547 assertBitmapsEquivalent(srcFile, imageFile); 548 549 // Test for modifying one attribute. 550 exifInterface = new ExifInterface(imageFile.getAbsolutePath()); 551 String backupValue = exifInterface.getAttribute(ExifInterface.TAG_MAKE); 552 exifInterface.setAttribute(ExifInterface.TAG_MAKE, "abc"); 553 exifInterface.saveAttributes(); 554 // Check if thumbnail offset and length are properly updated without parsing the data again. 555 if (expectedValue.hasThumbnail) { 556 testThumbnail(expectedValue, exifInterface); 557 } 558 assertEquals("abc", exifInterface.getAttribute(ExifInterface.TAG_MAKE)); 559 // Check if thumbnail bytes can be retrieved from the new thumbnail range. 560 if (expectedValue.hasThumbnail) { 561 testThumbnail(expectedValue, exifInterface); 562 } 563 564 // Restore the backup value. 565 exifInterface.setAttribute(ExifInterface.TAG_MAKE, backupValue); 566 exifInterface.saveAttributes(); 567 exifInterface = new ExifInterface(imageFile.getAbsolutePath()); 568 compareWithExpectedValue(exifInterface, expectedValue, verboseTag, false); 569 570 FileDescriptor fd = null; 571 try { 572 fd = Os.open(imageFile.getAbsolutePath(), OsConstants.O_RDWR, 0600); 573 exifInterface = new ExifInterface(fd); 574 exifInterface.saveAttributes(); 575 Os.lseek(fd, 0, OsConstants.SEEK_SET); 576 exifInterface = new ExifInterface(fd); 577 compareWithExpectedValue(exifInterface, expectedValue, verboseTag, false); 578 579 // Test for modifying one attribute. 580 backupValue = exifInterface.getAttribute(ExifInterface.TAG_MAKE); 581 exifInterface.setAttribute(ExifInterface.TAG_MAKE, "abc"); 582 exifInterface.saveAttributes(); 583 // Check if thumbnail offset and length are properly updated without parsing the data 584 // again. 585 if (expectedValue.hasThumbnail) { 586 testThumbnail(expectedValue, exifInterface); 587 } 588 Os.lseek(fd, 0, OsConstants.SEEK_SET); 589 exifInterface = new ExifInterface(fd); 590 assertEquals("abc", exifInterface.getAttribute(ExifInterface.TAG_MAKE)); 591 // Check if thumbnail bytes can be retrieved from the new thumbnail range. 592 if (expectedValue.hasThumbnail) { 593 testThumbnail(expectedValue, exifInterface); 594 } 595 596 // Restore the backup value. 597 exifInterface.setAttribute(ExifInterface.TAG_MAKE, backupValue); 598 exifInterface.saveAttributes(); 599 Os.lseek(fd, 0, OsConstants.SEEK_SET); 600 exifInterface = new ExifInterface(fd); 601 compareWithExpectedValue(exifInterface, expectedValue, verboseTag, false); 602 } catch (ErrnoException e) { 603 throw e.rethrowAsIOException(); 604 } finally { 605 IoUtils.closeQuietly(fd); 606 } 607 imageFile.delete(); 608 } 609 readFromFilesWithExif(String fileName, int typedArrayResourceId)610 private void readFromFilesWithExif(String fileName, int typedArrayResourceId) 611 throws IOException { 612 ExpectedValue expectedValue = new ExpectedValue( 613 getContext().getResources().obtainTypedArray(typedArrayResourceId)); 614 615 testExifInterfaceCommon(fileName, expectedValue); 616 617 // Test for checking expected range by retrieving raw data with given offset and length. 618 testExifInterfaceRange(fileName, expectedValue); 619 } 620 writeToFilesWithoutExif(String fileName)621 private void writeToFilesWithoutExif(String fileName) throws IOException { 622 // Test for reading from external data storage. 623 Preconditions.assertTestFileExists(mInpPrefix + fileName); 624 File srcFile = new File(mInpPrefix, fileName); 625 File imageFile = clone(srcFile); 626 627 ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath()); 628 exifInterface.setAttribute(ExifInterface.TAG_MAKE, "abc"); 629 exifInterface.saveAttributes(); 630 631 assertBitmapsEquivalent(srcFile, imageFile); 632 exifInterface = new ExifInterface(imageFile.getAbsolutePath()); 633 String make = exifInterface.getAttribute(ExifInterface.TAG_MAKE); 634 assertEquals("abc", make); 635 imageFile.delete(); 636 } 637 testThumbnail(ExpectedValue expectedValue, ExifInterface exifInterface)638 private void testThumbnail(ExpectedValue expectedValue, ExifInterface exifInterface) { 639 byte[] thumbnailBytes = exifInterface.getThumbnailBytes(); 640 assertNotNull(thumbnailBytes); 641 642 // Note: NEF file (nikon_1aw1.nef) contains uncompressed thumbnail. 643 Bitmap thumbnailBitmap = exifInterface.getThumbnailBitmap(); 644 assertNotNull(thumbnailBitmap); 645 assertEquals(expectedValue.thumbnailWidth, thumbnailBitmap.getWidth()); 646 assertEquals(expectedValue.thumbnailHeight, thumbnailBitmap.getHeight()); 647 assertEquals(expectedValue.isThumbnailCompressed, exifInterface.isThumbnailCompressed()); 648 } 649 650 @Before setUp()651 public void setUp() { 652 StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() 653 .detectUnbufferedIo() 654 .penaltyDeath() 655 .build()); 656 } 657 658 @Test testReadExifDataFromExifByteOrderIIJpeg()659 public void testReadExifDataFromExifByteOrderIIJpeg() throws Throwable { 660 readFromFilesWithExif(JPEG_WITH_EXIF_BYTE_ORDER_II, R.array.jpeg_with_exif_byte_order_ii); 661 writeToFilesWithExif(JPEG_WITH_EXIF_BYTE_ORDER_II, R.array.jpeg_with_exif_byte_order_ii); 662 } 663 664 @Test testReadExifDataFromExifByteOrderMMJpeg()665 public void testReadExifDataFromExifByteOrderMMJpeg() throws Throwable { 666 readFromFilesWithExif(JPEG_WITH_EXIF_BYTE_ORDER_MM, R.array.jpeg_with_exif_byte_order_mm); 667 writeToFilesWithExif(JPEG_WITH_EXIF_BYTE_ORDER_MM, R.array.jpeg_with_exif_byte_order_mm); 668 } 669 670 @Test testReadExifDataFromLgG4Iso800Dng()671 public void testReadExifDataFromLgG4Iso800Dng() throws Throwable { 672 readFromFilesWithExif(DNG_WITH_EXIF_WITH_XMP, R.array.dng_with_exif_with_xmp); 673 } 674 675 @Test testReadExifDataFromLgG4Iso800Jpg()676 public void testReadExifDataFromLgG4Iso800Jpg() throws Throwable { 677 readFromFilesWithExif(JPEG_WITH_EXIF_WITH_XMP, R.array.jpeg_with_exif_with_xmp); 678 writeToFilesWithExif(JPEG_WITH_EXIF_WITH_XMP, R.array.jpeg_with_exif_with_xmp); 679 } 680 681 @Test testDoNotFailOnCorruptedImage()682 public void testDoNotFailOnCorruptedImage() throws Throwable { 683 // To keep the compatibility with old versions of ExifInterface, even on a corrupted image, 684 // it shouldn't raise any exceptions except an IOException when unable to open a file. 685 byte[] bytes = new byte[1024]; 686 try { 687 new ExifInterface(new ByteArrayInputStream(bytes)); 688 // Always success 689 } catch (IOException e) { 690 fail("Should not reach here!"); 691 } 692 } 693 694 @Test testReadExifDataFromVolantisJpg()695 public void testReadExifDataFromVolantisJpg() throws Throwable { 696 // Test if it is possible to parse the volantis generated JPEG smoothly. 697 readFromFilesWithExif(JPEG_VOLANTIS, R.array.volantis_jpg); 698 writeToFilesWithExif(JPEG_VOLANTIS, R.array.volantis_jpg); 699 } 700 701 @Test testReadExifDataFromSonyRX100Arw()702 public void testReadExifDataFromSonyRX100Arw() throws Throwable { 703 readFromFilesWithExif(ARW_SONY_RX_100, R.array.sony_rx_100_arw); 704 } 705 706 @Test testReadExifDataFromCanonG7XCr2()707 public void testReadExifDataFromCanonG7XCr2() throws Throwable { 708 readFromFilesWithExif(CR2_CANON_G7X, R.array.canon_g7x_cr2); 709 } 710 711 @Test testReadExifDataFromFujiX20Raf()712 public void testReadExifDataFromFujiX20Raf() throws Throwable { 713 readFromFilesWithExif(RAF_FUJI_X20, R.array.fuji_x20_raf); 714 } 715 716 @Test testReadExifDataFromNikon1AW1Nef()717 public void testReadExifDataFromNikon1AW1Nef() throws Throwable { 718 readFromFilesWithExif(NEF_NIKON_1AW1, R.array.nikon_1aw1_nef); 719 } 720 721 @Test testReadExifDataFromNikonP330Nrw()722 public void testReadExifDataFromNikonP330Nrw() throws Throwable { 723 readFromFilesWithExif(NRW_NIKON_P330, R.array.nikon_p330_nrw); 724 } 725 726 @Test testReadExifDataFromOlympusEPL3Orf()727 public void testReadExifDataFromOlympusEPL3Orf() throws Throwable { 728 readFromFilesWithExif(ORF_OLYMPUS_E_PL3, R.array.olympus_e_pl3_orf); 729 } 730 731 @Test testReadExifDataFromPanasonicGM5Rw2()732 public void testReadExifDataFromPanasonicGM5Rw2() throws Throwable { 733 readFromFilesWithExif(RW2_PANASONIC_GM5, R.array.panasonic_gm5_rw2); 734 } 735 736 @Test testReadExifDataFromPentaxK5Pef()737 public void testReadExifDataFromPentaxK5Pef() throws Throwable { 738 readFromFilesWithExif(PEF_PENTAX_K5, R.array.pentax_k5_pef); 739 } 740 741 @Test testReadExifDataFromSamsungNX3000Srw()742 public void testReadExifDataFromSamsungNX3000Srw() throws Throwable { 743 readFromFilesWithExif(SRW_SAMSUNG_NX3000, R.array.samsung_nx3000_srw); 744 } 745 746 @Test testPngFiles()747 public void testPngFiles() throws Throwable { 748 readFromFilesWithExif(PNG_WITH_EXIF_BYTE_ORDER_II, R.array.png_with_exif_byte_order_ii); 749 writeToFilesWithoutExif(PNG_WITHOUT_EXIF); 750 } 751 752 @Test testStandaloneData()753 public void testStandaloneData() throws Throwable { 754 readFromStandaloneDataWithExif(JPEG_WITH_EXIF_BYTE_ORDER_II, 755 R.array.standalone_data_with_exif_byte_order_ii); 756 readFromStandaloneDataWithExif(JPEG_WITH_EXIF_BYTE_ORDER_MM, 757 R.array.standalone_data_with_exif_byte_order_mm); 758 } 759 760 @Test testWebpFiles()761 public void testWebpFiles() throws Throwable { 762 readFromFilesWithExif(WEBP_WITH_EXIF, R.array.webp_with_exif); 763 writeToFilesWithExif(WEBP_WITH_EXIF, R.array.webp_with_exif); 764 writeToFilesWithoutExif(WEBP_WITHOUT_EXIF_WITH_ANIM_DATA); 765 writeToFilesWithoutExif(WEBP_WITHOUT_EXIF); 766 writeToFilesWithoutExif(WEBP_WITHOUT_EXIF_WITH_LOSSLESS_ENCODING); 767 } 768 769 @Test testGetSetDateTime()770 public void testGetSetDateTime() throws Throwable { 771 final long expectedDatetimeValue = 1454059947000L; 772 final String dateTimeValue = "2017:02:02 22:22:22"; 773 final String dateTimeOriginalValue = "2017:01:01 11:11:11"; 774 775 Preconditions.assertTestFileExists(mInpPrefix + JPEG_WITH_DATETIME_TAG); 776 File srcFile = new File(mInpPrefix, JPEG_WITH_DATETIME_TAG); 777 File imageFile = clone(srcFile); 778 779 ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath()); 780 assertEquals(expectedDatetimeValue, exif.getDateTime()); 781 assertEquals(expectedDatetimeValue, exif.getDateTimeOriginal()); 782 assertEquals(expectedDatetimeValue, exif.getDateTimeDigitized()); 783 assertEquals(expectedDatetimeValue, exif.getGpsDateTime()); 784 785 exif.setAttribute(ExifInterface.TAG_DATETIME, dateTimeValue); 786 exif.setAttribute(ExifInterface.TAG_DATETIME_ORIGINAL, dateTimeOriginalValue); 787 exif.saveAttributes(); 788 789 // Check that the DATETIME value is not overwritten by DATETIME_ORIGINAL's value. 790 exif = new ExifInterface(imageFile.getAbsolutePath()); 791 assertEquals(dateTimeValue, exif.getAttribute(ExifInterface.TAG_DATETIME)); 792 assertEquals(dateTimeOriginalValue, exif.getAttribute(ExifInterface.TAG_DATETIME_ORIGINAL)); 793 794 // Now remove the DATETIME value. 795 exif.setAttribute(ExifInterface.TAG_DATETIME, null); 796 exif.saveAttributes(); 797 798 // When the DATETIME has no value, then it should be set to DATETIME_ORIGINAL's value. 799 exif = new ExifInterface(imageFile.getAbsolutePath()); 800 assertEquals(dateTimeOriginalValue, exif.getAttribute(ExifInterface.TAG_DATETIME)); 801 imageFile.delete(); 802 } 803 804 @Test testIsSupportedMimeType()805 public void testIsSupportedMimeType() { 806 try { 807 ExifInterface.isSupportedMimeType(null); 808 fail(); 809 } catch (NullPointerException e) { 810 // expected 811 } 812 assertTrue(ExifInterface.isSupportedMimeType("image/jpeg")); 813 assertTrue(ExifInterface.isSupportedMimeType("image/x-adobe-dng")); 814 assertTrue(ExifInterface.isSupportedMimeType("image/x-canon-cr2")); 815 assertTrue(ExifInterface.isSupportedMimeType("image/x-nikon-nef")); 816 assertTrue(ExifInterface.isSupportedMimeType("image/x-nikon-nrw")); 817 assertTrue(ExifInterface.isSupportedMimeType("image/x-sony-arw")); 818 assertTrue(ExifInterface.isSupportedMimeType("image/x-panasonic-rw2")); 819 assertTrue(ExifInterface.isSupportedMimeType("image/x-olympus-orf")); 820 assertTrue(ExifInterface.isSupportedMimeType("image/x-pentax-pef")); 821 assertTrue(ExifInterface.isSupportedMimeType("image/x-samsung-srw")); 822 assertTrue(ExifInterface.isSupportedMimeType("image/x-fuji-raf")); 823 assertTrue(ExifInterface.isSupportedMimeType("image/heic")); 824 assertTrue(ExifInterface.isSupportedMimeType("image/heif")); 825 assertTrue(ExifInterface.isSupportedMimeType("image/png")); 826 assertTrue(ExifInterface.isSupportedMimeType("image/webp")); 827 assertFalse(ExifInterface.isSupportedMimeType("image/gif")); 828 } 829 830 @Test testSetAttribute()831 public void testSetAttribute() throws Throwable { 832 Preconditions.assertTestFileExists(mInpPrefix + JPEG_WITH_EXIF_BYTE_ORDER_MM); 833 File srcFile = new File(mInpPrefix, JPEG_WITH_EXIF_BYTE_ORDER_MM); 834 File imageFile = clone(srcFile); 835 836 ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath()); 837 try { 838 exif.setAttribute(null, null); 839 fail(); 840 } catch (NullPointerException e) { 841 // expected 842 } 843 844 // Test setting tag to null 845 assertNotNull(exif.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP)); 846 exif.setAttribute(ExifInterface.TAG_GPS_TIMESTAMP, null); 847 assertNull(exif.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP)); 848 849 // Test tags that are converted to rational values for compatibility: 850 // 1. GpsTimeStamp tag will be converted to rational in setAttribute and converted back to 851 // timestamp format in getAttribute. 852 String validGpsTimeStamp = "11:11:11"; 853 exif.setAttribute(ExifInterface.TAG_GPS_TIMESTAMP, validGpsTimeStamp); 854 assertEquals(validGpsTimeStamp, exif.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP)); 855 // Check that invalid format is not set 856 String invalidGpsTimeStamp = "11:11:11:11"; 857 exif.setAttribute(ExifInterface.TAG_GPS_TIMESTAMP, invalidGpsTimeStamp); 858 assertEquals(validGpsTimeStamp, exif.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP)); 859 860 // 2. FNumber tag will be converted to rational in setAttribute and converted back to 861 // double value in getAttribute 862 String validFNumber = "2.4"; 863 exif.setAttribute(ExifInterface.TAG_F_NUMBER, validFNumber); 864 assertEquals(validFNumber, exif.getAttribute(ExifInterface.TAG_F_NUMBER)); 865 // Check that invalid format is not set 866 String invalidFNumber = "invalid format"; 867 exif.setAttribute(ExifInterface.TAG_F_NUMBER, invalidFNumber); 868 assertEquals(validFNumber, exif.getAttribute(ExifInterface.TAG_F_NUMBER)); 869 870 // Test writing different types of formats: 871 // 1. Byte format tag 872 String gpsVersionId = "2.3.0.0"; 873 exif.setAttribute(ExifInterface.TAG_GPS_VERSION_ID, gpsVersionId); 874 byte[] setGpsVersionIdBytes = 875 exif.getAttribute(ExifInterface.TAG_GPS_VERSION_ID).getBytes(); 876 for (int i = 0; i < setGpsVersionIdBytes.length; i++) { 877 assertEquals(gpsVersionId.getBytes()[i], setGpsVersionIdBytes[i]); 878 } 879 // Test TAG_GPS_ALTITUDE_REF, which is an exceptional case since the only valid values are 880 // "0" and "1". 881 String gpsAltitudeRef = "1"; 882 exif.setAttribute(ExifInterface.TAG_GPS_ALTITUDE_REF, gpsAltitudeRef); 883 assertEquals(gpsAltitudeRef.getBytes()[0], 884 exif.getAttribute(ExifInterface.TAG_GPS_ALTITUDE_REF).getBytes()[0]); 885 886 // 2. String format tag 887 String makeValue = "MakeTest"; 888 exif.setAttribute(ExifInterface.TAG_MAKE, makeValue); 889 assertEquals(makeValue, exif.getAttribute(ExifInterface.TAG_MAKE)); 890 // Check that the following values are not parsed as rational values 891 String makeValueWithOneSlash = "Make/Test"; 892 exif.setAttribute(ExifInterface.TAG_MAKE, makeValueWithOneSlash); 893 assertEquals(makeValueWithOneSlash, exif.getAttribute(ExifInterface.TAG_MAKE)); 894 String makeValueWithTwoSlashes = "Make/Test/Test"; 895 exif.setAttribute(ExifInterface.TAG_MAKE, makeValueWithTwoSlashes); 896 assertEquals(makeValueWithTwoSlashes, exif.getAttribute(ExifInterface.TAG_MAKE)); 897 // When a value has a comma, it should be parsed as a string if any of the values before or 898 // after the comma is a string. 899 int defaultValue = -1; 900 String makeValueWithCommaType1 = "Make,2"; 901 exif.setAttribute(ExifInterface.TAG_MAKE, makeValueWithCommaType1); 902 assertEquals(makeValueWithCommaType1, exif.getAttribute(ExifInterface.TAG_MAKE)); 903 // Make sure that it's not stored as an integer value. 904 assertEquals(defaultValue, exif.getAttributeInt(ExifInterface.TAG_MAKE, defaultValue)); 905 String makeValueWithCommaType2 = "2,Make"; 906 exif.setAttribute(ExifInterface.TAG_MAKE, makeValueWithCommaType2); 907 assertEquals(makeValueWithCommaType2, exif.getAttribute(ExifInterface.TAG_MAKE)); 908 // Make sure that it's not stored as an integer value. 909 assertEquals(defaultValue, exif.getAttributeInt(ExifInterface.TAG_MAKE, defaultValue)); 910 911 // 3. Unsigned short format tag 912 String isoSpeedRatings = "800"; 913 exif.setAttribute(ExifInterface.TAG_ISO_SPEED_RATINGS, isoSpeedRatings); 914 assertEquals(isoSpeedRatings, exif.getAttribute(ExifInterface.TAG_ISO_SPEED_RATINGS)); 915 // When a value has multiple components, all of them should be of the format that the tag 916 // supports. Thus, the following values (SHORT,LONG) should not be set since TAG_COMPRESSION 917 // only allows short values. 918 assertNull(exif.getAttribute(ExifInterface.TAG_COMPRESSION)); 919 String invalidMultipleComponentsValueType1 = "1,65536"; 920 exif.setAttribute(ExifInterface.TAG_COMPRESSION, invalidMultipleComponentsValueType1); 921 assertNull(exif.getAttribute(ExifInterface.TAG_COMPRESSION)); 922 String invalidMultipleComponentsValueType2 = "65536,1"; 923 exif.setAttribute(ExifInterface.TAG_COMPRESSION, invalidMultipleComponentsValueType2); 924 assertNull(exif.getAttribute(ExifInterface.TAG_COMPRESSION)); 925 926 // 4. Unsigned long format tag 927 String validImageWidthValue = "65536"; // max unsigned short value + 1 928 exif.setAttribute(ExifInterface.TAG_IMAGE_WIDTH, validImageWidthValue); 929 assertEquals(validImageWidthValue, exif.getAttribute(ExifInterface.TAG_IMAGE_WIDTH)); 930 String invalidImageWidthValue = "-65536"; 931 exif.setAttribute(ExifInterface.TAG_IMAGE_WIDTH, invalidImageWidthValue); 932 assertEquals(validImageWidthValue, exif.getAttribute(ExifInterface.TAG_IMAGE_WIDTH)); 933 934 // 5. Unsigned rational format tag 935 String exposureTime = "1/8"; 936 exif.setAttribute(ExifInterface.TAG_APERTURE_VALUE, exposureTime); 937 assertEquals(exposureTime, exif.getAttribute(ExifInterface.TAG_APERTURE_VALUE)); 938 939 // 6. Signed rational format tag 940 String brightnessValue = "-220/100"; 941 exif.setAttribute(ExifInterface.TAG_BRIGHTNESS_VALUE, brightnessValue); 942 assertEquals(brightnessValue, exif.getAttribute(ExifInterface.TAG_BRIGHTNESS_VALUE)); 943 944 // 7. Undefined format tag 945 String userComment = "UserCommentTest"; 946 exif.setAttribute(ExifInterface.TAG_USER_COMMENT, userComment); 947 assertEquals(userComment, exif.getAttribute(ExifInterface.TAG_USER_COMMENT)); 948 949 imageFile.delete(); 950 } 951 952 @Test testGetAttributeForNullAndNonExistentTag()953 public void testGetAttributeForNullAndNonExistentTag() throws Throwable { 954 // JPEG_WITH_EXIF_BYTE_ORDER_MM does not have a value for TAG_SUBJECT_AREA tag. 955 Preconditions.assertTestFileExists(mInpPrefix + JPEG_WITH_EXIF_BYTE_ORDER_MM); 956 File srcFile = new File(mInpPrefix, JPEG_WITH_EXIF_BYTE_ORDER_MM); 957 File imageFile = clone(srcFile); 958 959 ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath()); 960 try { 961 exif.getAttribute(null); 962 fail(); 963 } catch (NullPointerException e) { 964 // expected 965 } 966 assertNull(exif.getAttribute(TAG_SUBJECT_AREA)); 967 968 int defaultValue = -1; 969 try { 970 exif.getAttributeInt(null, defaultValue); 971 fail(); 972 } catch (NullPointerException e) { 973 // expected 974 } 975 assertEquals(defaultValue, exif.getAttributeInt(TAG_SUBJECT_AREA, defaultValue)); 976 977 try { 978 exif.getAttributeDouble(null, defaultValue); 979 fail(); 980 } catch (NullPointerException e) { 981 // expected 982 } 983 assertEquals(defaultValue, exif.getAttributeInt(TAG_SUBJECT_AREA, defaultValue)); 984 985 try { 986 exif.getAttributeBytes(null); 987 fail(); 988 } catch (NullPointerException e) { 989 // expected 990 } 991 assertNull(exif.getAttributeBytes(TAG_SUBJECT_AREA)); 992 } 993 clone(File original)994 private static File clone(File original) throws IOException { 995 final File cloned = 996 File.createTempFile("cts_", +System.nanoTime() + "_" + original.getName()); 997 FileUtils.copyFileOrThrow(original, cloned); 998 return cloned; 999 } 1000 readShort(InputStream is)1001 private short readShort(InputStream is) throws IOException { 1002 int ch1 = is.read(); 1003 int ch2 = is.read(); 1004 if ((ch1 | ch2) < 0) { 1005 throw new EOFException(); 1006 } 1007 return (short) ((ch1 << 8) + (ch2)); 1008 } 1009 1010 /** 1011 * Asserts that {@code expectedImageFile} and {@code actualImageFile} can be decoded by 1012 * {@link BitmapFactory} and the results have the same width, height and MIME type. 1013 * 1014 * <p>This does not check the image itself for similarity/equality. 1015 */ assertBitmapsEquivalent(File expectedImageFile, File actualImageFile)1016 private void assertBitmapsEquivalent(File expectedImageFile, File actualImageFile) { 1017 BitmapFactory.Options expectedOptions = new BitmapFactory.Options(); 1018 Bitmap expectedBitmap = Objects.requireNonNull( 1019 decodeBitmap(expectedImageFile, expectedOptions)); 1020 BitmapFactory.Options actualOptions = new BitmapFactory.Options(); 1021 Bitmap actualBitmap = Objects.requireNonNull(decodeBitmap(actualImageFile, actualOptions)); 1022 1023 assertEquals(expectedOptions.outWidth, actualOptions.outWidth); 1024 assertEquals(expectedOptions.outHeight, actualOptions.outHeight); 1025 assertEquals(expectedOptions.outMimeType, actualOptions.outMimeType); 1026 assertEquals(expectedBitmap.getWidth(), actualBitmap.getWidth()); 1027 assertEquals(expectedBitmap.getHeight(), actualBitmap.getHeight()); 1028 } 1029 1030 /** 1031 * Equivalent to {@link BitmapFactory#decodeFile(String, BitmapFactory.Options)} but uses a 1032 * {@link BufferedInputStream} to avoid violating 1033 * {@link StrictMode.ThreadPolicy.Builder#detectUnbufferedIo()}. 1034 */ decodeBitmap(File file, BitmapFactory.Options options)1035 private static Bitmap decodeBitmap(File file, BitmapFactory.Options options) { 1036 try (BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(file))) { 1037 return BitmapFactory.decodeStream(inputStream, /* outPadding= */ null, options); 1038 } catch (IOException e) { 1039 return null; 1040 } 1041 } 1042 } 1043