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