use androidx ExifInterface to parse rotation. fixes #4154

Daniel Gultsch created

Change summary

build.gradle                                                      |   8 
src/main/java/eu/siacs/conversations/entities/Account.java        |   4 
src/main/java/eu/siacs/conversations/persistance/FileBackend.java |  40 
src/main/java/eu/siacs/conversations/utils/ExifHelper.java        | 161 -
4 files changed, 35 insertions(+), 178 deletions(-)

Detailed changes

build.gradle 🔗

@@ -8,7 +8,7 @@ buildscript {
         mavenCentral()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:7.0.1'
+        classpath 'com.android.tools.build:gradle:7.0.2'
     }
 }
 
@@ -42,12 +42,12 @@ dependencies {
     }
     conversationsPlaystoreCompatImplementation("com.android.installreferrer:installreferrer:2.2")
     conversationsPlaystoreSystemImplementation("com.android.installreferrer:installreferrer:2.2")
-    quicksyPlaystoreCompatImplementation 'com.google.android.gms:play-services-auth-api-phone:17.5.0'
-    quicksyPlaystoreSystemImplementation 'com.google.android.gms:play-services-auth-api-phone:17.5.0'
+    quicksyPlaystoreCompatImplementation 'com.google.android.gms:play-services-auth-api-phone:17.5.1'
+    quicksyPlaystoreSystemImplementation 'com.google.android.gms:play-services-auth-api-phone:17.5.1'
     implementation 'org.sufficientlysecure:openpgp-api:10.0'
     implementation 'com.theartofdev.edmodo:android-image-cropper:2.8.0'
     implementation 'androidx.appcompat:appcompat:1.2.0'
-    implementation 'androidx.exifinterface:exifinterface:1.3.2'
+    implementation 'androidx.exifinterface:exifinterface:1.3.3'
     implementation 'androidx.cardview:cardview:1.0.0'
     implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
     implementation 'androidx.emoji:emoji:1.1.0'

src/main/java/eu/siacs/conversations/entities/Account.java 🔗

@@ -5,6 +5,8 @@ import android.database.Cursor;
 import android.os.SystemClock;
 import android.util.Log;
 
+import com.google.common.base.Strings;
+
 import org.json.JSONException;
 import org.json.JSONObject;
 
@@ -247,7 +249,7 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
     }
 
     public String getHostname() {
-        return this.hostname == null ? "" : this.hostname;
+        return Strings.nullToEmpty(this.hostname);
     }
 
     public void setHostname(String hostname) {

src/main/java/eu/siacs/conversations/persistance/FileBackend.java 🔗

@@ -31,6 +31,7 @@ import android.util.LruCache;
 import androidx.annotation.RequiresApi;
 import androidx.annotation.StringRes;
 import androidx.core.content.FileProvider;
+import androidx.exifinterface.media.ExifInterface;
 
 import java.io.ByteArrayOutputStream;
 import java.io.Closeable;
@@ -64,7 +65,6 @@ import eu.siacs.conversations.ui.RecordingActivity;
 import eu.siacs.conversations.ui.util.Attachment;
 import eu.siacs.conversations.utils.Compatibility;
 import eu.siacs.conversations.utils.CryptoHelper;
-import eu.siacs.conversations.utils.ExifHelper;
 import eu.siacs.conversations.utils.FileUtils;
 import eu.siacs.conversations.utils.FileWriterException;
 import eu.siacs.conversations.utils.MimeUtils;
@@ -808,19 +808,34 @@ public class FileBackend {
         }
     }
 
