diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java index fb3e54728a119fe6fb6cd03ad118792521edb7e7..e9254414ee720943d1b042bcdb5042988fef6c4b 100644 --- a/src/main/java/eu/siacs/conversations/Config.java +++ b/src/main/java/eu/siacs/conversations/Config.java @@ -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; diff --git a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java index 542d6dec34f4f302d2ebb9b96cc85e1b76ad91a4..5cc0b12576a9173e3806f36637a131bde7512ddd 100644 --- a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java @@ -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; diff --git a/src/main/java/eu/siacs/conversations/ui/PublishGroupChatProfilePictureActivity.java b/src/main/java/eu/siacs/conversations/ui/PublishGroupChatProfilePictureActivity.java index 4643a9c814058e427e230063ca81661e7accfc85..198eadbd14cfbe1d93eefe7ad935726d5102edeb 100644 --- a/src/main/java/eu/siacs/conversations/ui/PublishGroupChatProfilePictureActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/PublishGroupChatProfilePictureActivity.java @@ -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 diff --git a/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java b/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java index 1bf8dc9e489d45de81bed116defff5e49fc709b2..d5945802459be35f88464e62b3ed23a19d0c32dd 100644 --- a/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java @@ -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);