diff --git a/briar-android/src/androidTestOfficial/java/org/briarproject/briar/android/conversation/AttachmentControllerIntegrationTest.java b/briar-android/src/androidTestOfficial/java/org/briarproject/briar/android/attachment/AttachmentRetrieverIntegrationTest.java similarity index 89% rename from briar-android/src/androidTestOfficial/java/org/briarproject/briar/android/conversation/AttachmentControllerIntegrationTest.java rename to briar-android/src/androidTestOfficial/java/org/briarproject/briar/android/attachment/AttachmentRetrieverIntegrationTest.java index d639230b8..eea2427f3 100644 --- a/briar-android/src/androidTestOfficial/java/org/briarproject/briar/android/conversation/AttachmentControllerIntegrationTest.java +++ b/briar-android/src/androidTestOfficial/java/org/briarproject/briar/android/attachment/AttachmentRetrieverIntegrationTest.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.android.conversation; +package org.briarproject.briar.android.attachment; import android.content.res.AssetManager; import android.support.test.InstrumentationRegistry; @@ -21,7 +21,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @RunWith(AndroidJUnit4.class) -public class AttachmentControllerIntegrationTest { +public class AttachmentRetrieverIntegrationTest { private static final String smallKitten = "https://upload.wikimedia.org/wikipedia/commons/thumb/0/06/Kitten_in_Rizal_Park%2C_Manila.jpg/160px-Kitten_in_Rizal_Park%2C_Manila.jpg"; @@ -47,15 +47,15 @@ public class AttachmentControllerIntegrationTest { ); private final MessageId msgId = new MessageId(getRandomId()); - private final AttachmentController controller = - new AttachmentController(null, dimensions); + private final AttachmentRetriever retriever = + new AttachmentRetriever(null, dimensions); @Test public void testSmallJpegImage() throws Exception { AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg"); InputStream is = getUrlInputStream(smallKitten); Attachment a = new Attachment(is); - AttachmentItem item = controller.getAttachmentItem(h, a, true); + AttachmentItem item = retriever.getAttachmentItem(h, a, true); assertEquals(msgId, item.getMessageId()); assertEquals(160, item.getWidth()); assertEquals(240, item.getHeight()); @@ -71,7 +71,7 @@ public class AttachmentControllerIntegrationTest { AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg"); InputStream is = getUrlInputStream(originalKitten); Attachment a = new Attachment(is); - AttachmentItem item = controller.getAttachmentItem(h, a, true); + AttachmentItem item = retriever.getAttachmentItem(h, a, true); assertEquals(msgId, item.getMessageId()); assertEquals(1728, item.getWidth()); assertEquals(2592, item.getHeight()); @@ -87,7 +87,7 @@ public class AttachmentControllerIntegrationTest { AttachmentHeader h = new AttachmentHeader(msgId, "image/png"); InputStream is = getUrlInputStream(pngKitten); Attachment a = new Attachment(is); - AttachmentItem item = controller.getAttachmentItem(h, a, true); + AttachmentItem item = retriever.getAttachmentItem(h, a, true); assertEquals(msgId, item.getMessageId()); assertEquals(737, item.getWidth()); assertEquals(510, item.getHeight()); @@ -103,7 +103,7 @@ public class AttachmentControllerIntegrationTest { AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg"); InputStream is = getUrlInputStream(uberGif); Attachment a = new Attachment(is); - AttachmentItem item = controller.getAttachmentItem(h, a, true); + AttachmentItem item = retriever.getAttachmentItem(h, a, true); assertEquals(1, item.getWidth()); assertEquals(1, item.getHeight()); assertEquals(dimensions.minHeight, item.getThumbnailWidth()); @@ -118,7 +118,7 @@ public class AttachmentControllerIntegrationTest { AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg"); InputStream is = getUrlInputStream(lottaPixel); Attachment a = new Attachment(is); - AttachmentItem item = controller.getAttachmentItem(h, a, true); + AttachmentItem item = retriever.getAttachmentItem(h, a, true); assertEquals(64250, item.getWidth()); assertEquals(64250, item.getHeight()); assertEquals(dimensions.maxWidth, item.getThumbnailWidth()); @@ -133,7 +133,7 @@ public class AttachmentControllerIntegrationTest { AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg"); InputStream is = getUrlInputStream(imageIoCrash); Attachment a = new Attachment(is); - AttachmentItem item = controller.getAttachmentItem(h, a, true); + AttachmentItem item = retriever.getAttachmentItem(h, a, true); assertEquals(1184, item.getWidth()); assertEquals(448, item.getHeight()); assertEquals(dimensions.maxWidth, item.getThumbnailWidth()); @@ -148,7 +148,7 @@ public class AttachmentControllerIntegrationTest { AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg"); InputStream is = getUrlInputStream(gimpCrash); Attachment a = new Attachment(is); - AttachmentItem item = controller.getAttachmentItem(h, a, true); + AttachmentItem item = retriever.getAttachmentItem(h, a, true); assertEquals(1, item.getWidth()); assertEquals(1, item.getHeight()); assertEquals(dimensions.minHeight, item.getThumbnailWidth()); @@ -163,7 +163,7 @@ public class AttachmentControllerIntegrationTest { AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg"); InputStream is = getUrlInputStream(optiPngAfl); Attachment a = new Attachment(is); - AttachmentItem item = controller.getAttachmentItem(h, a, true); + AttachmentItem item = retriever.getAttachmentItem(h, a, true); assertEquals(32, item.getWidth()); assertEquals(32, item.getHeight()); assertEquals(dimensions.minHeight, item.getThumbnailWidth()); @@ -178,7 +178,7 @@ public class AttachmentControllerIntegrationTest { AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg"); InputStream is = getUrlInputStream(librawError); Attachment a = new Attachment(is); - AttachmentItem item = controller.getAttachmentItem(h, a, true); + AttachmentItem item = retriever.getAttachmentItem(h, a, true); assertTrue(item.hasError()); } @@ -187,7 +187,7 @@ public class AttachmentControllerIntegrationTest { AttachmentHeader h = new AttachmentHeader(msgId, "image/gif"); InputStream is = getAssetInputStream("animated.gif"); Attachment a = new Attachment(is); - AttachmentItem item = controller.getAttachmentItem(h, a, true); + AttachmentItem item = retriever.getAttachmentItem(h, a, true); assertEquals(65535, item.getWidth()); assertEquals(65535, item.getHeight()); assertEquals(dimensions.maxWidth, item.getThumbnailWidth()); @@ -202,7 +202,7 @@ public class AttachmentControllerIntegrationTest { AttachmentHeader h = new AttachmentHeader(msgId, "image/gif"); InputStream is = getAssetInputStream("animated2.gif"); Attachment a = new Attachment(is); - AttachmentItem item = controller.getAttachmentItem(h, a, true); + AttachmentItem item = retriever.getAttachmentItem(h, a, true); assertEquals(10000, item.getWidth()); assertEquals(10000, item.getHeight()); assertEquals(dimensions.maxWidth, item.getThumbnailWidth()); @@ -217,7 +217,7 @@ public class AttachmentControllerIntegrationTest { AttachmentHeader h = new AttachmentHeader(msgId, "image/gif"); InputStream is = getAssetInputStream("error_large.gif"); Attachment a = new Attachment(is); - AttachmentItem item = controller.getAttachmentItem(h, a, true); + AttachmentItem item = retriever.getAttachmentItem(h, a, true); assertEquals(16384, item.getWidth()); assertEquals(16384, item.getHeight()); assertEquals(dimensions.maxWidth, item.getThumbnailWidth()); @@ -232,7 +232,7 @@ public class AttachmentControllerIntegrationTest { AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg"); InputStream is = getAssetInputStream("error_high.jpg"); Attachment a = new Attachment(is); - AttachmentItem item = controller.getAttachmentItem(h, a, true); + AttachmentItem item = retriever.getAttachmentItem(h, a, true); assertEquals(1, item.getWidth()); assertEquals(10000, item.getHeight()); assertEquals(dimensions.minWidth, item.getThumbnailWidth()); @@ -247,7 +247,7 @@ public class AttachmentControllerIntegrationTest { AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg"); InputStream is = getAssetInputStream("error_wide.jpg"); Attachment a = new Attachment(is); - AttachmentItem item = controller.getAttachmentItem(h, a, true); + AttachmentItem item = retriever.getAttachmentItem(h, a, true); assertEquals(1920, item.getWidth()); assertEquals(1, item.getHeight()); assertEquals(dimensions.maxWidth, item.getThumbnailWidth()); 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 new file mode 100644 index 000000000..3e4e64767 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentCreationTask.java @@ -0,0 +1,114 @@ +package org.briarproject.briar.android.attachment; + +import android.content.ContentResolver; +import android.net.Uri; +import android.support.annotation.Nullable; + +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.briar.api.messaging.AttachmentHeader; +import org.briarproject.briar.api.messaging.MessagingManager; +import org.jsoup.UnsupportedMimeTypeException; + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +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.IoUtils.tryToClose; +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; + +@NotNullByDefault +class AttachmentCreationTask { + + private static Logger LOG = + getLogger(AttachmentCreationTask.class.getName()); + + private final MessagingManager messagingManager; + private final ContentResolver contentResolver; + private final GroupId groupId; + private final List uris; + private final boolean needsSize; + @Nullable + private AttachmentCreator attachmentCreator; + + private volatile boolean canceled = false; + + AttachmentCreationTask(MessagingManager messagingManager, + ContentResolver contentResolver, + AttachmentCreator attachmentCreator, GroupId groupId, + List uris, boolean needsSize) { + this.messagingManager = messagingManager; + this.contentResolver = contentResolver; + this.groupId = groupId; + this.uris = uris; + this.needsSize = needsSize; + this.attachmentCreator = attachmentCreator; + } + + public void cancel() { + canceled = true; + attachmentCreator = null; + } + + @IoExecutor + public void storeAttachments() { + for (Uri uri: uris) processUri(uri); + if (!canceled && attachmentCreator != null) + attachmentCreator.onAttachmentCreationFinished(); + attachmentCreator = null; + } + + @IoExecutor + private void processUri(Uri uri) { + if (canceled) return; + try { + AttachmentHeader h = storeAttachment(uri); + if (attachmentCreator != null) { + attachmentCreator + .onAttachmentHeaderReceived(uri, h, needsSize); + } + } catch (DbException | IOException e) { + logException(LOG, WARNING, e); + if (attachmentCreator != null) { + attachmentCreator.onAttachmentError(uri, e); + canceled = true; + } + } + } + + @IoExecutor + private AttachmentHeader storeAttachment(Uri uri) + throws IOException, DbException { + long start = now(); + String contentType = contentResolver.getType(uri); + if (contentType == null) throw new IOException("null content type"); + if (!isValidMimeType(contentType)) + throw new UnsupportedMimeTypeException("", contentType, + uri.toString()); + InputStream is = contentResolver.openInputStream(uri); + if (is == null) throw new IOException(); + long timestamp = System.currentTimeMillis(); + AttachmentHeader h = messagingManager + .addLocalAttachment(groupId, timestamp, contentType, is); + tryToClose(is, LOG, WARNING); + logDuration(LOG, "Storing attachment", start); + return h; + } + + private boolean isValidMimeType(@Nullable String mimeType) { + if (mimeType == null) return false; + for (String supportedType : IMAGE_MIME_TYPES) { + if (supportedType.equals(mimeType)) return true; + } + return false; + } + +} 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 new file mode 100644 index 000000000..32f12c2fc --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentCreator.java @@ -0,0 +1,206 @@ +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.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; +import java.util.logging.Logger; + +import static java.util.Objects.requireNonNull; +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.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 controller; + + private final Map unsentItems = + new ConcurrentHashMap<>(); + private final Map> + liveDataResult = new ConcurrentHashMap<>(); + + @Nullable + private MutableLiveData liveDataFinished = null; + @Nullable + private AttachmentCreationTask task; + + public AttachmentCreator(Application app, @IoExecutor Executor ioExecutor, + MessagingManager messagingManager, + AttachmentRetriever controller) { + this.app = app; + this.ioExecutor = ioExecutor; + this.messagingManager = messagingManager; + this.controller = controller; + } + + @UiThread + public AttachmentResult storeAttachments(GroupId groupId, + Collection uris) { + if (task != null && !isStoring()) throw new AssertionError(); + List> itemResults = new ArrayList<>(); + List urisToStore = new ArrayList<>(); + for (Uri uri : uris) { + MutableLiveData liveData = + new MutableLiveData<>(); + itemResults.add(liveData); + liveDataResult.put(uri, liveData); + if (unsentItems.containsKey(uri)) { + // This can happen due to configuration changes. + // So don't create a new attachment, if we have one already. + AttachmentItem item = requireNonNull(unsentItems.get(uri)); + AttachmentItemResult result = + new AttachmentItemResult(uri, item); + liveData.setValue(result); + } else { + urisToStore.add(uri); + } + } + boolean needsSize = uris.size() == 1; + task = new AttachmentCreationTask(messagingManager, + app.getContentResolver(), this, groupId, urisToStore, + needsSize); + ioExecutor.execute(() -> task.storeAttachments()); + liveDataFinished = new MutableLiveData<>(); + return new AttachmentResult(itemResults, liveDataFinished); + } + + @IoExecutor + void onAttachmentHeaderReceived(Uri uri, AttachmentHeader h, + boolean needsSize) { + // get and cache AttachmentItem for ImagePreview + try { + Attachment a = controller.getMessageAttachment(h); + AttachmentItem item = controller.getAttachmentItem(h, a, needsSize); + if (item.hasError()) throw new IOException(); + unsentItems.put(uri, item); + MutableLiveData result = + liveDataResult.get(uri); + if (result != null) { // might have been cleared on UiThread + result.postValue(new AttachmentItemResult(uri, item)); + } + } catch (IOException | DbException e) { + logException(LOG, WARNING, e); + onAttachmentError(uri, e); + } + } + + @IoExecutor + void onAttachmentError(Uri uri, Throwable t) { + 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 + } + MutableLiveData result = liveDataResult.get(uri); + if (result != null) + result.postValue(new AttachmentItemResult(errorMsg)); + // expect to receive a cancel from the UI + } + + @IoExecutor + void onAttachmentCreationFinished() { + if (liveDataFinished != null) liveDataFinished.postValue(true); + } + + @UiThread + public List getAttachmentHeadersForSending() { + List headers = + new ArrayList<>(unsentItems.values().size()); + for (AttachmentItem item : unsentItems.values()) { + headers.add(item.getHeader()); + } + return headers; + } + + /** + * Marks the attachments as sent and adds the items to the cache for display + * + * @param id The MessageId of the sent message. + */ + public void onAttachmentsSent(MessageId id) { + controller.cachePut(id, new ArrayList<>(unsentItems.values())); + resetState(); + } + + @UiThread + public void cancel() { + if (task == null) throw new AssertionError(); + task.cancel(); + // let observers know that they can remove themselves + for (MutableLiveData liveData : liveDataResult + .values()) { + if (liveData.getValue() == null) { + liveData.setValue(null); + } + } + if (liveDataFinished != null) liveDataFinished.setValue(false); + deleteUnsentAttachments(); + resetState(); + } + + @UiThread + private void resetState() { + task = null; + liveDataResult.clear(); + liveDataFinished = null; + unsentItems.clear(); + } + + @UiThread + public void deleteUnsentAttachments() { + List itemsToDelete = + new ArrayList<>(unsentItems.values()); + ioExecutor.execute(() -> { + for (AttachmentItem item : itemsToDelete) { + try { + messagingManager.removeAttachment(item.getHeader()); + } catch (DbException e) { + logException(LOG, WARNING, e); + } + } + }); + } + + private boolean isStoring() { + return liveDataFinished != null; + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/AttachmentDimensions.java b/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentDimensions.java similarity index 75% rename from briar-android/src/main/java/org/briarproject/briar/android/conversation/AttachmentDimensions.java rename to briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentDimensions.java index 081a6480b..9c60101ee 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/AttachmentDimensions.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentDimensions.java @@ -1,11 +1,16 @@ -package org.briarproject.briar.android.conversation; +package org.briarproject.briar.android.attachment; import android.content.res.Resources; import android.support.annotation.VisibleForTesting; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.briar.R; -class AttachmentDimensions { +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault +public class AttachmentDimensions { final int defaultSize; final int minWidth, maxWidth; @@ -21,7 +26,7 @@ class AttachmentDimensions { this.maxHeight = maxHeight; } - static AttachmentDimensions getAttachmentDimensions(Resources res) { + public static AttachmentDimensions getAttachmentDimensions(Resources res) { int defaultSize = res.getDimensionPixelSize(R.dimen.message_bubble_image_default); int minWidth = res.getDimensionPixelSize( @@ -33,7 +38,7 @@ class AttachmentDimensions { int maxHeight = res.getDimensionPixelSize( R.dimen.message_bubble_image_max_height); return new AttachmentDimensions(defaultSize, minWidth, maxWidth, - minHeight, minHeight); + minHeight, maxHeight); } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/AttachmentItem.java b/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentItem.java similarity index 87% rename from briar-android/src/main/java/org/briarproject/briar/android/conversation/AttachmentItem.java rename to briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentItem.java index 598336daa..8caafac7c 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/AttachmentItem.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentItem.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.android.conversation; +package org.briarproject.briar.android.attachment; import android.os.Parcel; import android.os.Parcelable; @@ -12,6 +12,8 @@ import java.util.concurrent.atomic.AtomicLong; import javax.annotation.concurrent.Immutable; +import static java.util.Objects.requireNonNull; + @Immutable @NotNullByDefault public class AttachmentItem implements Parcelable { @@ -57,8 +59,8 @@ public class AttachmentItem implements Parcelable { MessageId messageId = new MessageId(messageIdByte); width = in.readInt(); height = in.readInt(); - String mimeType = in.readString(); - extension = in.readString(); + String mimeType = requireNonNull(in.readString()); + extension = requireNonNull(in.readString()); thumbnailWidth = in.readInt(); thumbnailHeight = in.readInt(); hasError = in.readByte() != 0; @@ -82,27 +84,27 @@ public class AttachmentItem implements Parcelable { return height; } - String getMimeType() { + public String getMimeType() { return header.getContentType(); } - String getExtension() { + public String getExtension() { return extension; } - int getThumbnailWidth() { + public int getThumbnailWidth() { return thumbnailWidth; } - int getThumbnailHeight() { + public int getThumbnailHeight() { return thumbnailHeight; } - boolean hasError() { + public boolean hasError() { return hasError; } - String getTransitionName() { + public String getTransitionName() { return String.valueOf(instanceId); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/AttachmentResult.java b/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentItemResult.java similarity index 77% rename from briar-android/src/main/java/org/briarproject/briar/android/conversation/AttachmentResult.java rename to briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentItemResult.java index 2a84252a2..1c5cc8dd3 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/AttachmentResult.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentItemResult.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.android.conversation; +package org.briarproject.briar.android.attachment; import android.net.Uri; @@ -9,7 +9,7 @@ import javax.annotation.concurrent.Immutable; @Immutable @NotNullByDefault -public class AttachmentResult { +public class AttachmentItemResult { @Nullable private final Uri uri; @@ -18,13 +18,13 @@ public class AttachmentResult { @Nullable private final String errorMsg; - public AttachmentResult(Uri uri, AttachmentItem item) { + public AttachmentItemResult(Uri uri, AttachmentItem item) { this.uri = uri; this.item = item; this.errorMsg = null; } - public AttachmentResult(@Nullable String errorMsg) { + public AttachmentItemResult(@Nullable String errorMsg) { this.uri = null; this.item = null; this.errorMsg = errorMsg; 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 new file mode 100644 index 000000000..8b5709d5c --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentManager.java @@ -0,0 +1,20 @@ +package org.briarproject.briar.android.attachment; + +import android.net.Uri; +import android.support.annotation.UiThread; + +import org.briarproject.briar.api.messaging.AttachmentHeader; + +import java.util.Collection; +import java.util.List; + +@UiThread +public interface AttachmentManager{ + + AttachmentResult storeAttachments(Collection uri); + + List getAttachmentHeadersForSending(); + + void cancel(); + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentResult.java b/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentResult.java new file mode 100644 index 000000000..4e347b6fb --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentResult.java @@ -0,0 +1,33 @@ +package org.briarproject.briar.android.attachment; + +import android.arch.lifecycle.LiveData; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import java.util.Collection; + +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault +public class AttachmentResult { + + private final Collection> itemResults; + private final LiveData finished; + + public AttachmentResult( + Collection> itemResults, + LiveData finished) { + this.itemResults = itemResults; + this.finished = finished; + } + + public Collection> getItemResults() { + return itemResults; + } + + public LiveData getFinished() { + return finished; + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/AttachmentController.java b/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentRetriever.java similarity index 71% rename from briar-android/src/main/java/org/briarproject/briar/android/conversation/AttachmentController.java rename to briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentRetriever.java index f7479beb7..d3fb1188b 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/AttachmentController.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentRetriever.java @@ -1,9 +1,7 @@ -package org.briarproject.briar.android.conversation; +package org.briarproject.briar.android.attachment; -import android.content.ContentResolver; import android.graphics.BitmapFactory; import android.graphics.BitmapFactory.Options; -import android.net.Uri; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; import android.support.media.ExifInterface; @@ -15,9 +13,8 @@ import org.briarproject.bramble.api.Pair; import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DbException; 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.android.conversation.ImageHelper.DecodeResult; +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; @@ -38,20 +35,18 @@ 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.Objects.requireNonNull; 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.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; @NotNullByDefault -class AttachmentController { +public class AttachmentRetriever { private static final Logger LOG = - getLogger(AttachmentController.class.getName()); + getLogger(AttachmentRetriever.class.getName()); private static final int READ_LIMIT = 1024 * 8192; private final MessagingManager messagingManager; @@ -60,12 +55,11 @@ class AttachmentController { private final int minWidth, maxWidth; private final int minHeight, maxHeight; - private final Map unsentItems = - new ConcurrentHashMap<>(); private final Map> attachmentCache = new ConcurrentHashMap<>(); - AttachmentController(MessagingManager messagingManager, + @VisibleForTesting + AttachmentRetriever(MessagingManager messagingManager, AttachmentDimensions dimensions, ImageHelper imageHelper) { this.messagingManager = messagingManager; this.imageHelper = imageHelper; @@ -76,7 +70,7 @@ class AttachmentController { maxHeight = dimensions.maxHeight; } - AttachmentController(MessagingManager messagingManager, + public AttachmentRetriever(MessagingManager messagingManager, AttachmentDimensions dimensions) { this(messagingManager, dimensions, new ImageHelper() { @Override @@ -99,17 +93,17 @@ class AttachmentController { }); } - void put(MessageId messageId, List attachments) { + public void cachePut(MessageId messageId, List attachments) { attachmentCache.put(messageId, attachments); } @Nullable - List get(MessageId messageId) { + public List cacheGet(MessageId messageId) { return attachmentCache.get(messageId); } @DatabaseExecutor - List> getMessageAttachments( + public List> getMessageAttachments( List headers) throws DbException { long start = now(); List> attachments = @@ -118,86 +112,13 @@ class AttachmentController { Attachment a = messagingManager.getAttachment(h.getMessageId()); attachments.add(new Pair<>(h, a)); } - logDuration(LOG, "Loading attachment", start); + logDuration(LOG, "Loading attachments", start); return attachments; } @DatabaseExecutor - AttachmentItem createAttachmentHeader(ContentResolver contentResolver, - GroupId groupId, Uri uri, boolean needsSize) - throws IOException, DbException { - if (unsentItems.containsKey(uri)) { - // This can happen due to configuration (screen orientation) change. - // So don't create a new attachment, if we have one already. - return requireNonNull(unsentItems.get(uri)); - } - long start = now(); - InputStream is = contentResolver.openInputStream(uri); - if (is == null) throw new IOException(); - String contentType = contentResolver.getType(uri); - if (contentType == null) throw new IOException("null content type"); - long timestamp = System.currentTimeMillis(); - AttachmentHeader h = messagingManager - .addLocalAttachment(groupId, timestamp, contentType, is); - tryToClose(is, LOG, WARNING); - logDuration(LOG, "Storing attachment", start); - // get and store AttachmentItem for ImagePreview - AttachmentItem item = - getAttachmentItem(contentResolver, uri, h, needsSize); - if (item.hasError()) throw new IOException(); - unsentItems.put(uri, item); - return item; - } - - boolean isValidMimeType(@Nullable String mimeType) { - if (mimeType == null) return false; - for (String supportedType : IMAGE_MIME_TYPES) { - if (supportedType.equals(mimeType)) return true; - } - return false; - } - - @DatabaseExecutor - void deleteUnsentAttachments() { - for (AttachmentItem item : unsentItems.values()) { - try { - messagingManager.removeAttachment(item.getHeader()); - } catch (DbException e) { - logException(LOG, WARNING, e); - } - } - unsentItems.clear(); - } - - List getUnsentAttachmentHeaders() { - List headers = - new ArrayList<>(unsentItems.values().size()); - for (AttachmentItem item : unsentItems.values()) { - headers.add(item.getHeader()); - } - return headers; - } - - /** - * Marks the attachments as sent and adds the items to the cache for display - * @param id The MessageId of the sent message. - */ - void onAttachmentsSent(MessageId id) { - attachmentCache.put(id, new ArrayList<>(unsentItems.values())); - unsentItems.clear(); - } - - @DatabaseExecutor - private AttachmentItem getAttachmentItem(ContentResolver contentResolver, - Uri uri, AttachmentHeader h, boolean needsSize) throws IOException { - InputStream is = null; - try { - is = contentResolver.openInputStream(uri); - if (is == null) throw new IOException(); - return getAttachmentItem(h, new Attachment(is), needsSize); - } finally { - if (is != null) tryToClose(is, LOG, WARNING); - } + Attachment getMessageAttachment(AttachmentHeader h) throws DbException { + return messagingManager.getAttachment(h.getMessageId()); } /** @@ -205,7 +126,7 @@ class AttachmentController { *

* Note: This closes the {@link Attachment}'s {@link InputStream}. */ - List getAttachmentItems( + public List getAttachmentItems( List> attachments) { boolean needsSize = attachments.size() == 1; List items = new ArrayList<>(attachments.size()); @@ -221,7 +142,6 @@ class AttachmentController { * Creates an {@link AttachmentItem} from the {@link Attachment}'s * {@link InputStream} which will be closed when this method returns. */ - @VisibleForTesting AttachmentItem getAttachmentItem(AttachmentHeader h, Attachment a, boolean needsSize) { if (!needsSize) { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageHelper.java b/briar-android/src/main/java/org/briarproject/briar/android/attachment/ImageHelper.java similarity index 90% rename from briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageHelper.java rename to briar-android/src/main/java/org/briarproject/briar/android/attachment/ImageHelper.java index 203a45222..1264d6511 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageHelper.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/attachment/ImageHelper.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.android.conversation; +package org.briarproject.briar.android.attachment; import android.support.annotation.Nullable; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java index fac65e937..925655815 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java @@ -52,6 +52,8 @@ import org.briarproject.bramble.api.sync.event.MessagesSentEvent; import org.briarproject.briar.R; import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.BriarActivity; +import org.briarproject.briar.android.attachment.AttachmentItem; +import org.briarproject.briar.android.attachment.AttachmentRetriever; import org.briarproject.briar.android.blog.BlogActivity; import org.briarproject.briar.android.conversation.ConversationVisitor.AttachmentCache; import org.briarproject.briar.android.conversation.ConversationVisitor.TextCache; @@ -183,7 +185,7 @@ public class ConversationActivity extends BriarActivity loadMessages(); }; - private AttachmentController attachmentController; + private AttachmentRetriever attachmentRetriever; private ConversationViewModel viewModel; private ConversationVisitor visitor; private ConversationAdapter adapter; @@ -218,7 +220,7 @@ public class ConversationActivity extends BriarActivity viewModel = ViewModelProviders.of(this, viewModelFactory) .get(ConversationViewModel.class); - attachmentController = viewModel.getAttachmentController(); + attachmentRetriever = viewModel.getAttachmentRetriever(); setContentView(R.layout.activity_conversation); @@ -456,13 +458,13 @@ public class ConversationActivity extends BriarActivity // If the message has a single image, load its size - for multiple // images we use a grid so the size is fixed if (h.getAttachmentHeaders().size() == 1) { - List items = attachmentController.get(id); + List items = attachmentRetriever.cacheGet(id); if (items == null) { LOG.info("Eagerly loading image size for latest message"); - items = attachmentController.getAttachmentItems( - attachmentController.getMessageAttachments( + items = attachmentRetriever.getAttachmentItems( + attachmentRetriever.getMessageAttachments( h.getAttachmentHeaders())); - attachmentController.put(id, items); + attachmentRetriever.cachePut(id, items); } } } @@ -544,10 +546,10 @@ public class ConversationActivity extends BriarActivity runOnDbThread(() -> { try { List> attachments = - attachmentController.getMessageAttachments(headers); + attachmentRetriever.getMessageAttachments(headers); // TODO move getting the items off to IoExecutor, if size == 1 List items = - attachmentController.getAttachmentItems(attachments); + attachmentRetriever.getAttachmentItems(attachments); displayMessageAttachments(messageId, items); } catch (DbException e) { logException(LOG, WARNING, e); @@ -558,7 +560,7 @@ public class ConversationActivity extends BriarActivity private void displayMessageAttachments(MessageId m, List items) { runOnUiThreadUnlessDestroyed(() -> { - attachmentController.put(m, items); + attachmentRetriever.cachePut(m, items); Pair pair = adapter.getMessageItem(m); if (pair != null) { @@ -903,7 +905,7 @@ public class ConversationActivity extends BriarActivity @Override public List getAttachmentItems(MessageId m, List headers) { - List attachments = attachmentController.get(m); + List attachments = attachmentRetriever.cacheGet(m); if (attachments == null) { loadMessageAttachments(m, headers); return emptyList(); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationListener.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationListener.java index d7c445fae..d961ff1e2 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationListener.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationListener.java @@ -4,6 +4,7 @@ import android.support.annotation.UiThread; import android.view.View; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.briar.android.attachment.AttachmentItem; @UiThread @NotNullByDefault diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationMessageItem.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationMessageItem.java index 1732e3ab8..6b853932a 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationMessageItem.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationMessageItem.java @@ -3,6 +3,7 @@ package org.briarproject.briar.android.conversation; import android.support.annotation.LayoutRes; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.briar.android.attachment.AttachmentItem; import org.briarproject.briar.api.messaging.PrivateMessageHeader; import java.util.List; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationMessageViewHolder.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationMessageViewHolder.java index 94c80639a..6524129b4 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationMessageViewHolder.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationMessageViewHolder.java @@ -9,6 +9,7 @@ import android.view.ViewGroup; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.briar.R; +import org.briarproject.briar.android.attachment.AttachmentItem; import static android.support.constraint.ConstraintSet.WRAP_CONTENT; import static android.support.v4.content.ContextCompat.getColor; 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 663e94e98..aa8718c0f 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 @@ -5,7 +5,6 @@ import android.arch.lifecycle.AndroidViewModel; import android.arch.lifecycle.LiveData; import android.arch.lifecycle.MutableLiveData; import android.arch.lifecycle.Transformations; -import android.content.ContentResolver; import android.net.Uri; import android.support.annotation.Nullable; import android.support.annotation.UiThread; @@ -20,26 +19,26 @@ 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; import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.MessageId; -import org.briarproject.bramble.api.system.AndroidExecutor; -import org.briarproject.briar.R; +import org.briarproject.briar.android.attachment.AttachmentCreator; +import org.briarproject.briar.android.attachment.AttachmentManager; +import org.briarproject.briar.android.attachment.AttachmentResult; +import org.briarproject.briar.android.attachment.AttachmentRetriever; import org.briarproject.briar.android.util.UiUtils; -import org.briarproject.briar.android.view.TextAttachmentController.AttachmentManager; import org.briarproject.briar.android.viewmodel.LiveEvent; import org.briarproject.briar.android.viewmodel.MutableLiveEvent; import org.briarproject.briar.api.messaging.AttachmentHeader; -import org.briarproject.briar.api.messaging.FileTooBigException; import org.briarproject.briar.api.messaging.MessagingManager; import org.briarproject.briar.api.messaging.PrivateMessage; import org.briarproject.briar.api.messaging.PrivateMessageFactory; import org.briarproject.briar.api.messaging.PrivateMessageHeader; -import java.io.IOException; import java.util.Collection; import java.util.List; import java.util.concurrent.Executor; @@ -53,14 +52,12 @@ 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.conversation.AttachmentDimensions.getAttachmentDimensions; +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; -import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_IMAGE_SIZE; @NotNullByDefault -public class ConversationViewModel extends AndroidViewModel implements - AttachmentManager { +public class ConversationViewModel extends AndroidViewModel + implements AttachmentManager { private static Logger LOG = getLogger(ConversationViewModel.class.getName()); @@ -78,10 +75,13 @@ public class ConversationViewModel extends AndroidViewModel implements private final ContactManager contactManager; private final SettingsManager settingsManager; private final PrivateMessageFactory privateMessageFactory; - private final AttachmentController attachmentController; + private final AttachmentRetriever attachmentRetriever; + private final AttachmentCreator attachmentCreator; @Nullable private ContactId contactId = null; + @Nullable + private volatile GroupId messagingGroupId = null; private final MutableLiveData contact = new MutableLiveData<>(); private final LiveData contactAuthorId = Transformations.map(contact, c -> c.getAuthor().getId()); @@ -97,15 +97,14 @@ public class ConversationViewModel extends AndroidViewModel implements new MutableLiveData<>(); private final MutableLiveData contactDeleted = new MutableLiveData<>(); - private final MutableLiveData messagingGroupId = - new MutableLiveData<>(); private final MutableLiveData addedHeader = new MutableLiveData<>(); @Inject ConversationViewModel(Application application, @DatabaseExecutor Executor dbExecutor, - @CryptoExecutor Executor cryptoExecutor, TransactionManager db, + @CryptoExecutor Executor cryptoExecutor, + @IoExecutor Executor ioExecutor, TransactionManager db, MessagingManager messagingManager, ContactManager contactManager, SettingsManager settingsManager, PrivateMessageFactory privateMessageFactory) { @@ -117,15 +116,17 @@ public class ConversationViewModel extends AndroidViewModel implements this.contactManager = contactManager; this.settingsManager = settingsManager; this.privateMessageFactory = privateMessageFactory; - this.attachmentController = new AttachmentController(messagingManager, + this.attachmentRetriever = new AttachmentRetriever(messagingManager, getAttachmentDimensions(application.getResources())); + this.attachmentCreator = new AttachmentCreator(getApplication(), + ioExecutor, messagingManager, attachmentRetriever); contactDeleted.setValue(false); } @Override protected void onCleared() { super.onCleared(); - attachmentController.deleteUnsentAttachments(); + attachmentCreator.deleteUnsentAttachments(); } /** @@ -149,6 +150,10 @@ public class ConversationViewModel extends AndroidViewModel implements contact.postValue(c); logDuration(LOG, "Loading contact", start); start = now(); + messagingGroupId = + messagingManager.getConversationId(contactId); + logDuration(LOG, "Load conversation GroupId", start); + start = now(); checkFeaturesAndOnboarding(contactId); logDuration(LOG, "Checking for image support", start); } catch (NoSuchContactException e) { @@ -185,73 +190,29 @@ public class ConversationViewModel extends AndroidViewModel implements void sendMessage(@Nullable String text, List attachmentHeaders, long timestamp) { - if (messagingGroupId.getValue() == null) loadGroupId(); - observeForeverOnce(messagingGroupId, groupId -> { - if (groupId == null) return; - createMessage(groupId, text, attachmentHeaders, timestamp); - }); + GroupId groupId = messagingGroupId; + if (groupId == null) throw new IllegalStateException(); + createMessage(groupId, text, attachmentHeaders, timestamp); } @Override - public LiveData storeAttachment(Uri uri, - boolean needsSize) { - // use LiveData to not keep references to view scope - MutableLiveData result = new MutableLiveData<>(); - // check first if mime type is supported - ContentResolver contentResolver = - getApplication().getContentResolver(); - String mimeType = contentResolver.getType(uri); - if (!attachmentController.isValidMimeType(mimeType)) { - String errorMsg = getApplication().getString( - R.string.image_attach_error_invalid_mime_type, mimeType); - result.setValue(new AttachmentResult(errorMsg)); - return result; - } - if (messagingGroupId.getValue() == null) loadGroupId(); - observeForeverOnce(messagingGroupId, groupId -> dbExecutor.execute(() - -> { - if (groupId == null) throw new IllegalStateException(); - long start = now(); - try { - AttachmentItem item = attachmentController - .createAttachmentHeader(contentResolver, groupId, uri, - needsSize); - result.postValue(new AttachmentResult(uri, item)); - } catch(FileTooBigException e) { - logException(LOG, WARNING, e); - int mb = MAX_IMAGE_SIZE / 1024 / 1024; - String errorMsg = getApplication() - .getString(R.string.image_attach_error_too_big, mb); - result.postValue(new AttachmentResult(errorMsg)); - } catch (DbException | IOException e) { - logException(LOG, WARNING, e); - result.postValue(new AttachmentResult(null)); - } - logDuration(LOG, "Storing attachment", start); - })); - return result; + @UiThread + public AttachmentResult storeAttachments(Collection uris) { + GroupId groupId = messagingGroupId; + if (groupId == null) throw new IllegalStateException(); + return attachmentCreator.storeAttachments(groupId, uris); } @Override - public List getAttachmentHeaders() { - return attachmentController.getUnsentAttachmentHeaders(); + @UiThread + public List getAttachmentHeadersForSending() { + return attachmentCreator.getAttachmentHeadersForSending(); } @Override - public void removeAttachments() { - dbExecutor.execute(attachmentController::deleteUnsentAttachments); - } - - private void loadGroupId() { - if (contactId == null) throw new IllegalStateException(); - dbExecutor.execute(() -> { - try { - messagingGroupId.postValue( - messagingManager.getConversationId(contactId)); - } catch (DbException e) { - logException(LOG, WARNING, e); - } - }); + @UiThread + public void cancel() { + attachmentCreator.cancel(); } @DatabaseExecutor @@ -337,7 +298,7 @@ public class ConversationViewModel extends AndroidViewModel implements message.getId(), message.getGroupId(), message.getTimestamp(), true, true, false, false, text != null, attachments); - attachmentController.onAttachmentsSent(m.getMessage().getId()); + attachmentCreator.onAttachmentsSent(m.getMessage().getId()); // TODO add text to cache when available here addedHeader.postValue(h); } catch (DbException e) { @@ -351,8 +312,8 @@ public class ConversationViewModel extends AndroidViewModel implements addedHeader.setValue(null); } - AttachmentController getAttachmentController() { - return attachmentController; + AttachmentRetriever getAttachmentRetriever() { + return attachmentRetriever; } LiveData getContact() { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationVisitor.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationVisitor.java index 87ec94f07..48f39e666 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationVisitor.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationVisitor.java @@ -7,6 +7,7 @@ import android.support.annotation.UiThread; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.briar.R; +import org.briarproject.briar.android.attachment.AttachmentItem; import org.briarproject.briar.api.blog.BlogInvitationRequest; import org.briarproject.briar.api.blog.BlogInvitationResponse; import org.briarproject.briar.api.conversation.ConversationMessageVisitor; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageActivity.java index aebb3069e..1e1f3bf36 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageActivity.java @@ -31,6 +31,7 @@ import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.briar.R; import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.BriarActivity; +import org.briarproject.briar.android.attachment.AttachmentItem; import org.briarproject.briar.android.util.BriarSnackbarBuilder; import org.briarproject.briar.android.view.PullDownLayout; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageAdapter.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageAdapter.java index 8c9e15d30..f97a5f233 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageAdapter.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageAdapter.java @@ -12,6 +12,7 @@ import android.view.WindowManager; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.briar.R; +import org.briarproject.briar.android.attachment.AttachmentItem; import org.briarproject.briar.android.conversation.glide.Radii; import java.util.ArrayList; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageFragment.java index 1d1b0d1fd..684e216a8 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageFragment.java @@ -21,6 +21,7 @@ import com.github.chrisbanes.photoview.PhotoView; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.briar.R; import org.briarproject.briar.android.activity.BaseActivity; +import org.briarproject.briar.android.attachment.AttachmentItem; import org.briarproject.briar.android.conversation.glide.GlideApp; import javax.annotation.ParametersAreNonnullByDefault; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageViewHolder.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageViewHolder.java index f76e3c2ec..4019bb4f4 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageViewHolder.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageViewHolder.java @@ -11,6 +11,7 @@ import com.bumptech.glide.load.Transformation; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.briar.R; +import org.briarproject.briar.android.attachment.AttachmentItem; import org.briarproject.briar.android.conversation.glide.BriarImageTransformation; import org.briarproject.briar.android.conversation.glide.GlideApp; import org.briarproject.briar.android.conversation.glide.Radii; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageViewModel.java index c8c9451c9..33f29d3e3 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageViewModel.java @@ -13,6 +13,7 @@ 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.MessageId; +import org.briarproject.briar.android.attachment.AttachmentItem; import org.briarproject.briar.android.viewmodel.LiveEvent; import org.briarproject.briar.android.viewmodel.MutableLiveEvent; import org.briarproject.briar.api.messaging.Attachment; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/glide/BriarDataFetcher.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/glide/BriarDataFetcher.java index ac239f4a4..1394f8035 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/glide/BriarDataFetcher.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/glide/BriarDataFetcher.java @@ -10,7 +10,7 @@ import org.briarproject.bramble.api.db.DatabaseExecutor; 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.conversation.AttachmentItem; +import org.briarproject.briar.android.attachment.AttachmentItem; import org.briarproject.briar.api.messaging.MessagingManager; import java.io.InputStream; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/glide/BriarDataFetcherFactory.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/glide/BriarDataFetcherFactory.java index 87f55a170..69a920ae9 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/glide/BriarDataFetcherFactory.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/glide/BriarDataFetcherFactory.java @@ -2,7 +2,7 @@ package org.briarproject.briar.android.conversation.glide; import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; -import org.briarproject.briar.android.conversation.AttachmentItem; +import org.briarproject.briar.android.attachment.AttachmentItem; import org.briarproject.briar.api.messaging.MessagingManager; import java.util.concurrent.Executor; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/glide/BriarGlideModule.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/glide/BriarGlideModule.java index a1a66ab33..a477f2f44 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/glide/BriarGlideModule.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/glide/BriarGlideModule.java @@ -10,7 +10,7 @@ import com.bumptech.glide.module.AppGlideModule; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.briar.android.BriarApplication; -import org.briarproject.briar.android.conversation.AttachmentItem; +import org.briarproject.briar.android.attachment.AttachmentItem; import java.io.InputStream; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/glide/BriarModelLoader.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/glide/BriarModelLoader.java index b4e132392..dd5008f67 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/glide/BriarModelLoader.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/glide/BriarModelLoader.java @@ -8,7 +8,7 @@ import com.bumptech.glide.signature.ObjectKey; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.briar.android.BriarApplication; -import org.briarproject.briar.android.conversation.AttachmentItem; +import org.briarproject.briar.android.attachment.AttachmentItem; import java.io.InputStream; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/glide/BriarModelLoaderFactory.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/glide/BriarModelLoaderFactory.java index 7d077e50e..07ed62227 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/glide/BriarModelLoaderFactory.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/glide/BriarModelLoaderFactory.java @@ -6,7 +6,7 @@ import com.bumptech.glide.load.model.MultiModelLoaderFactory; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.briar.android.BriarApplication; -import org.briarproject.briar.android.conversation.AttachmentItem; +import org.briarproject.briar.android.attachment.AttachmentItem; import java.io.InputStream; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/view/ImagePreview.java b/briar-android/src/main/java/org/briarproject/briar/android/view/ImagePreview.java index 5c66d87b2..2a0585371 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/view/ImagePreview.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/view/ImagePreview.java @@ -9,7 +9,7 @@ import android.view.LayoutInflater; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.briar.R; -import org.briarproject.briar.android.conversation.AttachmentResult; +import org.briarproject.briar.android.attachment.AttachmentItemResult; import java.util.Collection; @@ -72,7 +72,7 @@ public class ImagePreview extends ConstraintLayout { imageList.setAdapter(adapter); } - void loadPreviewImage(AttachmentResult result) { + void loadPreviewImage(AttachmentItemResult result) { ImagePreviewAdapter adapter = ((ImagePreviewAdapter) imageList.getAdapter()); requireNonNull(adapter).loadItemPreview(result); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/view/ImagePreviewAdapter.java b/briar-android/src/main/java/org/briarproject/briar/android/view/ImagePreviewAdapter.java index 0982e4523..dcbfd9ec3 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/view/ImagePreviewAdapter.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/view/ImagePreviewAdapter.java @@ -8,7 +8,7 @@ import android.view.ViewGroup; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.briar.R; -import org.briarproject.briar.android.conversation.AttachmentResult; +import org.briarproject.briar.android.attachment.AttachmentItemResult; import java.util.ArrayList; import java.util.Collection; @@ -50,7 +50,7 @@ class ImagePreviewAdapter extends Adapter { return items.size(); } - void loadItemPreview(AttachmentResult result) { + void loadItemPreview(AttachmentItemResult result) { ImagePreviewItem newItem = new ImagePreviewItem(requireNonNull(result.getUri())); int pos = items.indexOf(newItem); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/view/ImagePreviewItem.java b/briar-android/src/main/java/org/briarproject/briar/android/view/ImagePreviewItem.java index e9c1d0585..5a4f24648 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/view/ImagePreviewItem.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/view/ImagePreviewItem.java @@ -4,7 +4,7 @@ import android.net.Uri; import android.support.annotation.Nullable; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; -import org.briarproject.briar.android.conversation.AttachmentItem; +import org.briarproject.briar.android.attachment.AttachmentItem; import java.util.ArrayList; import java.util.Collection; @@ -30,10 +30,6 @@ class ImagePreviewItem { return items; } - Uri getUri() { - return uri; - } - public void setItem(AttachmentItem item) { this.item = item; } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/view/ImagePreviewViewHolder.java b/briar-android/src/main/java/org/briarproject/briar/android/view/ImagePreviewViewHolder.java index f3c400238..4fefb2ae5 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/view/ImagePreviewViewHolder.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/view/ImagePreviewViewHolder.java @@ -38,9 +38,9 @@ class ImagePreviewViewHolder extends ViewHolder { } void bind(ImagePreviewItem item) { - if (item.getItem() == null) return; + if (item.getItem() == null) return; // shows progress bar GlideApp.with(imageView) - .load(item.getUri()) + .load(item.getItem()) .diskCacheStrategy(NONE) .error(ERROR_RES) .downsample(FIT_CENTER) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/view/TextAttachmentController.java b/briar-android/src/main/java/org/briarproject/briar/android/view/TextAttachmentController.java index 9774463b2..a6434c865 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/view/TextAttachmentController.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/view/TextAttachmentController.java @@ -3,6 +3,7 @@ package org.briarproject.briar.android.view; import android.app.Activity; import android.arch.lifecycle.LifecycleOwner; import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.Observer; import android.content.ClipData; import android.content.Context; import android.content.Intent; @@ -17,9 +18,10 @@ import android.widget.Toast; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.briar.R; -import org.briarproject.briar.android.conversation.AttachmentResult; +import org.briarproject.briar.android.attachment.AttachmentItemResult; +import org.briarproject.briar.android.attachment.AttachmentManager; +import org.briarproject.briar.android.attachment.AttachmentResult; import org.briarproject.briar.android.view.ImagePreview.ImagePreviewListener; -import org.briarproject.briar.api.messaging.AttachmentHeader; import java.util.ArrayList; import java.util.List; @@ -38,7 +40,6 @@ import static android.support.v4.content.ContextCompat.getColor; import static android.support.v4.view.AbsSavedState.EMPTY_STATE; import static android.view.View.GONE; import static android.widget.Toast.LENGTH_LONG; -import static java.util.Collections.emptyList; import static java.util.Objects.requireNonNull; import static org.briarproject.briar.android.util.UiUtils.resolveColorAttribute; import static org.briarproject.briar.api.messaging.MessagingConstants.IMAGE_MIME_TYPES; @@ -55,9 +56,8 @@ public class TextAttachmentController extends TextSendController private final CompositeSendButton sendButton; private final AttachmentManager attachmentManager; - private CharSequence textHint; - private List imageUris = emptyList(); - private int urisLoaded = 0; + private final List imageUris = new ArrayList<>(); + private final CharSequence textHint; private boolean loadingUris = false; public TextAttachmentController(TextInputView v, ImagePreview imagePreview, @@ -96,7 +96,7 @@ public class TextAttachmentController extends TextSendController if (canSend()) { if (loadingUris) throw new AssertionError(); listener.onSendClick(textInput.getText(), - attachmentManager.getAttachmentHeaders()); + attachmentManager.getAttachmentHeadersForSending()); reset(); } } @@ -140,14 +140,12 @@ public class TextAttachmentController extends TextSendController public void onImageReceived(@Nullable Intent resultData) { if (resultData == null) return; - if (loadingUris) throw new AssertionError(); + if (loadingUris || !imageUris.isEmpty()) throw new AssertionError(); if (resultData.getData() != null) { - imageUris = new ArrayList<>(1); imageUris.add(resultData.getData()); onNewUris(); } else if (SDK_INT >= 18 && resultData.getClipData() != null) { ClipData clipData = resultData.getClipData(); - imageUris = new ArrayList<>(clipData.getItemCount()); for (int i = 0; i < clipData.getItemCount(); i++) { imageUris.add(clipData.getItemAt(i).getUri()); } @@ -157,40 +155,62 @@ public class TextAttachmentController extends TextSendController private void onNewUris() { if (imageUris.isEmpty()) return; - if (loadingUris || urisLoaded != 0) throw new AssertionError(); + if (loadingUris) throw new AssertionError(); loadingUris = true; updateViewState(); textInput.setHint(R.string.image_caption_hint); List items = ImagePreviewItem.fromUris(imageUris); imagePreview.showPreview(items); // store attachments and show preview when successful - boolean needsSize = items.size() == 1; - for (ImagePreviewItem item : items) { - attachmentManager.storeAttachment(item.getUri(), needsSize) - .observe(imageListener, this::onAttachmentResultReceived); + AttachmentResult result = attachmentManager.storeAttachments(imageUris); + for (LiveData liveData : result + .getItemResults()) { + onLiveDataReturned(liveData); } + result.getFinished().observe(imageListener, new Observer() { + @Override + public void onChanged(@Nullable Boolean finished) { + if (finished != null && finished) onAllAttachmentsCreated(); + result.getFinished().removeObserver(this); + } + }); } - private void onAttachmentResultReceived(AttachmentResult result) { - if (!loadingUris) return; // if this is false, the user cancelled + private void onLiveDataReturned(LiveData liveData) { + liveData.observe(imageListener, new Observer() { + @Override + public void onChanged(@Nullable AttachmentItemResult result) { + if (result != null) { + onAttachmentResultReceived(result); + } + liveData.removeObserver(this); + } + }); + } + + private void onAttachmentResultReceived(AttachmentItemResult result) { + if (!loadingUris) throw new AssertionError(); if (result.isError() || result.getUri() == null) { onError(result.getErrorMsg()); } else { imagePreview.loadPreviewImage(result); - urisLoaded++; - checkAllUrisLoaded(); } } + private void onAllAttachmentsCreated() { + if (!loadingUris) throw new AssertionError(); + loadingUris = false; + updateViewState(); + } + private void reset() { // restore hint textInput.setHint(textHint); // hide image layout imagePreview.setVisibility(GONE); // reset image URIs - imageUris = emptyList(); - // no URIs has been loaded - urisLoaded = 0; + imageUris.clear(); + // definitely not loading anymore loadingUris = false; // show the image button again, so images can get attached updateViewState(); @@ -208,7 +228,8 @@ public class TextAttachmentController extends TextSendController @Nullable public Parcelable onRestoreInstanceState(Parcelable inState) { SavedState state = (SavedState) inState; - imageUris = requireNonNull(state.imageUris); + if (!imageUris.isEmpty()) throw new AssertionError(); + if (state.imageUris != null) imageUris.addAll(state.imageUris); onNewUris(); return state.getSuperState(); } @@ -226,19 +247,10 @@ public class TextAttachmentController extends TextSendController @Override public void onCancel() { textInput.clearText(); - attachmentManager.removeAttachments(); + attachmentManager.cancel(); reset(); } - private void checkAllUrisLoaded() { - if (!loadingUris) throw new AssertionError(); - if (urisLoaded == imageUris.size()) { - loadingUris = false; - // all images were turned into attachments - updateViewState(); - } - } - public void showImageOnboarding(Activity activity, Runnable onOnboardingSeen) { PromptStateChangeListener listener = (prompt, state) -> { @@ -297,19 +309,4 @@ public class TextAttachmentController extends TextSendController void onAttachImage(Intent intent); } - public interface AttachmentManager { - /** - * Stores a new attachment in the database. - * - * @param uri The Uri of the attachment to store. - * @param needsSize true if this is the only image in the message - * and therefore needs to know its size. - */ - LiveData storeAttachment(Uri uri, boolean needsSize); - - List getAttachmentHeaders(); - - void removeAttachments(); - } - } diff --git a/briar-android/src/test/java/org/briarproject/briar/android/conversation/AttachmentControllerTest.java b/briar-android/src/test/java/org/briarproject/briar/android/attachment/AttachmentRetrieverTest.java similarity index 93% rename from briar-android/src/test/java/org/briarproject/briar/android/conversation/AttachmentControllerTest.java rename to briar-android/src/test/java/org/briarproject/briar/android/attachment/AttachmentRetrieverTest.java index f5abfc706..f0f38bdb1 100644 --- a/briar-android/src/test/java/org/briarproject/briar/android/conversation/AttachmentControllerTest.java +++ b/briar-android/src/test/java/org/briarproject/briar/android/attachment/AttachmentRetrieverTest.java @@ -1,8 +1,8 @@ -package org.briarproject.briar.android.conversation; +package org.briarproject.briar.android.attachment; import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.test.BrambleMockTestCase; -import org.briarproject.briar.android.conversation.ImageHelper.DecodeResult; +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; @@ -19,7 +19,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -public class AttachmentControllerTest extends BrambleMockTestCase { +public class AttachmentRetrieverTest extends BrambleMockTestCase { private final AttachmentDimensions dimensions = new AttachmentDimensions( 100, 50, 200, 75, 300 @@ -32,8 +32,8 @@ public class AttachmentControllerTest extends BrambleMockTestCase { private final MessagingManager messagingManager = context.mock(MessagingManager.class); private final ImageHelper imageHelper = context.mock(ImageHelper.class); - private final AttachmentController controller = - new AttachmentController( + private final AttachmentRetriever controller = + new AttachmentRetriever( messagingManager, dimensions, imageHelper diff --git a/briar-android/src/test/java/org/briarproject/briar/android/conversation/MarkEnforcingInputStreamTest.java b/briar-android/src/test/java/org/briarproject/briar/android/attachment/MarkEnforcingInputStreamTest.java similarity index 97% rename from briar-android/src/test/java/org/briarproject/briar/android/conversation/MarkEnforcingInputStreamTest.java rename to briar-android/src/test/java/org/briarproject/briar/android/attachment/MarkEnforcingInputStreamTest.java index 45a30a499..14defdf4d 100644 --- a/briar-android/src/test/java/org/briarproject/briar/android/conversation/MarkEnforcingInputStreamTest.java +++ b/briar-android/src/test/java/org/briarproject/briar/android/attachment/MarkEnforcingInputStreamTest.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.android.conversation; +package org.briarproject.briar.android.attachment; import com.bumptech.glide.util.MarkEnforcingInputStream; diff --git a/briar-core/src/main/java/org/briarproject/briar/messaging/MessagingManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/messaging/MessagingManagerImpl.java index 8917be036..366eb768c 100644 --- a/briar-core/src/main/java/org/briarproject/briar/messaging/MessagingManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/messaging/MessagingManagerImpl.java @@ -174,6 +174,10 @@ class MessagingManagerImpl extends ConversationClientImpl } if (is.available() > 0) throw new FileTooBigException(); is.close(); + try { + Thread.sleep(1000); + } catch (InterruptedException ignored) { + } Message m = messageFactory.createMessage(groupId, timestamp, body); clientHelper.addLocalMessage(m, new BdfDictionary(), false); return new AttachmentHeader(m.getId(), contentType);