diff --git a/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentCreationTask.java b/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentCreationTask.java index d9e77d1d81ce81d134dbc4137b653af8a0dcc133..e9139be2518fbe34d37c90720e777ec3bf82e6be 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentCreationTask.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentCreationTask.java @@ -1,6 +1,8 @@ package org.briarproject.briar.android.attachment; import android.content.ContentResolver; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory.Options; import android.net.Uri; import android.support.annotation.Nullable; @@ -12,11 +14,17 @@ import org.briarproject.briar.api.messaging.AttachmentHeader; import org.briarproject.briar.api.messaging.MessagingManager; import org.jsoup.UnsupportedMimeTypeException; +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.Collection; import java.util.logging.Logger; +import static android.graphics.Bitmap.CompressFormat.JPEG; +import static android.graphics.BitmapFactory.decodeStream; +import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; import static java.util.logging.Logger.getLogger; import static org.briarproject.bramble.util.IoUtils.tryToClose; @@ -24,6 +32,7 @@ import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.now; import static org.briarproject.briar.api.messaging.MessagingConstants.IMAGE_MIME_TYPES; +import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_IMAGE_SIZE; @NotNullByDefault class AttachmentCreationTask { @@ -31,8 +40,11 @@ class AttachmentCreationTask { private static Logger LOG = getLogger(AttachmentCreationTask.class.getName()); + private static final int MAX_ATTACHMENT_DIMENSION = 1000; + private final MessagingManager messagingManager; private final ContentResolver contentResolver; + private final ImageSizeCalculator imageSizeCalculator; private final GroupId groupId; private final Collection<Uri> uris; private final boolean needsSize; @@ -43,24 +55,26 @@ class AttachmentCreationTask { AttachmentCreationTask(MessagingManager messagingManager, ContentResolver contentResolver, - AttachmentCreator attachmentCreator, GroupId groupId, - Collection<Uri> uris, boolean needsSize) { + AttachmentCreator attachmentCreator, + ImageSizeCalculator imageSizeCalculator, + GroupId groupId, Collection<Uri> uris, boolean needsSize) { this.messagingManager = messagingManager; this.contentResolver = contentResolver; + this.imageSizeCalculator = imageSizeCalculator; this.groupId = groupId; this.uris = uris; this.needsSize = needsSize; this.attachmentCreator = attachmentCreator; } - public void cancel() { + void cancel() { canceled = true; attachmentCreator = null; } @IoExecutor - public void storeAttachments() { - for (Uri uri: uris) processUri(uri); + void storeAttachments() { + for (Uri uri : uris) processUri(uri); AttachmentCreator attachmentCreator = this.attachmentCreator; if (!canceled && attachmentCreator != null) attachmentCreator.onAttachmentCreationFinished(); @@ -98,6 +112,8 @@ class AttachmentCreationTask { } InputStream is = contentResolver.openInputStream(uri); if (is == null) throw new IOException(); + is = compressImage(is, contentType); + contentType = "image/jpeg"; long timestamp = System.currentTimeMillis(); AttachmentHeader h = messagingManager .addLocalAttachment(groupId, timestamp, contentType, is); @@ -113,4 +129,48 @@ class AttachmentCreationTask { return false; } + private InputStream compressImage(InputStream is, String contentType) + throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try { + Bitmap bitmap = createBitmap(is, contentType); + for (int quality = 100; quality >= 0; quality -= 10) { + if (!bitmap.compress(JPEG, quality, out)) + throw new IOException(); + if (out.size() <= MAX_IMAGE_SIZE) { + if (LOG.isLoggable(INFO)) { + LOG.info("Compressed image to " + + out.size() + " bytes, quality " + quality); + } + return new ByteArrayInputStream(out.toByteArray()); + } + out.reset(); + } + throw new IOException(); + } finally { + tryToClose(is, LOG, WARNING); + } + } + + private Bitmap createBitmap(InputStream is, String contentType) + throws IOException { + is = new BufferedInputStream(is); + Size size = imageSizeCalculator.getSize(is, contentType); + if (size.error) throw new IOException(); + if (LOG.isLoggable(INFO)) + LOG.info("Original image size: " + size.width + "x" + size.height); + int dimension = Math.max(size.width, size.height); + int scale = 1; + while (dimension > MAX_ATTACHMENT_DIMENSION) { + scale *= 2; + dimension /= 2; + } + if (LOG.isLoggable(INFO)) + LOG.info("Scaling attachment by factor of " + scale); + Options options = new Options(); + options.inSampleSize = scale; + Bitmap bitmap = decodeStream(is, null, options); + if (bitmap == null) throw new IOException(); + return bitmap; + } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentCreatorImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentCreatorImpl.java index bdf9d852dc787203da2a5612f1342be5afd7580d..e78be6805ff3a829cd12875b6989955fc36b3873 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentCreatorImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentCreatorImpl.java @@ -47,6 +47,7 @@ class AttachmentCreatorImpl implements AttachmentCreator { private final Executor ioExecutor; private final MessagingManager messagingManager; private final AttachmentRetriever retriever; + private final ImageSizeCalculator imageSizeCalculator; private final CopyOnWriteArrayList<Uri> uris = new CopyOnWriteArrayList<>(); private final CopyOnWriteArrayList<AttachmentItemResult> itemResults = @@ -60,11 +61,13 @@ class AttachmentCreatorImpl implements AttachmentCreator { @Inject AttachmentCreatorImpl(Application app, @IoExecutor Executor ioExecutor, - MessagingManager messagingManager, AttachmentRetriever retriever) { + MessagingManager messagingManager, AttachmentRetriever retriever, + ImageSizeCalculator imageSizeCalculator) { this.app = app; this.ioExecutor = ioExecutor; this.messagingManager = messagingManager; this.retriever = retriever; + this.imageSizeCalculator = imageSizeCalculator; } @Override @@ -78,7 +81,8 @@ class AttachmentCreatorImpl implements AttachmentCreator { if (id == null) throw new IllegalStateException(); boolean needsSize = uris.size() == 1; task = new AttachmentCreationTask(messagingManager, - app.getContentResolver(), this, id, uris, needsSize); + app.getContentResolver(), this, imageSizeCalculator, id, + uris, needsSize); ioExecutor.execute(() -> task.storeAttachments()); }); MutableLiveData<AttachmentResult> result = new MutableLiveData<>();