-    private int getRotation(File file) {
-        return getRotation(Uri.parse("file://" + file.getAbsolutePath()));
+    private int getRotation(final File file) {
+        try (final InputStream inputStream = new FileInputStream(file)) {
+            return getRotation(inputStream);
+        } catch (Exception e) {
+            return 0;
+        }
     }
 
-    private int getRotation(Uri image) {
-        InputStream is = null;
-        try {
-            is = mXmppConnectionService.getContentResolver().openInputStream(image);
-            return ExifHelper.getOrientation(is);
-        } catch (FileNotFoundException e) {
+    private int getRotation(final Uri image) {
+        try (final InputStream is = mXmppConnectionService.getContentResolver().openInputStream(image)) {
+            return is == null ? 0 : getRotation(is);
+        } catch (final Exception e) {
             return 0;
-        } finally {
-            close(is);
+        }
+    }
+
+    private static int getRotation(final InputStream inputStream) throws IOException {
+        final ExifInterface exif = new ExifInterface(inputStream);
+        final int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED);
+        switch (orientation) {
+            case ExifInterface.ORIENTATION_ROTATE_180:
+                return 180;
+            case ExifInterface.ORIENTATION_ROTATE_90:
+                return 90;
+            case ExifInterface.ORIENTATION_ROTATE_270:
+                return 270;
+            default:
+                return 0;
         }
     }
 
@@ -1468,7 +1483,8 @@ public class FileBackend {
             this.resId = resId;
         }
 
-        public @StringRes int getResId() {
+        public @StringRes
+        int getResId() {
             return resId;
         }
     }

src/main/java/eu/siacs/conversations/utils/ExifHelper.java 🔗

@@ -1,161 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package eu.siacs.conversations.utils;
-
-import android.util.Log;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-public class ExifHelper {
-    private static final String TAG = "CameraExif";
-
-    public static int getOrientation(InputStream is) {
-        if (is == null) {
-            return 0;
-        }
-
-        byte[] buf = new byte[8];
-        int length = 0;
-
-        // ISO/IEC 10918-1:1993(E)
-        while (read(is, buf, 2) && (buf[0] & 0xFF) == 0xFF) {
-            int marker = buf[1] & 0xFF;
-
-            // Check if the marker is a padding.
-            if (marker == 0xFF) {
-                continue;
-            }
-
-            // Check if the marker is SOI or TEM.
-            if (marker == 0xD8 || marker == 0x01) {
-                continue;
-            }
-            // Check if the marker is EOI or SOS.
-            if (marker == 0xD9 || marker == 0xDA) {
-                return 0;
-            }
-
-            // Get the length and check if it is reasonable.
-            if (!read(is, buf, 2)) {
-                return 0;
-            }
-            length = pack(buf, 0, 2, false);
-            if (length < 2) {
-                Log.e(TAG, "Invalid length");
-                return 0;
-            }
-            length -= 2;
-
-            // Break if the marker is EXIF in APP1.
-            if (marker == 0xE1 && length >= 6) {
-                if (!read(is, buf, 6)) return 0;
-                length -= 6;
-                if (pack(buf, 0, 4, false) == 0x45786966 &&
-                    pack(buf, 4, 2, false) == 0) {
-                    break;
-                }
-            }
-
-            // Skip other markers.
-            try {
-                is.skip(length);
-            } catch (IOException ex) {
-                return 0;
-            }
-            length = 0;
-        }
-
-        // JEITA CP-3451 Exif Version 2.2
-        if (length > 8) {
-            int offset = 0;
-            byte[] jpeg = new byte[length];
-            if (!read(is, jpeg, length)) {
-                return 0;
-            }
-
-            // Identify the byte order.
-            int tag = pack(jpeg, offset, 4, false);
-            if (tag != 0x49492A00 && tag != 0x4D4D002A) {
-                Log.e(TAG, "Invalid byte order");
-                return 0;
-            }
-            boolean littleEndian = (tag == 0x49492A00);
-
-            // Get the offset and check if it is reasonable.
-            int count = pack(jpeg, offset + 4, 4, littleEndian) + 2;
-            if (count < 10 || count > length) {
-                Log.e(TAG, "Invalid offset");
-                return 0;
-            }
-            offset += count;
-            length -= count;
-
-            // Get the count and go through all the elements.
-            count = pack(jpeg, offset - 2, 2, littleEndian);
-            while (count-- > 0 && length >= 12) {
-                // Get the tag and check if it is orientation.
-                tag = pack(jpeg, offset, 2, littleEndian);
-                if (tag == 0x0112) {
-                    // We do not really care about type and count, do we?
-                    int orientation = pack(jpeg, offset + 8, 2, littleEndian);
-                    switch (orientation) {
-                        case 1:
-                            return 0;
-                        case 3:
-                            return 180;
-                        case 6:
-                            return 90;
-                        case 8:
-                            return 270;
-                    }
-                    Log.i(TAG, "Unsupported orientation");
-                    return 0;
-                }
-                offset += 12;
-                length -= 12;
-            }
-        }
-
-        Log.i(TAG, "Orientation not found");
-        return 0;
-    }
-
-    private static int pack(byte[] bytes, int offset, int length,
-            boolean littleEndian) {
-        int step = 1;
-        if (littleEndian) {
-            offset += length - 1;
-            step = -1;
-        }
-
-        int value = 0;
-        while (length-- > 0) {
-            value = (value << 8) | (bytes[offset] & 0xFF);
-            offset += step;
-        }
-        return value;
-    }
-
-    private static boolean read(InputStream is, byte[] buf, int length) {
-        try {
-            return is.read(buf, 0, length) == length;
-        } catch (IOException ex) {
-            return false;
-        }
-    }
-}