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