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 android.content.res.TypedArray; 20 import android.graphics.Bitmap; 21 import android.graphics.BitmapFactory; 22 import android.media.ExifInterface; 23 import android.os.Environment; 24 import android.test.AndroidTestCase; 25 import android.util.Log; 26 import android.system.ErrnoException; 27 import android.system.Os; 28 import android.system.OsConstants; 29 30 import java.io.BufferedInputStream; 31 import java.io.File; 32 import java.io.FileDescriptor; 33 import java.io.FileInputStream; 34 import java.io.FileOutputStream; 35 import java.io.InputStream; 36 import java.io.IOException; 37 import java.lang.reflect.Type; 38 39 import libcore.io.IoUtils; 40 import libcore.io.Streams; 41 42 public class ExifInterfaceTest extends AndroidTestCase { 43 private static final String TAG = ExifInterface.class.getSimpleName(); 44 private static final boolean VERBOSE = false; // lots of logging 45 46 private static final double DIFFERENCE_TOLERANCE = .001; 47 48 // List of files. 49 private static final String EXIF_BYTE_ORDER_II_JPEG = "image_exif_byte_order_ii.jpg"; 50 private static final String EXIF_BYTE_ORDER_MM_JPEG = "image_exif_byte_order_mm.jpg"; 51 private static final String LG_G4_ISO_800_DNG = "lg_g4_iso_800.dng"; 52 private static final int[] IMAGE_RESOURCES = new int[] { 53 R.raw.image_exif_byte_order_ii, R.raw.image_exif_byte_order_mm, R.raw.lg_g4_iso_800 }; 54 private static final String[] IMAGE_FILENAMES = new String[] { 55 EXIF_BYTE_ORDER_II_JPEG, EXIF_BYTE_ORDER_MM_JPEG, LG_G4_ISO_800_DNG }; 56 57 private static final String[] EXIF_TAGS = { 58 ExifInterface.TAG_MAKE, 59 ExifInterface.TAG_MODEL, 60 ExifInterface.TAG_F_NUMBER, 61 ExifInterface.TAG_DATETIME, 62 ExifInterface.TAG_EXPOSURE_TIME, 63 ExifInterface.TAG_FLASH, 64 ExifInterface.TAG_FOCAL_LENGTH, 65 ExifInterface.TAG_GPS_ALTITUDE, 66 ExifInterface.TAG_GPS_ALTITUDE_REF, 67 ExifInterface.TAG_GPS_DATESTAMP, 68 ExifInterface.TAG_GPS_LATITUDE, 69 ExifInterface.TAG_GPS_LATITUDE_REF, 70 ExifInterface.TAG_GPS_LONGITUDE, 71 ExifInterface.TAG_GPS_LONGITUDE_REF, 72 ExifInterface.TAG_GPS_PROCESSING_METHOD, 73 ExifInterface.TAG_GPS_TIMESTAMP, 74 ExifInterface.TAG_IMAGE_LENGTH, 75 ExifInterface.TAG_IMAGE_WIDTH, 76 ExifInterface.TAG_ISO_SPEED_RATINGS, 77 ExifInterface.TAG_ORIENTATION, 78 ExifInterface.TAG_WHITE_BALANCE 79 }; 80 81 private static class ExpectedValue { 82 // Thumbnail information. 83 public final boolean hasThumbnail; 84 public final int thumbnailWidth; 85 public final int thumbnailHeight; 86 87 // GPS information. 88 public final boolean hasLatLong; 89 public final float latitude; 90 public final float longitude; 91 public final float altitude; 92 93 // Values. 94 public final String make; 95 public final String model; 96 public final float aperture; 97 public final String datetime; 98 public final float exposureTime; 99 public final float flash; 100 public final String focalLength; 101 public final String gpsAltitude; 102 public final String gpsAltitudeRef; 103 public final String gpsDatestamp; 104 public final String gpsLatitude; 105 public final String gpsLatitudeRef; 106 public final String gpsLongitude; 107 public final String gpsLongitudeRef; 108 public final String gpsProcessingMethod; 109 public final String gpsTimestamp; 110 public final int imageLength; 111 public final int imageWidth; 112 public final String iso; 113 public final int orientation; 114 public final int whiteBalance; 115 getString(TypedArray typedArray, int index)116 private static String getString(TypedArray typedArray, int index) { 117 String stringValue = typedArray.getString(index); 118 if (stringValue == null || stringValue.equals("")) { 119 return null; 120 } 121 return stringValue.trim(); 122 } 123 ExpectedValue(TypedArray typedArray)124 public ExpectedValue(TypedArray typedArray) { 125 // Reads thumbnail information. 126 hasThumbnail = typedArray.getBoolean(0, false); 127 thumbnailWidth = typedArray.getInt(1, 0); 128 thumbnailHeight = typedArray.getInt(2, 0); 129 130 // Reads GPS information. 131 hasLatLong = typedArray.getBoolean(3, false); 132 latitude = typedArray.getFloat(4, 0f); 133 longitude = typedArray.getFloat(5, 0f); 134 altitude = typedArray.getFloat(6, 0f); 135 136 // Reads values. 137 make = getString(typedArray, 7); 138 model = getString(typedArray, 8); 139 aperture = typedArray.getFloat(9, 0f); 140 datetime = getString(typedArray, 10); 141 exposureTime = typedArray.getFloat(11, 0f); 142 flash = typedArray.getFloat(12, 0f); 143 focalLength = getString(typedArray, 13); 144 gpsAltitude = getString(typedArray, 14); 145 gpsAltitudeRef = getString(typedArray, 15); 146 gpsDatestamp = getString(typedArray, 16); 147 gpsLatitude = getString(typedArray, 17); 148 gpsLatitudeRef = getString(typedArray, 18); 149 gpsLongitude = getString(typedArray, 19); 150 gpsLongitudeRef = getString(typedArray, 20); 151 gpsProcessingMethod = getString(typedArray, 21); 152 gpsTimestamp = getString(typedArray, 22); 153 imageLength = typedArray.getInt(23, 0); 154 imageWidth = typedArray.getInt(24, 0); 155 iso = getString(typedArray, 25); 156 orientation = typedArray.getInt(26, 0); 157 whiteBalance = typedArray.getInt(27, 0); 158 159 typedArray.recycle(); 160 } 161 } 162 163 @Override setUp()164 protected void setUp() throws Exception { 165 for (int i = 0; i < IMAGE_RESOURCES.length; ++i) { 166 String outputPath = new File(Environment.getExternalStorageDirectory(), 167 IMAGE_FILENAMES[i]).getAbsolutePath(); 168 try (InputStream inputStream = getContext().getResources().openRawResource( 169 IMAGE_RESOURCES[i])) { 170 try (FileOutputStream outputStream = new FileOutputStream(outputPath)) { 171 Streams.copy(inputStream, outputStream); 172 } 173 } 174 } 175 super.setUp(); 176 } 177 178 @Override tearDown()179 protected void tearDown() throws Exception { 180 for (int i = 0; i < IMAGE_RESOURCES.length; ++i) { 181 String imageFilePath = new File(Environment.getExternalStorageDirectory(), 182 IMAGE_FILENAMES[i]).getAbsolutePath(); 183 File imageFile = new File(imageFilePath); 184 if (imageFile.exists()) { 185 imageFile.delete(); 186 } 187 } 188 189 super.tearDown(); 190 } 191 testReadExifDataFromExifByteOrderIIJpeg()192 public void testReadExifDataFromExifByteOrderIIJpeg() throws Throwable { 193 testExifInterfaceForJpeg(EXIF_BYTE_ORDER_II_JPEG, R.array.exifbyteorderii_jpg); 194 } 195 testReadExifDataFromExifByteOrderMMJpeg()196 public void testReadExifDataFromExifByteOrderMMJpeg() throws Throwable { 197 testExifInterfaceForJpeg(EXIF_BYTE_ORDER_MM_JPEG, R.array.exifbyteordermm_jpg); 198 } 199 testReadExifDataFromLgG4Iso800Dng()200 public void testReadExifDataFromLgG4Iso800Dng() throws Throwable { 201 testExifInterfaceForRaw(LG_G4_ISO_800_DNG, R.array.lg_g4_iso_800_dng); 202 } 203 printExifTagsAndValues(String fileName, ExifInterface exifInterface)204 private void printExifTagsAndValues(String fileName, ExifInterface exifInterface) { 205 // Prints thumbnail information. 206 if (exifInterface.hasThumbnail()) { 207 byte[] thumbnailBytes = exifInterface.getThumbnail(); 208 if (thumbnailBytes != null) { 209 Log.v(TAG, fileName + " Thumbnail size = " + thumbnailBytes.length); 210 Bitmap bitmap = BitmapFactory.decodeByteArray( 211 thumbnailBytes, 0, thumbnailBytes.length); 212 if (bitmap == null) { 213 Log.e(TAG, fileName + " Corrupted thumbnail!"); 214 } else { 215 Log.v(TAG, fileName + " Thumbnail size: " + bitmap.getWidth() + ", " 216 + bitmap.getHeight()); 217 } 218 } else { 219 Log.e(TAG, fileName + " Unexpected result: No thumbnails were found. " 220 + "A thumbnail is expected."); 221 } 222 } else { 223 if (exifInterface.getThumbnail() != null) { 224 Log.e(TAG, fileName + " Unexpected result: A thumbnail was found. " 225 + "No thumbnail is expected."); 226 } else { 227 Log.v(TAG, fileName + " No thumbnail"); 228 } 229 } 230 231 // Prints GPS information. 232 Log.v(TAG, fileName + " Altitude = " + exifInterface.getAltitude(.0)); 233 234 float[] latLong = new float[2]; 235 if (exifInterface.getLatLong(latLong)) { 236 Log.v(TAG, fileName + " Latitude = " + latLong[0]); 237 Log.v(TAG, fileName + " Longitude = " + latLong[1]); 238 } else { 239 Log.v(TAG, fileName + " No latlong data"); 240 } 241 242 // Prints values. 243 for (String tagKey : EXIF_TAGS) { 244 String tagValue = exifInterface.getAttribute(tagKey); 245 Log.v(TAG, fileName + " Key{" + tagKey + "} = '" + tagValue + "'"); 246 } 247 } 248 assertIntTag(ExifInterface exifInterface, String tag, int expectedValue)249 private void assertIntTag(ExifInterface exifInterface, String tag, int expectedValue) { 250 int intValue = exifInterface.getAttributeInt(tag, 0); 251 assertEquals(expectedValue, intValue); 252 } 253 assertFloatTag(ExifInterface exifInterface, String tag, float expectedValue)254 private void assertFloatTag(ExifInterface exifInterface, String tag, float expectedValue) { 255 double doubleValue = exifInterface.getAttributeDouble(tag, 0.0); 256 assertEquals(expectedValue, doubleValue, DIFFERENCE_TOLERANCE); 257 } 258 assertStringTag(ExifInterface exifInterface, String tag, String expectedValue)259 private void assertStringTag(ExifInterface exifInterface, String tag, String expectedValue) { 260 String stringValue = exifInterface.getAttribute(tag); 261 if (stringValue != null) { 262 stringValue = stringValue.trim(); 263 } 264 assertEquals(expectedValue, stringValue); 265 } 266 compareWithExpectedValue(ExifInterface exifInterface, ExpectedValue expectedValue, String verboseTag)267 private void compareWithExpectedValue(ExifInterface exifInterface, 268 ExpectedValue expectedValue, String verboseTag) { 269 if (VERBOSE) { 270 printExifTagsAndValues(verboseTag, exifInterface); 271 } 272 // Checks a thumbnail image. 273 assertEquals(expectedValue.hasThumbnail, exifInterface.hasThumbnail()); 274 if (expectedValue.hasThumbnail) { 275 byte[] thumbnailBytes = exifInterface.getThumbnail(); 276 assertNotNull(thumbnailBytes); 277 Bitmap thumbnailBitmap = 278 BitmapFactory.decodeByteArray(thumbnailBytes, 0, thumbnailBytes.length); 279 assertNotNull(thumbnailBitmap); 280 assertEquals(expectedValue.thumbnailWidth, thumbnailBitmap.getWidth()); 281 assertEquals(expectedValue.thumbnailHeight, thumbnailBitmap.getHeight()); 282 } else { 283 assertNull(exifInterface.getThumbnail()); 284 } 285 286 // Checks GPS information. 287 float[] latLong = new float[2]; 288 assertEquals(expectedValue.hasLatLong, exifInterface.getLatLong(latLong)); 289 if (expectedValue.hasLatLong) { 290 assertEquals(expectedValue.latitude, latLong[0], DIFFERENCE_TOLERANCE); 291 assertEquals(expectedValue.longitude, latLong[1], DIFFERENCE_TOLERANCE); 292 } 293 assertEquals(expectedValue.altitude, exifInterface.getAltitude(.0), DIFFERENCE_TOLERANCE); 294 295 // Checks values. 296 assertStringTag(exifInterface, ExifInterface.TAG_MAKE, expectedValue.make); 297 assertStringTag(exifInterface, ExifInterface.TAG_MODEL, expectedValue.model); 298 assertFloatTag(exifInterface, ExifInterface.TAG_F_NUMBER, expectedValue.aperture); 299 assertStringTag(exifInterface, ExifInterface.TAG_DATETIME, expectedValue.datetime); 300 assertFloatTag(exifInterface, ExifInterface.TAG_EXPOSURE_TIME, expectedValue.exposureTime); 301 assertFloatTag(exifInterface, ExifInterface.TAG_FLASH, expectedValue.flash); 302 assertStringTag(exifInterface, ExifInterface.TAG_FOCAL_LENGTH, expectedValue.focalLength); 303 assertStringTag(exifInterface, ExifInterface.TAG_GPS_ALTITUDE, expectedValue.gpsAltitude); 304 assertStringTag(exifInterface, ExifInterface.TAG_GPS_ALTITUDE_REF, 305 expectedValue.gpsAltitudeRef); 306 assertStringTag(exifInterface, ExifInterface.TAG_GPS_DATESTAMP, expectedValue.gpsDatestamp); 307 assertStringTag(exifInterface, ExifInterface.TAG_GPS_LATITUDE, expectedValue.gpsLatitude); 308 assertStringTag(exifInterface, ExifInterface.TAG_GPS_LATITUDE_REF, 309 expectedValue.gpsLatitudeRef); 310 assertStringTag(exifInterface, ExifInterface.TAG_GPS_LONGITUDE, expectedValue.gpsLongitude); 311 assertStringTag(exifInterface, ExifInterface.TAG_GPS_LONGITUDE_REF, 312 expectedValue.gpsLongitudeRef); 313 assertStringTag(exifInterface, ExifInterface.TAG_GPS_PROCESSING_METHOD, 314 expectedValue.gpsProcessingMethod); 315 assertStringTag(exifInterface, ExifInterface.TAG_GPS_TIMESTAMP, expectedValue.gpsTimestamp); 316 assertIntTag(exifInterface, ExifInterface.TAG_IMAGE_LENGTH, expectedValue.imageLength); 317 assertIntTag(exifInterface, ExifInterface.TAG_IMAGE_WIDTH, expectedValue.imageWidth); 318 assertStringTag(exifInterface, ExifInterface.TAG_ISO_SPEED_RATINGS, expectedValue.iso); 319 assertIntTag(exifInterface, ExifInterface.TAG_ORIENTATION, expectedValue.orientation); 320 assertIntTag(exifInterface, ExifInterface.TAG_WHITE_BALANCE, expectedValue.whiteBalance); 321 } 322 testExifInterfaceCommon(File imageFile, ExpectedValue expectedValue)323 private void testExifInterfaceCommon(File imageFile, ExpectedValue expectedValue) 324 throws IOException { 325 String verboseTag = imageFile.getName(); 326 327 // Creates via path. 328 ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath()); 329 assertNotNull(exifInterface); 330 compareWithExpectedValue(exifInterface, expectedValue, verboseTag); 331 332 // Creates from an asset file. 333 InputStream in = null; 334 try { 335 in = getContext().getAssets().open(imageFile.getName()); 336 exifInterface = new ExifInterface(in); 337 compareWithExpectedValue(exifInterface, expectedValue, verboseTag); 338 } finally { 339 IoUtils.closeQuietly(in); 340 } 341 342 // Creates via InputStream. 343 in = null; 344 try { 345 in = new BufferedInputStream(new FileInputStream(imageFile.getAbsolutePath())); 346 exifInterface = new ExifInterface(in); 347 compareWithExpectedValue(exifInterface, expectedValue, verboseTag); 348 } finally { 349 IoUtils.closeQuietly(in); 350 } 351 352 // Creates via FileDescriptor. 353 FileDescriptor fd = null; 354 try { 355 fd = Os.open(imageFile.getAbsolutePath(), OsConstants.O_RDONLY, 0600); 356 exifInterface = new ExifInterface(fd); 357 compareWithExpectedValue(exifInterface, expectedValue, verboseTag); 358 } catch (ErrnoException e) { 359 throw e.rethrowAsIOException(); 360 } finally { 361 IoUtils.closeQuietly(fd); 362 } 363 } 364 testSaveAttributes_withFileName(File imageFile, ExpectedValue expectedValue)365 private void testSaveAttributes_withFileName(File imageFile, ExpectedValue expectedValue) 366 throws IOException { 367 String verboseTag = imageFile.getName(); 368 369 ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath()); 370 exifInterface.saveAttributes(); 371 exifInterface = new ExifInterface(imageFile.getAbsolutePath()); 372 compareWithExpectedValue(exifInterface, expectedValue, verboseTag); 373 374 // Test for modifying one attribute. 375 String backupValue = exifInterface.getAttribute(ExifInterface.TAG_MAKE); 376 exifInterface.setAttribute(ExifInterface.TAG_MAKE, "abc"); 377 exifInterface.saveAttributes(); 378 exifInterface = new ExifInterface(imageFile.getAbsolutePath()); 379 assertEquals("abc", exifInterface.getAttribute(ExifInterface.TAG_MAKE)); 380 // Restore the backup value. 381 exifInterface.setAttribute(ExifInterface.TAG_MAKE, backupValue); 382 exifInterface.saveAttributes(); 383 exifInterface = new ExifInterface(imageFile.getAbsolutePath()); 384 compareWithExpectedValue(exifInterface, expectedValue, verboseTag); 385 } 386 testSaveAttributes_withFileDescriptor(File imageFile, ExpectedValue expectedValue)387 private void testSaveAttributes_withFileDescriptor(File imageFile, ExpectedValue expectedValue) 388 throws IOException { 389 String verboseTag = imageFile.getName(); 390 391 FileDescriptor fd = null; 392 try { 393 fd = Os.open(imageFile.getAbsolutePath(), OsConstants.O_RDWR, 0600); 394 ExifInterface exifInterface = new ExifInterface(fd); 395 exifInterface.saveAttributes(); 396 Os.lseek(fd, 0, OsConstants.SEEK_SET); 397 exifInterface = new ExifInterface(fd); 398 compareWithExpectedValue(exifInterface, expectedValue, verboseTag); 399 400 // Test for modifying one attribute. 401 String backupValue = exifInterface.getAttribute(ExifInterface.TAG_MAKE); 402 exifInterface.setAttribute(ExifInterface.TAG_MAKE, "abc"); 403 exifInterface.saveAttributes(); 404 Os.lseek(fd, 0, OsConstants.SEEK_SET); 405 exifInterface = new ExifInterface(fd); 406 assertEquals("abc", exifInterface.getAttribute(ExifInterface.TAG_MAKE)); 407 // Restore the backup value. 408 exifInterface.setAttribute(ExifInterface.TAG_MAKE, backupValue); 409 exifInterface.saveAttributes(); 410 Os.lseek(fd, 0, OsConstants.SEEK_SET); 411 exifInterface = new ExifInterface(fd); 412 compareWithExpectedValue(exifInterface, expectedValue, verboseTag); 413 } catch (ErrnoException e) { 414 throw e.rethrowAsIOException(); 415 } finally { 416 IoUtils.closeQuietly(fd); 417 } 418 } 419 testSaveAttributes_withInputStream(File imageFile, ExpectedValue expectedValue)420 private void testSaveAttributes_withInputStream(File imageFile, ExpectedValue expectedValue) 421 throws IOException { 422 InputStream in = null; 423 try { 424 in = getContext().getAssets().open(imageFile.getName()); 425 ExifInterface exifInterface = new ExifInterface(in); 426 exifInterface.saveAttributes(); 427 } catch (UnsupportedOperationException e) { 428 // Expected. saveAttributes is not supported with an ExifInterface object which was 429 // created with InputStream. 430 return; 431 } finally { 432 IoUtils.closeQuietly(in); 433 } 434 fail("Should not reach here!"); 435 } 436 testExifInterfaceForJpeg(String fileName, int typedArrayResourceId)437 private void testExifInterfaceForJpeg(String fileName, int typedArrayResourceId) 438 throws IOException { 439 ExpectedValue expectedValue = new ExpectedValue( 440 getContext().getResources().obtainTypedArray(typedArrayResourceId)); 441 File imageFile = new File(Environment.getExternalStorageDirectory(), fileName); 442 443 // Test for reading from various inputs. 444 testExifInterfaceCommon(imageFile, expectedValue); 445 446 // Test for saving attributes. 447 testSaveAttributes_withFileName(imageFile, expectedValue); 448 testSaveAttributes_withFileDescriptor(imageFile, expectedValue); 449 testSaveAttributes_withInputStream(imageFile, expectedValue); 450 } 451 testExifInterfaceForRaw(String fileName, int typedArrayResourceId)452 private void testExifInterfaceForRaw(String fileName, int typedArrayResourceId) 453 throws IOException { 454 ExpectedValue expectedValue = new ExpectedValue( 455 getContext().getResources().obtainTypedArray(typedArrayResourceId)); 456 File imageFile = new File(Environment.getExternalStorageDirectory(), fileName); 457 458 // Test for reading from various inputs. 459 testExifInterfaceCommon(imageFile, expectedValue); 460 461 // Since ExifInterface does not support for saving attributes for RAW files, do not test 462 // about writing back in here. 463 } 464 } 465