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