~singpolyma/cheogram-android

466cbac006766c7d442331824600ddbbf2f01b69 — Stephen Paul Weber 5 months ago 89c5e11
Allow publishing an animated avatar

This requires not compressing it or cropping it unless we want to do *a lot*
more code, so file must be under 100k or we freeze it and no promises what your
non-square avatars will look like generally.
M src/main/java/eu/siacs/conversations/Config.java => src/main/java/eu/siacs/conversations/Config.java +1 -1
@@ 86,7 86,7 @@ public final class Config {

    public static final int AVATAR_SIZE = 192;
    public static final Bitmap.CompressFormat AVATAR_FORMAT = Bitmap.CompressFormat.JPEG;
    public static final int AVATAR_CHAR_LIMIT = 9400;
    public static final int AVATAR_CHAR_LIMIT = 100000;

    public static final int IMAGE_SIZE = 1920;
    public static final Bitmap.CompressFormat IMAGE_FORMAT = Bitmap.CompressFormat.JPEG;

M src/main/java/eu/siacs/conversations/persistance/FileBackend.java => src/main/java/eu/siacs/conversations/persistance/FileBackend.java +70 -7
@@ 2,6 2,7 @@ package eu.siacs.conversations.persistance;

import android.content.ContentResolver;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Bitmap;


@@ 1504,21 1505,60 @@ public class FileBackend {
    }

    private Avatar getUncompressedAvatar(Uri uri) {
        Bitmap bitmap = null;
        try {
            bitmap =
            if (android.os.Build.VERSION.SDK_INT >= 28) {
                ImageDecoder.Source source = ImageDecoder.createSource(mXmppConnectionService.getContentResolver(), uri);
                int[] size = new int[] { 0, 0 };
                boolean[] animated = new boolean[] { false };
                String[] mimeType = new String[] { null };
                Drawable drawable = ImageDecoder.decodeDrawable(source, (decoder, info, src) -> {
                    mimeType[0] = info.getMimeType();
                    animated[0] = info.isAnimated();
                    size[0] = info.getSize().getWidth();
                    size[1] = info.getSize().getHeight();
                });

                if (animated[0]) {
                    Avatar avatar = getPepAvatar(uri, size[0], size[1], mimeType[0]);
                    if (avatar != null) return avatar;
                }

                return getPepAvatar(drawDrawable(drawable), Bitmap.CompressFormat.PNG, 100);
            } else {
                Bitmap bitmap =
                    BitmapFactory.decodeStream(
                            mXmppConnectionService.getContentResolver().openInputStream(uri));
            return getPepAvatar(bitmap, Bitmap.CompressFormat.PNG, 100);
                return getPepAvatar(bitmap, Bitmap.CompressFormat.PNG, 100);
            }
        } catch (Exception e) {
            return null;
        } finally {
            if (bitmap != null) {
                bitmap.recycle();
            }
        }
    }

    private Avatar getPepAvatar(Uri uri, int width, int height, final String mimeType) throws IOException, NoSuchAlgorithmException {
        AssetFileDescriptor fd = mXmppConnectionService.getContentResolver().openAssetFileDescriptor(uri, "r");
        if (fd.getLength() > Config.AVATAR_CHAR_LIMIT) return null; // Too big to use raw file

        ByteArrayOutputStream mByteArrayOutputStream = new ByteArrayOutputStream();
        Base64OutputStream mBase64OutputStream =
                new Base64OutputStream(mByteArrayOutputStream, Base64.DEFAULT);
        MessageDigest digest = MessageDigest.getInstance("SHA-1");
        DigestOutputStream mDigestOutputStream =
                new DigestOutputStream(mBase64OutputStream, digest);

        ByteStreams.copy(fd.createInputStream(), mDigestOutputStream);
        mDigestOutputStream.flush();
        mDigestOutputStream.close();

        final Avatar avatar = new Avatar();
        avatar.sha1sum = CryptoHelper.bytesToHex(digest.digest());
        avatar.image = new String(mByteArrayOutputStream.toByteArray());
        avatar.type = mimeType;
        avatar.width = width;
        avatar.height = height;
        return avatar;
    }

    private Avatar getPepAvatar(Bitmap bitmap, Bitmap.CompressFormat format, int quality) {
        try {
            ByteArrayOutputStream mByteArrayOutputStream = new ByteArrayOutputStream();


@@ 1696,6 1736,29 @@ public class FileBackend {
        return Uri.fromFile(getAvatarFile(avatar));
    }

    public Drawable cropCenterSquareDrawable(Uri image, int size) {
        if (android.os.Build.VERSION.SDK_INT >= 28) {
            try {
                ImageDecoder.Source source = ImageDecoder.createSource(mXmppConnectionService.getContentResolver(), image);
                return ImageDecoder.decodeDrawable(source, (decoder, info, src) -> {
                    int w = info.getSize().getWidth();
                    int h = info.getSize().getHeight();
                    Rect r = rectForSize(w, h, size);
                    decoder.setTargetSize(r.width(), r.height());

                    int newSize = Math.min(r.width(), r.height());
                    int left = (r.width() - newSize) / 2;
                    int top = (r.height() - newSize) / 2;
                    decoder.setCrop(new Rect(left, top, left + newSize, top + newSize));
                });
            } catch (final IOException e) {
                return null;
            }
        } else {
            return new BitmapDrawable(cropCenterSquare(image, size));
        }
    }

    public Bitmap cropCenterSquare(Uri image, int size) {
        if (image == null) {
            return null;

M src/main/java/eu/siacs/conversations/ui/PublishGroupChatProfilePictureActivity.java => src/main/java/eu/siacs/conversations/ui/PublishGroupChatProfilePictureActivity.java +22 -2
@@ 31,9 31,11 @@ package eu.siacs.conversations.ui;

import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.drawable.AnimatedImageDrawable;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.View;


@@ 83,10 85,13 @@ public class PublishGroupChatProfilePictureActivity extends XmppActivity impleme
            bitmap = xmppConnectionService.getAvatarService().get(conversation, size);
        } else {
            Log.d(Config.LOGTAG, "loading " + uri.toString() + " into preview");
            bitmap = new BitmapDrawable(xmppConnectionService.getFileBackend().cropCenterSquare(uri, size));
            bitmap = xmppConnectionService.getFileBackend().cropCenterSquareDrawable(uri, size);
        }
        this.binding.accountImage.setImageDrawable(bitmap);
        this.binding.publishButton.setEnabled(uri != null);
        if (Build.VERSION.SDK_INT >= 28 && bitmap instanceof AnimatedImageDrawable) {
            ((AnimatedImageDrawable) bitmap).start();
        }
    }

    @Override


@@ 131,9 136,24 @@ public class PublishGroupChatProfilePictureActivity extends XmppActivity impleme
            }
        } else if (requestCode == REQUEST_CHOOSE_PICTURE) {
            if (resultCode == RESULT_OK) {
                PublishProfilePictureActivity.cropUri(this, data.getData());
                cropUri(data.getData());
            }
        }
    }

    public void cropUri(final Uri uri) {
        if (Build.VERSION.SDK_INT >= 28) {
            this.uri = uri;
            reloadAvatar();
            if (this.binding.accountImage.getDrawable() instanceof AnimatedImageDrawable) {
                return;
            }
        }

        CropImage.activity(uri).setOutputCompressFormat(Bitmap.CompressFormat.PNG)
                .setAspectRatio(1, 1)
                .setMinCropResultSize(Config.AVATAR_SIZE, Config.AVATAR_SIZE)
                .start(this);
    }

    @Override

