~singpolyma/cheogram-android

ea0dc558cb823ef0286e44cafb7080ce6de3427d — Daniel Gultsch 2 years ago 80d8b6d
use androidx ExifInterface to parse rotation. fixes #4154
4 files changed, 35 insertions(+), 178 deletions(-)

M build.gradle
M src/main/java/eu/siacs/conversations/entities/Account.java
M src/main/java/eu/siacs/conversations/persistance/FileBackend.java
D src/main/java/eu/siacs/conversations/utils/ExifHelper.java
M build.gradle => build.gradle +4 -4
@@ 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'

M src/main/java/eu/siacs/conversations/entities/Account.java => src/main/java/eu/siacs/conversations/entities/Account.java +3 -1
@@ 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) {

M src/main/java/eu/siacs/conversations/persistance/FileBackend.java => src/main/java/eu/siacs/conversations/persistance/FileBackend.java +28 -12
@@ 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;
        }
    }

D src/main/java/eu/siacs/conversations/utils/ExifHelper.java => src/main/java/eu/siacs/conversations/utils/ExifHelper.java +0 -161
@@ 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;
        }
    }
}