aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-10-10 18:21:11 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2022-10-10 18:21:11 +0000
commit3c53934294e678ce8dddf8fd4de53248e221c841 (patch)
treeded9bad01807d883c6adc9db9d75fe325e9ec5a0
parent519a3332e6c8fdede8c97258f204198f32700112 (diff)
parentcfa07d99d34b6426a82be93b2834c5120ee4154d (diff)
downloadsupport-sparse-9158283-L16600000958391730.tar.gz
Merge "Merge cherrypicks of [2234224, 2234225, 2245634] into androidx-exifinterface-release." into androidx-exifinterface-releasesparse-9158283-L16600000958391730
-rw-r--r--buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt2
-rw-r--r--exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java79
-rw-r--r--exifinterface/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java15
3 files changed, 88 insertions, 8 deletions
diff --git a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
index 3e33133bb22..88828ffc52a 100644
--- a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
@@ -59,7 +59,7 @@ object LibraryVersions {
val DYNAMICANIMATION_KTX = Version("1.0.0-alpha04")
val EMOJI = Version("1.2.0-alpha01")
val ENTERPRISE = Version("1.1.0-alpha02")
- val EXIFINTERFACE = Version("1.3.4")
+ val EXIFINTERFACE = Version("1.3.5")
val FRAGMENT = Version("1.3.0-alpha08")
val FUTURES = Version("1.2.0-alpha01")
val GRIDLAYOUT = Version("1.1.0-alpha01")
diff --git a/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java b/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java
index 3a0edbb055b..2b0a5e51390 100644
--- a/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java
+++ b/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java
@@ -1240,15 +1240,19 @@ public class ExifInterfaceTest {
ExpectedValue expectedValue = new ExpectedValue(
getApplicationContext().getResources().obtainTypedArray(typedArrayResourceId));
- File imageFile = getFileFromExternalDir(fileName);
+ File srcFile = getFileFromExternalDir(fileName);
+ File imageFile = clone(srcFile);
String verboseTag = imageFile.getName();
ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath());
exifInterface.saveAttributes();
exifInterface = new ExifInterface(imageFile.getAbsolutePath());
compareWithExpectedValue(exifInterface, expectedValue, verboseTag, false);
+ assertBitmapsEquivalent(srcFile, imageFile);
+ assertSecondSaveProducesSameSizeFile(imageFile);
// Test for modifying one attribute.
+ exifInterface = new ExifInterface(imageFile.getAbsolutePath());
String backupValue = exifInterface.getAttribute(ExifInterface.TAG_MAKE);
exifInterface.setAttribute(ExifInterface.TAG_MAKE, "abc");
exifInterface.saveAttributes();
@@ -1256,7 +1260,6 @@ public class ExifInterfaceTest {
if (expectedValue.hasThumbnail) {
testThumbnail(expectedValue, exifInterface);
}
- exifInterface = new ExifInterface(imageFile.getAbsolutePath());
assertEquals("abc", exifInterface.getAttribute(ExifInterface.TAG_MAKE));
// Check if thumbnail bytes can be retrieved from the new thumbnail range.
if (expectedValue.hasThumbnail) {
@@ -1300,15 +1303,19 @@ public class ExifInterfaceTest {
}
private void writeToFilesWithoutExif(String fileName) throws IOException {
- File imageFile = getFileFromExternalDir(fileName);
+ File srcFile = getFileFromExternalDir(fileName);
+ File imageFile = clone(srcFile);
ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath());
exifInterface.setAttribute(ExifInterface.TAG_MAKE, "abc");
exifInterface.saveAttributes();
+ assertBitmapsEquivalent(srcFile, imageFile);
exifInterface = new ExifInterface(imageFile.getAbsolutePath());
String make = exifInterface.getAttribute(ExifInterface.TAG_MAKE);
assertEquals("abc", make);
+
+ assertSecondSaveProducesSameSizeFile(imageFile);
}
private void testThumbnail(ExpectedValue expectedValue, ExifInterface exifInterface) {
@@ -1405,4 +1412,70 @@ public class ExifInterfaceTest {
return new File(getApplicationContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES),
fileName);
}
+
+ /**
+ * Asserts that {@code expectedImageFile} and {@code actualImageFile} can be decoded by
+ * {@link BitmapFactory} and the results have the same width, height and MIME type.
+ *
+ * <p>This does not check the image itself for similarity/equality.
+ */
+ private void assertBitmapsEquivalent(File expectedImageFile, File actualImageFile) {
+ BitmapFactory.Options expectedOptions = new BitmapFactory.Options();
+ Bitmap expectedBitmap = decodeBitmap(expectedImageFile, expectedOptions);
+ BitmapFactory.Options actualOptions = new BitmapFactory.Options();
+ Bitmap actualBitmap = decodeBitmap(actualImageFile, actualOptions);
+
+ assertEquals(expectedOptions.outWidth, actualOptions.outWidth);
+ assertEquals(expectedOptions.outHeight, actualOptions.outHeight);
+ assertEquals(expectedOptions.outMimeType, actualOptions.outMimeType);
+ assertEquals(expectedBitmap.getWidth(), actualBitmap.getWidth());
+ assertEquals(expectedBitmap.getHeight(), actualBitmap.getHeight());
+ }
+
+ /**
+ * Equivalent to {@link BitmapFactory#decodeFile(String, BitmapFactory.Options)} but uses a
+ * {@link BufferedInputStream} to avoid violating
+ * {@link StrictMode.ThreadPolicy.Builder#detectUnbufferedIo()}.
+ */
+ private static Bitmap decodeBitmap(File file, BitmapFactory.Options options) {
+ try (BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(file))) {
+ return BitmapFactory.decodeStream(inputStream, /* outPadding= */ null, options);
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Asserts that saving the file the second time (without modifying any attributes) produces
+ * exactly the same length file as the first save. The first save (with no modifications) is
+ * expected to (possibly) change the file length because {@link ExifInterface} may move/reformat
+ * the Exif block within the file, but the second save should not make further modifications.
+ */
+ private void assertSecondSaveProducesSameSizeFile(File imageFileAfterOneSave)
+ throws IOException {
+ File imageFileAfterTwoSaves = clone(imageFileAfterOneSave);
+ ExifInterface exifInterface = new ExifInterface(imageFileAfterTwoSaves.getAbsolutePath());
+ exifInterface.saveAttributes();
+ if (imageFileAfterOneSave.getAbsolutePath().endsWith(".png")
+ || imageFileAfterOneSave.getAbsolutePath().endsWith(".webp")) {
+ // PNG and (some) WebP files are (surprisingly) modified between the first and second
+ // save (b/249097443), so we check the difference between second and third save instead.
+ File imageFileAfterThreeSaves = clone(imageFileAfterTwoSaves);
+ exifInterface = new ExifInterface(imageFileAfterThreeSaves.getAbsolutePath());
+ exifInterface.saveAttributes();
+ assertEquals(imageFileAfterTwoSaves.length(), imageFileAfterThreeSaves.length());
+ } else {
+ assertEquals(imageFileAfterOneSave.length(), imageFileAfterTwoSaves.length());
+ }
+ }
+
+ private File clone(File original) throws IOException {
+ File cloned =
+ File.createTempFile("tmp_", System.nanoTime() + "_" + original.getName());
+ try (FileInputStream inputStream = new FileInputStream(original);
+ FileOutputStream outputStream = new FileOutputStream(cloned)) {
+ copy(inputStream, outputStream);
+ }
+ return cloned;
+ }
}
diff --git a/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java b/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java
index 22c4c286460..507066850be 100644
--- a/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java
+++ b/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java
@@ -6518,6 +6518,11 @@ public class ExifInterface {
// Skip input stream to the end of the EXIF chunk
totalInputStream.skipFully(WEBP_CHUNK_TYPE_BYTE_LENGTH);
int exifChunkLength = totalInputStream.readInt();
+ // RIFF chunks have a single padding byte at the end if the declared chunk size is
+ // odd.
+ if (exifChunkLength % 2 != 0) {
+ exifChunkLength++;
+ }
totalInputStream.skipFully(exifChunkLength);
// Write new EXIF chunk to output stream
@@ -6589,7 +6594,7 @@ public class ExifInterface {
int widthAndHeight = 0;
int width = 0;
int height = 0;
- int alpha = 0;
+ boolean alpha = false;
// Save VP8 frame data for later
byte[] vp8Frame = new byte[3];
@@ -6624,7 +6629,7 @@ public class ExifInterface {
width = ((widthAndHeight << 18) >> 18) + 1;
height = ((widthAndHeight << 4) >> 18) + 1;
// Retrieve alpha bit
- alpha = widthAndHeight & (1 << 3);
+ alpha = (widthAndHeight & (1 << 3)) != 0;
bytesToRead -= (1 /* VP8L signature */ + 4);
}
@@ -6632,10 +6637,12 @@ public class ExifInterface {
nonHeaderOutputStream.write(WEBP_CHUNK_TYPE_VP8X);
nonHeaderOutputStream.writeInt(WEBP_CHUNK_TYPE_VP8X_DEFAULT_LENGTH);
byte[] data = new byte[WEBP_CHUNK_TYPE_VP8X_DEFAULT_LENGTH];
+ // ALPHA flag
+ if (alpha) {
+ data[0] = (byte) (data[0] | (1 << 4));
+ }
// EXIF flag
data[0] = (byte) (data[0] | (1 << 3));
- // ALPHA flag
- data[0] = (byte) (data[0] | (alpha << 4));
// VP8X stores Width - 1 and Height - 1 values
width -= 1;
height -= 1;