M src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java => src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java +17 -5
@@ 2,6 2,7 @@ package eu.siacs.conversations.ui;

import android.app.Activity;
import android.content.Intent;
import android.graphics.drawable.AnimatedImageDrawable;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.Bitmap;


@@ 174,7 175,7 @@ public class PublishProfilePictureActivity extends XmppActivity implements XmppC
            }
        } else if (requestCode == REQUEST_CHOOSE_PICTURE) {
            if (resultCode == RESULT_OK) {
                cropUri(this, data.getData());
                cropUri(data.getData());
            }
        }
    }


@@ 227,7 228,7 @@ public class PublishProfilePictureActivity extends XmppActivity implements XmppC
        final Uri uri = intent != null ? intent.getData() : null;

        if (uri != null && handledExternalUri.compareAndSet(false,true)) {
            cropUri(this, uri);
            cropUri(uri);
            return;
        }



@@ 237,11 238,19 @@ public class PublishProfilePictureActivity extends XmppActivity implements XmppC
        configureActionBar(getSupportActionBar(), !this.mInitialAccountSetup && !handledExternalUri.get());
    }

    public static void cropUri(final Activity activity, final Uri uri) {
    public void cropUri(final Uri uri) {
        if (Build.VERSION.SDK_INT >= 28) {
            loadImageIntoPreview(uri);
            if (this.avatar.getDrawable() instanceof AnimatedImageDrawable) {
                this.avatarUri = uri;
                return;
            }
        }

        CropImage.activity(uri).setOutputCompressFormat(Bitmap.CompressFormat.PNG)
                .setAspectRatio(1, 1)
                .setMinCropResultSize(Config.AVATAR_SIZE, Config.AVATAR_SIZE)
                .start(activity);
                .start(this);
    }

    protected void loadImageIntoPreview(Uri uri) {


@@ 251,7 260,7 @@ public class PublishProfilePictureActivity extends XmppActivity implements XmppC
            bm = avatarService().get(account, (int) getResources().getDimension(R.dimen.publish_avatar_size));
        } else {
            try {
                bm = new BitmapDrawable(xmppConnectionService.getFileBackend().cropCenterSquare(uri, (int) getResources().getDimension(R.dimen.publish_avatar_size)));
                bm = xmppConnectionService.getFileBackend().cropCenterSquareDrawable(uri, (int) getResources().getDimension(R.dimen.publish_avatar_size));
            } catch (Exception e) {
                Log.d(Config.LOGTAG, "unable to load bitmap into image view", e);
            }


@@ 265,6 274,9 @@ public class PublishProfilePictureActivity extends XmppActivity implements XmppC
            return;
        }
        this.avatar.setImageDrawable(bm);
        if (Build.VERSION.SDK_INT >= 28 && bm instanceof AnimatedImageDrawable) {
            ((AnimatedImageDrawable) bm).start();
        }
        if (support) {
            togglePublishButton(uri != null, R.string.publish);
            this.hintOrWarning.setVisibility(View.INVISIBLE);