diff --git a/briar-android/src/androidTestOfficial/java/org/briarproject/briar/android/BriarUiTestComponent.java b/briar-android/src/androidTestOfficial/java/org/briarproject/briar/android/BriarUiTestComponent.java index 8aa02d42af75f09ebd92eb8e9d7f88a7c50bb8ce..8d905dcc11ca17df8f4e9301e55393af7891c78c 100644 --- a/briar-android/src/androidTestOfficial/java/org/briarproject/briar/android/BriarUiTestComponent.java +++ b/briar-android/src/androidTestOfficial/java/org/briarproject/briar/android/BriarUiTestComponent.java @@ -4,6 +4,7 @@ import org.briarproject.bramble.BrambleAndroidModule; import org.briarproject.bramble.BrambleCoreModule; import org.briarproject.bramble.account.BriarAccountModule; import org.briarproject.briar.BriarCoreModule; +import org.briarproject.briar.android.attachment.AttachmentModule; import org.briarproject.briar.android.navdrawer.NavDrawerActivityTest; import javax.inject.Singleton; @@ -13,6 +14,7 @@ import dagger.Component; @Singleton @Component(modules = { AppModule.class, + AttachmentModule.class, BriarCoreModule.class, BrambleAndroidModule.class, BriarAccountModule.class, diff --git a/briar-android/src/androidTestOfficial/java/org/briarproject/briar/android/attachment/AttachmentRetrieverIntegrationTest.java b/briar-android/src/androidTestOfficial/java/org/briarproject/briar/android/attachment/AttachmentRetrieverIntegrationTest.java index 9881c74fb0117262106ad3a1de27e614c465adcd..29a0ecab53201278f6a537fd38a7c296746a67d0 100644 --- a/briar-android/src/androidTestOfficial/java/org/briarproject/briar/android/attachment/AttachmentRetrieverIntegrationTest.java +++ b/briar-android/src/androidTestOfficial/java/org/briarproject/briar/android/attachment/AttachmentRetrieverIntegrationTest.java @@ -47,8 +47,10 @@ public class AttachmentRetrieverIntegrationTest { ); private final MessageId msgId = new MessageId(getRandomId()); + private final ImageHelper imageHelper = new ImageHelperImpl(); private final AttachmentRetriever retriever = - new AttachmentRetriever(null, dimensions); + new AttachmentRetrieverImpl(null, dimensions, imageHelper, + new ImageSizeCalculator(imageHelper)); @Test public void testSmallJpegImage() throws Exception { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java b/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java index f71811826229f56bb4e99d1de65b20fe16d982f4..8fd44813c6bfeac16714d1c23fe3b13db614ab3a 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java @@ -30,6 +30,7 @@ import org.briarproject.bramble.api.system.LocationUtils; import org.briarproject.bramble.plugin.tor.CircumventionProvider; import org.briarproject.briar.BriarCoreEagerSingletons; import org.briarproject.briar.BriarCoreModule; +import org.briarproject.briar.android.attachment.AttachmentModule; import org.briarproject.briar.android.conversation.glide.BriarModelLoader; import org.briarproject.briar.android.login.SignInReminderReceiver; import org.briarproject.briar.android.reporting.BriarReportSender; @@ -68,7 +69,8 @@ import dagger.Component; BriarCoreModule.class, BrambleAndroidModule.class, BriarAccountModule.class, - AppModule.class + AppModule.class, + AttachmentModule.class }) public interface AndroidComponent extends BrambleCoreEagerSingletons, BrambleAndroidEagerSingletons, 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..8c55972a9b6567223247ba7729b3684958d13700 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 inSampleSize = 1; + while (dimension > MAX_ATTACHMENT_DIMENSION) { + inSampleSize *= 2; + dimension /= 2; + } + if (LOG.isLoggable(INFO)) + LOG.info("Scaling attachment by factor of " + inSampleSize); + Options options = new Options(); + options.inSampleSize = inSampleSize; + 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/AttachmentCreator.java b/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentCreator.java index 9d6433100c31441599b6a64e6c3fb94ef3cdc9f7..05916b20c0f56db669ce6fe09ca003b0403587f8 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentCreator.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentCreator.java @@ -1,85 +1,24 @@ package org.briarproject.briar.android.attachment; - -import android.app.Application; import android.arch.lifecycle.LiveData; -import android.arch.lifecycle.MutableLiveData; import android.net.Uri; -import android.support.annotation.Nullable; import android.support.annotation.UiThread; -import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.MessageId; -import org.briarproject.briar.R; -import org.briarproject.briar.api.messaging.Attachment; import org.briarproject.briar.api.messaging.AttachmentHeader; -import org.briarproject.briar.api.messaging.FileTooBigException; -import org.briarproject.briar.api.messaging.MessagingManager; -import org.jsoup.UnsupportedMimeTypeException; -import java.io.IOException; -import java.util.ArrayList; import java.util.Collection; import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.Executor; -import java.util.logging.Logger; - -import static java.util.logging.Level.WARNING; -import static java.util.logging.Logger.getLogger; -import static org.briarproject.bramble.util.LogUtils.logException; -import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce; -import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_IMAGE_SIZE; @NotNullByDefault -public class AttachmentCreator { - - private static Logger LOG = getLogger(AttachmentCreator.class.getName()); - - private final Application app; - @IoExecutor - private final Executor ioExecutor; - private final MessagingManager messagingManager; - private final AttachmentRetriever retriever; - - private final CopyOnWriteArrayList<Uri> uris = new CopyOnWriteArrayList<>(); - private final CopyOnWriteArrayList<AttachmentItemResult> itemResults = - new CopyOnWriteArrayList<>(); - - @Nullable - private AttachmentCreationTask task; - - @Nullable - private volatile MutableLiveData<AttachmentResult> result; - - public AttachmentCreator(Application app, @IoExecutor Executor ioExecutor, - MessagingManager messagingManager, AttachmentRetriever retriever) { - this.app = app; - this.ioExecutor = ioExecutor; - this.messagingManager = messagingManager; - this.retriever = retriever; - } +public interface AttachmentCreator { @UiThread - public LiveData<AttachmentResult> storeAttachments( - LiveData<GroupId> groupId, Collection<Uri> newUris) { - if (task != null || result != null || !uris.isEmpty()) - throw new IllegalStateException(); - uris.addAll(newUris); - observeForeverOnce(groupId, id -> { - if (id == null) throw new IllegalStateException(); - boolean needsSize = uris.size() == 1; - task = new AttachmentCreationTask(messagingManager, - app.getContentResolver(), this, id, uris, needsSize); - ioExecutor.execute(() -> task.storeAttachments()); - }); - MutableLiveData<AttachmentResult> result = new MutableLiveData<>(); - this.result = result; - return result; - } + LiveData<AttachmentResult> storeAttachments(LiveData<GroupId> groupId, + Collection<Uri> newUris); /** * This should be only called after configuration changes. @@ -87,72 +26,10 @@ public class AttachmentCreator { * They are already being created and returned by the existing LiveData. */ @UiThread - public LiveData<AttachmentResult> getLiveAttachments() { - MutableLiveData<AttachmentResult> result = this.result; - if (task == null || result == null || uris.isEmpty()) - throw new IllegalStateException(); - // A task is already running. It will update the result LiveData. - // So nothing more to do here. - return result; - } - - @IoExecutor - void onAttachmentHeaderReceived(Uri uri, AttachmentHeader h, - boolean needsSize) { - // get and cache AttachmentItem for ImagePreview - try { - Attachment a = retriever.getMessageAttachment(h); - AttachmentItem item = retriever.getAttachmentItem(a, needsSize); - if (item.hasError()) throw new IOException(); - AttachmentItemResult itemResult = - new AttachmentItemResult(uri, item); - itemResults.add(itemResult); - MutableLiveData<AttachmentResult> result = this.result; - if (result != null) result.postValue(getResult(false)); - } catch (IOException | DbException e) { - logException(LOG, WARNING, e); - onAttachmentError(uri, e); - } - } - - @IoExecutor - void onAttachmentError(Uri uri, Throwable t) { - // get error message - String errorMsg; - if (t instanceof UnsupportedMimeTypeException) { - String mimeType = ((UnsupportedMimeTypeException) t).getMimeType(); - errorMsg = app.getString( - R.string.image_attach_error_invalid_mime_type, mimeType); - } else if (t instanceof FileTooBigException) { - int mb = MAX_IMAGE_SIZE / 1024 / 1024; - errorMsg = app.getString(R.string.image_attach_error_too_big, mb); - } else { - errorMsg = null; // generic error - } - AttachmentItemResult itemResult = - new AttachmentItemResult(uri, errorMsg); - itemResults.add(itemResult); - MutableLiveData<AttachmentResult> result = this.result; - if (result != null) result.postValue(getResult(false)); - // expect to receive a cancel from the UI - } - - @IoExecutor - void onAttachmentCreationFinished() { - MutableLiveData<AttachmentResult> result = this.result; - if (result != null) result.postValue(getResult(true)); - } + LiveData<AttachmentResult> getLiveAttachments(); @UiThread - public List<AttachmentHeader> getAttachmentHeadersForSending() { - List<AttachmentHeader> headers = new ArrayList<>(itemResults.size()); - for (AttachmentItemResult itemResult : itemResults) { - // check if we are trying to send attachment items with errors - if (itemResult.getItem() == null) throw new IllegalStateException(); - headers.add(itemResult.getItem().getHeader()); - } - return headers; - } + List<AttachmentHeader> getAttachmentHeadersForSending(); /** * Marks the attachments as sent and adds the items to the cache for display @@ -160,70 +37,24 @@ public class AttachmentCreator { * @param id The MessageId of the sent message. */ @UiThread - public void onAttachmentsSent(MessageId id) { - List<AttachmentItem> items = new ArrayList<>(itemResults.size()); - for (AttachmentItemResult itemResult : itemResults) { - // check if we are trying to send attachment items with errors - if (itemResult.getItem() == null) throw new IllegalStateException(); - items.add(itemResult.getItem()); - } - retriever.cachePut(id, items); - resetState(); - } + void onAttachmentsSent(MessageId id); /** * Needs to be called when created attachments will not be sent anymore. */ @UiThread - public void cancel() { - if (task == null) throw new AssertionError(); - task.cancel(); - deleteUnsentAttachments(); - resetState(); - } + void cancel(); @UiThread - private void resetState() { - task = null; - uris.clear(); - itemResults.clear(); - MutableLiveData<AttachmentResult> result = this.result; - if (result != null) { - result.setValue(null); - this.result = null; - } - } + void deleteUnsentAttachments(); - @UiThread - public void deleteUnsentAttachments() { - // Make a copy for the IoExecutor as we clear the itemResults soon - List<AttachmentHeader> headers = new ArrayList<>(itemResults.size()); - for (AttachmentItemResult itemResult : itemResults) { - // check if we are trying to send attachment items with errors - if (itemResult.getItem() != null) - headers.add(itemResult.getItem().getHeader()); - } - ioExecutor.execute(() -> { - for (AttachmentHeader header : headers) { - try { - messagingManager.removeAttachment(header); - } catch (DbException e) { - logException(LOG, WARNING, e); - } - } - }); - } + @IoExecutor + void onAttachmentHeaderReceived(Uri uri, AttachmentHeader h, + boolean needsSize); - private AttachmentResult getResult(boolean finished) { - // Make a copy of the list, - // because our copy will continue to change in the background. - // (As it's a CopyOnWriteArrayList, - // the code that receives the result can safely do simple things - // like iterating over the list, - // but anything that involves calling more than one list method - // is still unsafe.) - Collection<AttachmentItemResult> items = new ArrayList<>(itemResults); - return new AttachmentResult(items, finished); - } + @IoExecutor + void onAttachmentError(Uri uri, Throwable t); -} + @IoExecutor + void onAttachmentCreationFinished(); +} \ No newline at end of file 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 new file mode 100644 index 0000000000000000000000000000000000000000..53687f48bcfad5d68128e8fc9c2e56113a96896a --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentCreatorImpl.java @@ -0,0 +1,233 @@ +package org.briarproject.briar.android.attachment; + + +import android.app.Application; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.MutableLiveData; +import android.net.Uri; +import android.support.annotation.Nullable; +import android.support.annotation.UiThread; + +import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.lifecycle.IoExecutor; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.sync.GroupId; +import org.briarproject.bramble.api.sync.MessageId; +import org.briarproject.briar.R; +import org.briarproject.briar.api.messaging.Attachment; +import org.briarproject.briar.api.messaging.AttachmentHeader; +import org.briarproject.briar.api.messaging.FileTooBigException; +import org.briarproject.briar.api.messaging.MessagingManager; +import org.jsoup.UnsupportedMimeTypeException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Executor; +import java.util.logging.Logger; + +import javax.inject.Inject; + +import static java.util.logging.Level.WARNING; +import static java.util.logging.Logger.getLogger; +import static org.briarproject.bramble.util.LogUtils.logException; +import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce; +import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_IMAGE_SIZE; + +@NotNullByDefault +class AttachmentCreatorImpl implements AttachmentCreator { + + private static Logger LOG = + getLogger(AttachmentCreatorImpl.class.getName()); + + private final Application app; + @IoExecutor + 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 = + new CopyOnWriteArrayList<>(); + + @Nullable + private AttachmentCreationTask task; + + @Nullable + private volatile MutableLiveData<AttachmentResult> result; + + @Inject + AttachmentCreatorImpl(Application app, @IoExecutor Executor ioExecutor, + MessagingManager messagingManager, AttachmentRetriever retriever, + ImageSizeCalculator imageSizeCalculator) { + this.app = app; + this.ioExecutor = ioExecutor; + this.messagingManager = messagingManager; + this.retriever = retriever; + this.imageSizeCalculator = imageSizeCalculator; + } + + @Override + @UiThread + public LiveData<AttachmentResult> storeAttachments( + LiveData<GroupId> groupId, Collection<Uri> newUris) { + if (task != null || result != null || !uris.isEmpty()) + throw new IllegalStateException(); + MutableLiveData<AttachmentResult> result = new MutableLiveData<>(); + this.result = result; + uris.addAll(newUris); + observeForeverOnce(groupId, id -> { + if (id == null) throw new IllegalStateException(); + boolean needsSize = uris.size() == 1; + task = new AttachmentCreationTask(messagingManager, + app.getContentResolver(), this, imageSizeCalculator, id, + uris, needsSize); + ioExecutor.execute(() -> task.storeAttachments()); + }); + return result; + } + + @Override + @UiThread + public LiveData<AttachmentResult> getLiveAttachments() { + MutableLiveData<AttachmentResult> result = this.result; + if (task == null || result == null || uris.isEmpty()) + throw new IllegalStateException(); + // A task is already running. It will update the result LiveData. + // So nothing more to do here. + return result; + } + + @Override + @IoExecutor + public void onAttachmentHeaderReceived(Uri uri, AttachmentHeader h, + boolean needsSize) { + // get and cache AttachmentItem for ImagePreview + try { + Attachment a = retriever.getMessageAttachment(h); + AttachmentItem item = retriever.getAttachmentItem(a, needsSize); + if (item.hasError()) throw new IOException(); + AttachmentItemResult itemResult = + new AttachmentItemResult(uri, item); + itemResults.add(itemResult); + MutableLiveData<AttachmentResult> result = this.result; + if (result != null) result.postValue(getResult(false)); + } catch (IOException | DbException e) { + logException(LOG, WARNING, e); + onAttachmentError(uri, e); + } + } + + @Override + @IoExecutor + public void onAttachmentError(Uri uri, Throwable t) { + // get error message + String errorMsg; + if (t instanceof UnsupportedMimeTypeException) { + String mimeType = ((UnsupportedMimeTypeException) t).getMimeType(); + errorMsg = app.getString( + R.string.image_attach_error_invalid_mime_type, mimeType); + } else if (t instanceof FileTooBigException) { + int mb = MAX_IMAGE_SIZE / 1024 / 1024; + errorMsg = app.getString(R.string.image_attach_error_too_big, mb); + } else { + errorMsg = null; // generic error + } + AttachmentItemResult itemResult = + new AttachmentItemResult(uri, errorMsg); + itemResults.add(itemResult); + MutableLiveData<AttachmentResult> result = this.result; + if (result != null) result.postValue(getResult(false)); + // expect to receive a cancel from the UI + } + + @Override + @IoExecutor + public void onAttachmentCreationFinished() { + MutableLiveData<AttachmentResult> result = this.result; + if (result != null) result.postValue(getResult(true)); + } + + @Override + @UiThread + public List<AttachmentHeader> getAttachmentHeadersForSending() { + List<AttachmentHeader> headers = new ArrayList<>(itemResults.size()); + for (AttachmentItemResult itemResult : itemResults) { + // check if we are trying to send attachment items with errors + if (itemResult.getItem() == null) throw new IllegalStateException(); + headers.add(itemResult.getItem().getHeader()); + } + return headers; + } + + @Override + @UiThread + public void onAttachmentsSent(MessageId id) { + List<AttachmentItem> items = new ArrayList<>(itemResults.size()); + for (AttachmentItemResult itemResult : itemResults) { + // check if we are trying to send attachment items with errors + if (itemResult.getItem() == null) throw new IllegalStateException(); + items.add(itemResult.getItem()); + } + retriever.cachePut(id, items); + resetState(); + } + + @Override + @UiThread + public void cancel() { + if (task == null) throw new AssertionError(); + task.cancel(); + deleteUnsentAttachments(); + resetState(); + } + + @UiThread + private void resetState() { + task = null; + uris.clear(); + itemResults.clear(); + MutableLiveData<AttachmentResult> result = this.result; + if (result != null) { + result.setValue(null); + this.result = null; + } + } + + @Override + @UiThread + public void deleteUnsentAttachments() { + // Make a copy for the IoExecutor as we clear the itemResults soon + List<AttachmentHeader> headers = new ArrayList<>(itemResults.size()); + for (AttachmentItemResult itemResult : itemResults) { + // check if we are trying to send attachment items with errors + if (itemResult.getItem() != null) + headers.add(itemResult.getItem().getHeader()); + } + ioExecutor.execute(() -> { + for (AttachmentHeader header : headers) { + try { + messagingManager.removeAttachment(header); + } catch (DbException e) { + logException(LOG, WARNING, e); + } + } + }); + } + + private AttachmentResult getResult(boolean finished) { + // Make a copy of the list, + // because our copy will continue to change in the background. + // (As it's a CopyOnWriteArrayList, + // the code that receives the result can safely do simple things + // like iterating over the list, + // but anything that involves calling more than one list method + // is still unsafe.) + Collection<AttachmentItemResult> items = new ArrayList<>(itemResults); + return new AttachmentResult(items, finished); + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentDimensions.java b/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentDimensions.java index 9c60101ee923f4314efb41f5650f03c76f40332a..1fda393939cdfb0693911cdad6e15d6e1a5444c5 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentDimensions.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentDimensions.java @@ -10,7 +10,7 @@ import javax.annotation.concurrent.Immutable; @Immutable @NotNullByDefault -public class AttachmentDimensions { +class AttachmentDimensions { final int defaultSize; final int minWidth, maxWidth; @@ -26,7 +26,7 @@ public class AttachmentDimensions { this.maxHeight = maxHeight; } - public static AttachmentDimensions getAttachmentDimensions(Resources res) { + static AttachmentDimensions getAttachmentDimensions(Resources res) { int defaultSize = res.getDimensionPixelSize(R.dimen.message_bubble_image_default); int minWidth = res.getDimensionPixelSize( diff --git a/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentManager.java b/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentManager.java index f69f6085ae98782088a6e7172bd71ef6f1f463ca..208d1de71611b566b38bfe1c11ef38941a08374a 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentManager.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentManager.java @@ -4,12 +4,14 @@ import android.arch.lifecycle.LiveData; import android.net.Uri; import android.support.annotation.UiThread; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.briar.api.messaging.AttachmentHeader; import java.util.Collection; import java.util.List; @UiThread +@NotNullByDefault public interface AttachmentManager { LiveData<AttachmentResult> storeAttachments(Collection<Uri> uri, diff --git a/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentModule.java b/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentModule.java new file mode 100644 index 0000000000000000000000000000000000000000..57ae44f565f8eddf9c777d779c5fd655e103015c --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentModule.java @@ -0,0 +1,43 @@ +package org.briarproject.briar.android.attachment; + +import android.app.Application; + +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; + +import static org.briarproject.briar.android.attachment.AttachmentDimensions.getAttachmentDimensions; + +@Module +public class AttachmentModule { + + @Provides + ImageHelper provideImageHelper(ImageHelperImpl imageHelper) { + return imageHelper; + } + + @Provides + ImageSizeCalculator provideImageSizeCalculator(ImageHelper imageHelper) { + return new ImageSizeCalculator(imageHelper); + } + + @Provides + AttachmentDimensions provideAttachmentDimensions(Application app) { + return getAttachmentDimensions(app.getResources()); + } + + @Provides + @Singleton + AttachmentRetriever provideAttachmentRetriever( + AttachmentRetrieverImpl attachmentRetriever) { + return attachmentRetriever; + } + + @Provides + @Singleton + AttachmentCreator provideAttachmentCreator( + AttachmentCreatorImpl attachmentCreator) { + return attachmentCreator; + } +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentRetriever.java b/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentRetriever.java index 31ba87e4a01aea8404e367ff533a9a527c7806e3..93bbcfba377e1b31d973cda44f6bbfcd06d5c1df 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentRetriever.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentRetriever.java @@ -1,241 +1,29 @@ package org.briarproject.briar.android.attachment; -import android.graphics.BitmapFactory; -import android.graphics.BitmapFactory.Options; import android.support.annotation.Nullable; -import android.support.annotation.VisibleForTesting; -import android.support.media.ExifInterface; -import android.webkit.MimeTypeMap; - -import com.bumptech.glide.util.MarkEnforcingInputStream; import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.sync.MessageId; -import org.briarproject.briar.android.attachment.ImageHelper.DecodeResult; import org.briarproject.briar.api.messaging.Attachment; import org.briarproject.briar.api.messaging.AttachmentHeader; -import org.briarproject.briar.api.messaging.MessagingManager; -import java.io.BufferedInputStream; -import java.io.IOException; import java.io.InputStream; import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.logging.Logger; - -import static android.support.media.ExifInterface.ORIENTATION_ROTATE_270; -import static android.support.media.ExifInterface.ORIENTATION_ROTATE_90; -import static android.support.media.ExifInterface.ORIENTATION_TRANSPOSE; -import static android.support.media.ExifInterface.ORIENTATION_TRANSVERSE; -import static android.support.media.ExifInterface.TAG_IMAGE_LENGTH; -import static android.support.media.ExifInterface.TAG_IMAGE_WIDTH; -import static android.support.media.ExifInterface.TAG_ORIENTATION; -import static java.util.logging.Level.WARNING; -import static java.util.logging.Logger.getLogger; -import static org.briarproject.bramble.util.IoUtils.tryToClose; -import static org.briarproject.bramble.util.LogUtils.logException; @NotNullByDefault -public class AttachmentRetriever { - - private static final Logger LOG = - getLogger(AttachmentRetriever.class.getName()); - private static final int READ_LIMIT = 1024 * 8192; - - private final MessagingManager messagingManager; - private final ImageHelper imageHelper; - private final int defaultSize; - private final int minWidth, maxWidth; - private final int minHeight, maxHeight; - - private final Map<MessageId, List<AttachmentItem>> attachmentCache = - new ConcurrentHashMap<>(); - - @VisibleForTesting - AttachmentRetriever(MessagingManager messagingManager, - AttachmentDimensions dimensions, ImageHelper imageHelper) { - this.messagingManager = messagingManager; - this.imageHelper = imageHelper; - defaultSize = dimensions.defaultSize; - minWidth = dimensions.minWidth; - maxWidth = dimensions.maxWidth; - minHeight = dimensions.minHeight; - maxHeight = dimensions.maxHeight; - } +public interface AttachmentRetriever { - public AttachmentRetriever(MessagingManager messagingManager, - AttachmentDimensions dimensions) { - this(messagingManager, dimensions, new ImageHelper() { - @Override - public DecodeResult decodeStream(InputStream is) { - Options options = new Options(); - options.inJustDecodeBounds = true; - BitmapFactory.decodeStream(is, null, options); - String mimeType = options.outMimeType; - if (mimeType == null) mimeType = ""; - return new DecodeResult(options.outWidth, options.outHeight, - mimeType); - } - - @Nullable - @Override - public String getExtensionFromMimeType(String mimeType) { - MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton(); - return mimeTypeMap.getExtensionFromMimeType(mimeType); - } - }); - } - - public void cachePut(MessageId messageId, - List<AttachmentItem> attachments) { - attachmentCache.put(messageId, attachments); - } + void cachePut(MessageId messageId, List<AttachmentItem> attachments); @Nullable - public List<AttachmentItem> cacheGet(MessageId messageId) { - return attachmentCache.get(messageId); - } + List<AttachmentItem> cacheGet(MessageId messageId); - public Attachment getMessageAttachment(AttachmentHeader h) - throws DbException { - return messagingManager.getAttachment(h); - } + Attachment getMessageAttachment(AttachmentHeader h) throws DbException; /** * Creates an {@link AttachmentItem} from the {@link Attachment}'s * {@link InputStream} which will be closed when this method returns. */ - public AttachmentItem getAttachmentItem(Attachment a, boolean needsSize) { - AttachmentHeader h = a.getHeader(); - if (!needsSize) { - String extension = - imageHelper.getExtensionFromMimeType(h.getContentType()); - boolean hasError = false; - if (extension == null) { - extension = ""; - hasError = true; - } - return new AttachmentItem(h, 0, 0, extension, 0, 0, hasError); - } - - Size size = new Size(); - InputStream is = new MarkEnforcingInputStream( - new BufferedInputStream(a.getStream())); - is.mark(READ_LIMIT); - try { - // use exif to get size - if (h.getContentType().equals("image/jpeg")) { - size = getSizeFromExif(is); - } - } catch (IOException e) { - logException(LOG, WARNING, e); - } - try { - // use BitmapFactory to get size - if (size.error) { - is.reset(); - // need to mark again to re-add read limit - is.mark(READ_LIMIT); - size = getSizeFromBitmap(is); - } - } catch (IOException e) { - logException(LOG, WARNING, e); - } finally { - tryToClose(is, LOG, WARNING); - } - - // calculate thumbnail size - Size thumbnailSize = new Size(defaultSize, defaultSize, size.mimeType); - if (!size.error) { - thumbnailSize = - getThumbnailSize(size.width, size.height, size.mimeType); - } - // get file extension - String extension = imageHelper.getExtensionFromMimeType(size.mimeType); - boolean hasError = extension == null || size.error; - if (!h.getContentType().equals(size.mimeType)) { - if (LOG.isLoggable(WARNING)) { - LOG.warning("Header has different mime type (" + - h.getContentType() + ") than image (" + size.mimeType + - ")."); - } - hasError = true; - } - if (extension == null) extension = ""; - return new AttachmentItem(h, size.width, size.height, extension, - thumbnailSize.width, thumbnailSize.height, hasError); - } - - /** - * Gets the size of a JPEG {@link InputStream} if EXIF info is available. - */ - private Size getSizeFromExif(InputStream is) throws IOException { - ExifInterface exif = new ExifInterface(is); - // these can return 0 independent of default value - int width = exif.getAttributeInt(TAG_IMAGE_WIDTH, 0); - int height = exif.getAttributeInt(TAG_IMAGE_LENGTH, 0); - if (width == 0 || height == 0) return new Size(); - int orientation = exif.getAttributeInt(TAG_ORIENTATION, 0); - if (orientation == ORIENTATION_ROTATE_90 || - orientation == ORIENTATION_ROTATE_270 || - orientation == ORIENTATION_TRANSVERSE || - orientation == ORIENTATION_TRANSPOSE) { - //noinspection SuspiciousNameCombination - return new Size(height, width, "image/jpeg"); - } - return new Size(width, height, "image/jpeg"); - } - - /** - * Gets the size of any image {@link InputStream}. - */ - private Size getSizeFromBitmap(InputStream is) { - DecodeResult result = imageHelper.decodeStream(is); - if (result.width < 1 || result.height < 1) return new Size(); - return new Size(result.width, result.height, result.mimeType); - } - - private Size getThumbnailSize(int width, int height, String mimeType) { - float widthPercentage = maxWidth / (float) width; - float heightPercentage = maxHeight / (float) height; - float scaleFactor = Math.min(widthPercentage, heightPercentage); - if (scaleFactor > 1) scaleFactor = 1f; - int thumbnailWidth = (int) (width * scaleFactor); - int thumbnailHeight = (int) (height * scaleFactor); - if (thumbnailWidth < minWidth || thumbnailHeight < minHeight) { - widthPercentage = minWidth / (float) width; - heightPercentage = minHeight / (float) height; - scaleFactor = Math.max(widthPercentage, heightPercentage); - thumbnailWidth = (int) (width * scaleFactor); - thumbnailHeight = (int) (height * scaleFactor); - if (thumbnailWidth > maxWidth) thumbnailWidth = maxWidth; - if (thumbnailHeight > maxHeight) thumbnailHeight = maxHeight; - } - return new Size(thumbnailWidth, thumbnailHeight, mimeType); - } - - private static class Size { - - private final int width; - private final int height; - private final String mimeType; - private final boolean error; - - private Size(int width, int height, String mimeType) { - this.width = width; - this.height = height; - this.mimeType = mimeType; - this.error = false; - } - - private Size() { - this.width = 0; - this.height = 0; - this.mimeType = ""; - this.error = true; - } - } - + AttachmentItem getAttachmentItem(Attachment a, boolean needsSize); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentRetrieverImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentRetrieverImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..2bef6e5192caa62d42306c46756941383e4618b6 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentRetrieverImpl.java @@ -0,0 +1,129 @@ +package org.briarproject.briar.android.attachment; + +import android.support.annotation.Nullable; + +import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.sync.MessageId; +import org.briarproject.briar.api.messaging.Attachment; +import org.briarproject.briar.api.messaging.AttachmentHeader; +import org.briarproject.briar.api.messaging.MessagingManager; + +import java.io.BufferedInputStream; +import java.io.InputStream; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Logger; + +import javax.inject.Inject; + +import static java.util.logging.Level.WARNING; +import static java.util.logging.Logger.getLogger; + +@NotNullByDefault +class AttachmentRetrieverImpl implements AttachmentRetriever { + + private static final Logger LOG = + getLogger(AttachmentRetrieverImpl.class.getName()); + + private final MessagingManager messagingManager; + private final ImageHelper imageHelper; + private final ImageSizeCalculator imageSizeCalculator; + private final int defaultSize; + private final int minWidth, maxWidth; + private final int minHeight, maxHeight; + + private final Map<MessageId, List<AttachmentItem>> attachmentCache = + new ConcurrentHashMap<>(); + + @Inject + AttachmentRetrieverImpl(MessagingManager messagingManager, + AttachmentDimensions dimensions, ImageHelper imageHelper, + ImageSizeCalculator imageSizeCalculator) { + this.messagingManager = messagingManager; + this.imageHelper = imageHelper; + this.imageSizeCalculator = imageSizeCalculator; + defaultSize = dimensions.defaultSize; + minWidth = dimensions.minWidth; + maxWidth = dimensions.maxWidth; + minHeight = dimensions.minHeight; + maxHeight = dimensions.maxHeight; + } + + @Override + public void cachePut(MessageId messageId, + List<AttachmentItem> attachments) { + attachmentCache.put(messageId, attachments); + } + + @Override + @Nullable + public List<AttachmentItem> cacheGet(MessageId messageId) { + return attachmentCache.get(messageId); + } + + @Override + public Attachment getMessageAttachment(AttachmentHeader h) + throws DbException { + return messagingManager.getAttachment(h); + } + + @Override + public AttachmentItem getAttachmentItem(Attachment a, boolean needsSize) { + AttachmentHeader h = a.getHeader(); + if (!needsSize) { + String extension = + imageHelper.getExtensionFromMimeType(h.getContentType()); + boolean hasError = false; + if (extension == null) { + extension = ""; + hasError = true; + } + return new AttachmentItem(h, 0, 0, extension, 0, 0, hasError); + } + + InputStream is = new BufferedInputStream(a.getStream()); + Size size = imageSizeCalculator.getSize(is, h.getContentType()); + + // calculate thumbnail size + Size thumbnailSize = new Size(defaultSize, defaultSize, size.mimeType); + if (!size.error) { + thumbnailSize = + getThumbnailSize(size.width, size.height, size.mimeType); + } + // get file extension + String extension = imageHelper.getExtensionFromMimeType(size.mimeType); + boolean hasError = extension == null || size.error; + if (!h.getContentType().equals(size.mimeType)) { + if (LOG.isLoggable(WARNING)) { + LOG.warning("Header has different mime type (" + + h.getContentType() + ") than image (" + size.mimeType + + ")."); + } + hasError = true; + } + if (extension == null) extension = ""; + return new AttachmentItem(h, size.width, size.height, extension, + thumbnailSize.width, thumbnailSize.height, hasError); + } + + private Size getThumbnailSize(int width, int height, String mimeType) { + float widthPercentage = maxWidth / (float) width; + float heightPercentage = maxHeight / (float) height; + float scaleFactor = Math.min(widthPercentage, heightPercentage); + if (scaleFactor > 1) scaleFactor = 1f; + int thumbnailWidth = (int) (width * scaleFactor); + int thumbnailHeight = (int) (height * scaleFactor); + if (thumbnailWidth < minWidth || thumbnailHeight < minHeight) { + widthPercentage = minWidth / (float) width; + heightPercentage = minHeight / (float) height; + scaleFactor = Math.max(widthPercentage, heightPercentage); + thumbnailWidth = (int) (width * scaleFactor); + thumbnailHeight = (int) (height * scaleFactor); + if (thumbnailWidth > maxWidth) thumbnailWidth = maxWidth; + if (thumbnailHeight > maxHeight) thumbnailHeight = maxHeight; + } + return new Size(thumbnailWidth, thumbnailHeight, mimeType); + } +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/attachment/ImageHelper.java b/briar-android/src/main/java/org/briarproject/briar/android/attachment/ImageHelper.java index 1264d65110692100f1310c25a8970cb09d1af36c..e1ea0776d6f7f2c18111cc61157c00b2acbe316d 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/attachment/ImageHelper.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/attachment/ImageHelper.java @@ -7,7 +7,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import java.io.InputStream; @NotNullByDefault -interface ImageHelper { +public interface ImageHelper { DecodeResult decodeStream(InputStream is); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/attachment/ImageHelperImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/attachment/ImageHelperImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..86a2ed39af90a74f4e7f46206ec4f17c62b9b4f3 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/attachment/ImageHelperImpl.java @@ -0,0 +1,39 @@ +package org.briarproject.briar.android.attachment; + +import android.graphics.BitmapFactory; +import android.support.annotation.Nullable; +import android.webkit.MimeTypeMap; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import java.io.InputStream; + +import javax.annotation.concurrent.Immutable; +import javax.inject.Inject; + +@Immutable +@NotNullByDefault +class ImageHelperImpl implements ImageHelper { + + @Inject + ImageHelperImpl() { + } + + @Override + public DecodeResult decodeStream(InputStream is) { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeStream(is, null, options); + String mimeType = options.outMimeType; + if (mimeType == null) mimeType = ""; + return new DecodeResult(options.outWidth, options.outHeight, + mimeType); + } + + @Nullable + @Override + public String getExtensionFromMimeType(String mimeType) { + MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton(); + return mimeTypeMap.getExtensionFromMimeType(mimeType); + } +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/attachment/ImageSizeCalculator.java b/briar-android/src/main/java/org/briarproject/briar/android/attachment/ImageSizeCalculator.java new file mode 100644 index 0000000000000000000000000000000000000000..41b3094cbce48ab222a904c350d9f2b3e87fb6a8 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/attachment/ImageSizeCalculator.java @@ -0,0 +1,94 @@ +package org.briarproject.briar.android.attachment; + +import android.support.media.ExifInterface; + +import com.bumptech.glide.util.MarkEnforcingInputStream; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.briar.android.attachment.ImageHelper.DecodeResult; + +import java.io.IOException; +import java.io.InputStream; +import java.util.logging.Logger; + +import static android.support.media.ExifInterface.ORIENTATION_ROTATE_270; +import static android.support.media.ExifInterface.ORIENTATION_ROTATE_90; +import static android.support.media.ExifInterface.ORIENTATION_TRANSPOSE; +import static android.support.media.ExifInterface.ORIENTATION_TRANSVERSE; +import static android.support.media.ExifInterface.TAG_IMAGE_LENGTH; +import static android.support.media.ExifInterface.TAG_IMAGE_WIDTH; +import static android.support.media.ExifInterface.TAG_ORIENTATION; +import static java.util.logging.Level.WARNING; +import static java.util.logging.Logger.getLogger; +import static org.briarproject.bramble.util.LogUtils.logException; + +@NotNullByDefault +class ImageSizeCalculator { + + private static final Logger LOG = + getLogger(ImageSizeCalculator.class.getName()); + + private static final int READ_LIMIT = 1024 * 8192; + + private final ImageHelper imageHelper; + + ImageSizeCalculator(ImageHelper imageHelper) { + this.imageHelper = imageHelper; + } + + Size getSize(InputStream is, String contentType) { + Size size = new Size(); + is = new MarkEnforcingInputStream(is); + is.mark(READ_LIMIT); + if (contentType.equals("image/jpeg")) { + try { + // use exif to get size + size = getSizeFromExif(is); + is.reset(); + } catch (IOException e) { + logException(LOG, WARNING, e); + } + } + if (size.error) { + // need to mark again to re-add read limit + is.mark(READ_LIMIT); + try { + // use BitmapFactory to get size + size = getSizeFromBitmap(is); + is.reset(); + } catch (IOException e) { + logException(LOG, WARNING, e); + } + } + return size; + } + + /** + * Gets the size of a JPEG {@link InputStream} if EXIF info is available. + */ + private Size getSizeFromExif(InputStream is) throws IOException { + ExifInterface exif = new ExifInterface(is); + // these can return 0 independent of default value + int width = exif.getAttributeInt(TAG_IMAGE_WIDTH, 0); + int height = exif.getAttributeInt(TAG_IMAGE_LENGTH, 0); + if (width == 0 || height == 0) return new Size(); + int orientation = exif.getAttributeInt(TAG_ORIENTATION, 0); + if (orientation == ORIENTATION_ROTATE_90 || + orientation == ORIENTATION_ROTATE_270 || + orientation == ORIENTATION_TRANSVERSE || + orientation == ORIENTATION_TRANSPOSE) { + //noinspection SuspiciousNameCombination + return new Size(height, width, "image/jpeg"); + } + return new Size(width, height, "image/jpeg"); + } + + /** + * Gets the size of any image {@link InputStream}. + */ + private Size getSizeFromBitmap(InputStream is) { + DecodeResult result = imageHelper.decodeStream(is); + if (result.width < 1 || result.height < 1) return new Size(); + return new Size(result.width, result.height, result.mimeType); + } +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/attachment/Size.java b/briar-android/src/main/java/org/briarproject/briar/android/attachment/Size.java new file mode 100644 index 0000000000000000000000000000000000000000..22871e1a55b8fbfa1631d2ba52eb8eb0da4401db --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/attachment/Size.java @@ -0,0 +1,23 @@ +package org.briarproject.briar.android.attachment; + +class Size { + + final int width; + final int height; + final String mimeType; + final boolean error; + + Size(int width, int height, String mimeType) { + this.width = width; + this.height = height; + this.mimeType = mimeType; + this.error = false; + } + + Size() { + this.width = 0; + this.height = 0; + this.mimeType = ""; + this.error = true; + } +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationViewModel.java index fc565d3a26e4e89e6f05a218c8f4782623e64e11..cb1c4128aaf7af1e843387cf341f9d95627d505c 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationViewModel.java @@ -18,7 +18,6 @@ import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.NoSuchContactException; import org.briarproject.bramble.api.db.TransactionManager; import org.briarproject.bramble.api.identity.AuthorId; -import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.settings.Settings; import org.briarproject.bramble.api.settings.SettingsManager; @@ -51,7 +50,6 @@ import static java.util.logging.Logger.getLogger; 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.android.attachment.AttachmentDimensions.getAttachmentDimensions; import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE; import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce; @@ -101,10 +99,13 @@ public class ConversationViewModel extends AndroidViewModel @Inject ConversationViewModel(Application application, @DatabaseExecutor Executor dbExecutor, - @IoExecutor Executor ioExecutor, TransactionManager db, - MessagingManager messagingManager, ContactManager contactManager, + TransactionManager db, + MessagingManager messagingManager, + ContactManager contactManager, SettingsManager settingsManager, - PrivateMessageFactory privateMessageFactory) { + PrivateMessageFactory privateMessageFactory, + AttachmentRetriever attachmentRetriever, + AttachmentCreator attachmentCreator) { super(application); this.dbExecutor = dbExecutor; this.db = db; @@ -112,10 +113,8 @@ public class ConversationViewModel extends AndroidViewModel this.contactManager = contactManager; this.settingsManager = settingsManager; this.privateMessageFactory = privateMessageFactory; - this.attachmentRetriever = new AttachmentRetriever(messagingManager, - getAttachmentDimensions(application.getResources())); - this.attachmentCreator = new AttachmentCreator(getApplication(), - ioExecutor, messagingManager, attachmentRetriever); + this.attachmentRetriever = attachmentRetriever; + this.attachmentCreator = attachmentCreator; messagingGroupId = Transformations .map(contact, c -> messagingManager.getContactGroup(c).getId()); contactDeleted.setValue(false); diff --git a/briar-android/src/test/java/org/briarproject/briar/android/attachment/AttachmentRetrieverTest.java b/briar-android/src/test/java/org/briarproject/briar/android/attachment/AttachmentRetrieverTest.java index 67670e8465ac63e6b86ab815f60dc9f93199da5a..4558c2ff8dd0e067070e88340d282e16c54b0c1c 100644 --- a/briar-android/src/test/java/org/briarproject/briar/android/attachment/AttachmentRetrieverTest.java +++ b/briar-android/src/test/java/org/briarproject/briar/android/attachment/AttachmentRetrieverTest.java @@ -2,11 +2,11 @@ package org.briarproject.briar.android.attachment; import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.test.BrambleMockTestCase; -import org.briarproject.briar.android.attachment.ImageHelper.DecodeResult; import org.briarproject.briar.api.messaging.Attachment; import org.briarproject.briar.api.messaging.AttachmentHeader; import org.briarproject.briar.api.messaging.MessagingManager; import org.jmock.Expectations; +import org.jmock.lib.legacy.ClassImposteriser; import org.junit.Test; import java.io.ByteArrayInputStream; @@ -24,14 +24,18 @@ public class AttachmentRetrieverTest extends BrambleMockTestCase { 100, 50, 200, 75, 300 ); private final MessageId msgId = new MessageId(getRandomId()); - private final MessagingManager messagingManager = - context.mock(MessagingManager.class); private final ImageHelper imageHelper = context.mock(ImageHelper.class); - private final AttachmentRetriever retriever = new AttachmentRetriever( - messagingManager, - dimensions, - imageHelper - ); + private final ImageSizeCalculator imageSizeCalculator; + private final AttachmentRetriever retriever; + + public AttachmentRetrieverTest() { + context.setImposteriser(ClassImposteriser.INSTANCE); + MessagingManager messagingManager = + context.mock(MessagingManager.class); + imageSizeCalculator = context.mock(ImageSizeCalculator.class); + retriever = new AttachmentRetrieverImpl(messagingManager, dimensions, + imageHelper, imageSizeCalculator); + } @Test public void testNoSize() { @@ -69,8 +73,9 @@ public class AttachmentRetrieverTest extends BrambleMockTestCase { Attachment attachment = getAttachment(mimeType); context.checking(new Expectations() {{ - oneOf(imageHelper).decodeStream(with(any(InputStream.class))); - will(returnValue(new DecodeResult(160, 240, mimeType))); + oneOf(imageSizeCalculator).getSize(with(any(InputStream.class)), + with(mimeType)); + will(returnValue(new Size(160, 240, mimeType))); oneOf(imageHelper).getExtensionFromMimeType(mimeType); will(returnValue("jpg")); }}); @@ -92,8 +97,9 @@ public class AttachmentRetrieverTest extends BrambleMockTestCase { Attachment attachment = getAttachment(mimeType); context.checking(new Expectations() {{ - oneOf(imageHelper).decodeStream(with(any(InputStream.class))); - will(returnValue(new DecodeResult(1728, 2592, mimeType))); + oneOf(imageSizeCalculator).getSize(with(any(InputStream.class)), + with(mimeType)); + will(returnValue(new Size(1728, 2592, mimeType))); oneOf(imageHelper).getExtensionFromMimeType(mimeType); will(returnValue("jpg")); }}); @@ -108,11 +114,13 @@ public class AttachmentRetrieverTest extends BrambleMockTestCase { @Test public void testMalformedError() { - Attachment attachment = getAttachment("image/jpeg"); + String mimeType = "image/jpeg"; + Attachment attachment = getAttachment(mimeType); context.checking(new Expectations() {{ - oneOf(imageHelper).decodeStream(with(any(InputStream.class))); - will(returnValue(new DecodeResult(0, 0, ""))); + oneOf(imageSizeCalculator).getSize(with(any(InputStream.class)), + with(mimeType)); + will(returnValue(new Size())); oneOf(imageHelper).getExtensionFromMimeType(""); will(returnValue(null)); }});