mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-17 13:19:52 +01:00
Merge branch '1468-reject-unsupported-images' into 'master'
Reject unsupported images Closes #1468 See merge request briar/briar!1038
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
package org.briarproject.briar.android.conversation;
|
package org.briarproject.briar.android.attachment;
|
||||||
|
|
||||||
import android.content.res.AssetManager;
|
import android.content.res.AssetManager;
|
||||||
import android.support.test.InstrumentationRegistry;
|
import android.support.test.InstrumentationRegistry;
|
||||||
@@ -21,7 +21,7 @@ import static org.junit.Assert.assertFalse;
|
|||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class AttachmentControllerIntegrationTest {
|
public class AttachmentRetrieverIntegrationTest {
|
||||||
|
|
||||||
private static final String smallKitten =
|
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";
|
"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 MessageId msgId = new MessageId(getRandomId());
|
||||||
|
|
||||||
private final AttachmentController controller =
|
private final AttachmentRetriever retriever =
|
||||||
new AttachmentController(null, dimensions);
|
new AttachmentRetriever(null, dimensions);
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSmallJpegImage() throws Exception {
|
public void testSmallJpegImage() throws Exception {
|
||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
InputStream is = getUrlInputStream(smallKitten);
|
InputStream is = getUrlInputStream(smallKitten);
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(is);
|
||||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||||
assertEquals(msgId, item.getMessageId());
|
assertEquals(msgId, item.getMessageId());
|
||||||
assertEquals(160, item.getWidth());
|
assertEquals(160, item.getWidth());
|
||||||
assertEquals(240, item.getHeight());
|
assertEquals(240, item.getHeight());
|
||||||
@@ -71,7 +71,7 @@ public class AttachmentControllerIntegrationTest {
|
|||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
InputStream is = getUrlInputStream(originalKitten);
|
InputStream is = getUrlInputStream(originalKitten);
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(is);
|
||||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||||
assertEquals(msgId, item.getMessageId());
|
assertEquals(msgId, item.getMessageId());
|
||||||
assertEquals(1728, item.getWidth());
|
assertEquals(1728, item.getWidth());
|
||||||
assertEquals(2592, item.getHeight());
|
assertEquals(2592, item.getHeight());
|
||||||
@@ -87,7 +87,7 @@ public class AttachmentControllerIntegrationTest {
|
|||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/png");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/png");
|
||||||
InputStream is = getUrlInputStream(pngKitten);
|
InputStream is = getUrlInputStream(pngKitten);
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(is);
|
||||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||||
assertEquals(msgId, item.getMessageId());
|
assertEquals(msgId, item.getMessageId());
|
||||||
assertEquals(737, item.getWidth());
|
assertEquals(737, item.getWidth());
|
||||||
assertEquals(510, item.getHeight());
|
assertEquals(510, item.getHeight());
|
||||||
@@ -103,7 +103,7 @@ public class AttachmentControllerIntegrationTest {
|
|||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
InputStream is = getUrlInputStream(uberGif);
|
InputStream is = getUrlInputStream(uberGif);
|
||||||
Attachment a = new Attachment(is);
|
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.getWidth());
|
||||||
assertEquals(1, item.getHeight());
|
assertEquals(1, item.getHeight());
|
||||||
assertEquals(dimensions.minHeight, item.getThumbnailWidth());
|
assertEquals(dimensions.minHeight, item.getThumbnailWidth());
|
||||||
@@ -118,7 +118,7 @@ public class AttachmentControllerIntegrationTest {
|
|||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
InputStream is = getUrlInputStream(lottaPixel);
|
InputStream is = getUrlInputStream(lottaPixel);
|
||||||
Attachment a = new Attachment(is);
|
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.getWidth());
|
||||||
assertEquals(64250, item.getHeight());
|
assertEquals(64250, item.getHeight());
|
||||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||||
@@ -133,7 +133,7 @@ public class AttachmentControllerIntegrationTest {
|
|||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
InputStream is = getUrlInputStream(imageIoCrash);
|
InputStream is = getUrlInputStream(imageIoCrash);
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(is);
|
||||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||||
assertEquals(1184, item.getWidth());
|
assertEquals(1184, item.getWidth());
|
||||||
assertEquals(448, item.getHeight());
|
assertEquals(448, item.getHeight());
|
||||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||||
@@ -148,7 +148,7 @@ public class AttachmentControllerIntegrationTest {
|
|||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
InputStream is = getUrlInputStream(gimpCrash);
|
InputStream is = getUrlInputStream(gimpCrash);
|
||||||
Attachment a = new Attachment(is);
|
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.getWidth());
|
||||||
assertEquals(1, item.getHeight());
|
assertEquals(1, item.getHeight());
|
||||||
assertEquals(dimensions.minHeight, item.getThumbnailWidth());
|
assertEquals(dimensions.minHeight, item.getThumbnailWidth());
|
||||||
@@ -163,7 +163,7 @@ public class AttachmentControllerIntegrationTest {
|
|||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
InputStream is = getUrlInputStream(optiPngAfl);
|
InputStream is = getUrlInputStream(optiPngAfl);
|
||||||
Attachment a = new Attachment(is);
|
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.getWidth());
|
||||||
assertEquals(32, item.getHeight());
|
assertEquals(32, item.getHeight());
|
||||||
assertEquals(dimensions.minHeight, item.getThumbnailWidth());
|
assertEquals(dimensions.minHeight, item.getThumbnailWidth());
|
||||||
@@ -178,7 +178,7 @@ public class AttachmentControllerIntegrationTest {
|
|||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
InputStream is = getUrlInputStream(librawError);
|
InputStream is = getUrlInputStream(librawError);
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(is);
|
||||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||||
assertTrue(item.hasError());
|
assertTrue(item.hasError());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,7 +187,7 @@ public class AttachmentControllerIntegrationTest {
|
|||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
|
||||||
InputStream is = getAssetInputStream("animated.gif");
|
InputStream is = getAssetInputStream("animated.gif");
|
||||||
Attachment a = new Attachment(is);
|
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.getWidth());
|
||||||
assertEquals(65535, item.getHeight());
|
assertEquals(65535, item.getHeight());
|
||||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||||
@@ -202,7 +202,7 @@ public class AttachmentControllerIntegrationTest {
|
|||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
|
||||||
InputStream is = getAssetInputStream("animated2.gif");
|
InputStream is = getAssetInputStream("animated2.gif");
|
||||||
Attachment a = new Attachment(is);
|
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.getWidth());
|
||||||
assertEquals(10000, item.getHeight());
|
assertEquals(10000, item.getHeight());
|
||||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||||
@@ -217,7 +217,7 @@ public class AttachmentControllerIntegrationTest {
|
|||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
|
||||||
InputStream is = getAssetInputStream("error_large.gif");
|
InputStream is = getAssetInputStream("error_large.gif");
|
||||||
Attachment a = new Attachment(is);
|
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.getWidth());
|
||||||
assertEquals(16384, item.getHeight());
|
assertEquals(16384, item.getHeight());
|
||||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||||
@@ -232,7 +232,7 @@ public class AttachmentControllerIntegrationTest {
|
|||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
InputStream is = getAssetInputStream("error_high.jpg");
|
InputStream is = getAssetInputStream("error_high.jpg");
|
||||||
Attachment a = new Attachment(is);
|
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.getWidth());
|
||||||
assertEquals(10000, item.getHeight());
|
assertEquals(10000, item.getHeight());
|
||||||
assertEquals(dimensions.minWidth, item.getThumbnailWidth());
|
assertEquals(dimensions.minWidth, item.getThumbnailWidth());
|
||||||
@@ -247,7 +247,7 @@ public class AttachmentControllerIntegrationTest {
|
|||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
InputStream is = getAssetInputStream("error_wide.jpg");
|
InputStream is = getAssetInputStream("error_wide.jpg");
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(is);
|
||||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||||
assertEquals(1920, item.getWidth());
|
assertEquals(1920, item.getWidth());
|
||||||
assertEquals(1, item.getHeight());
|
assertEquals(1, item.getHeight());
|
||||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
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.Collection;
|
||||||
|
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 Collection<Uri> uris;
|
||||||
|
private final boolean needsSize;
|
||||||
|
@Nullable
|
||||||
|
private volatile AttachmentCreator attachmentCreator;
|
||||||
|
|
||||||
|
private volatile boolean canceled = false;
|
||||||
|
|
||||||
|
AttachmentCreationTask(MessagingManager messagingManager,
|
||||||
|
ContentResolver contentResolver,
|
||||||
|
AttachmentCreator attachmentCreator, GroupId groupId,
|
||||||
|
Collection<Uri> 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);
|
||||||
|
AttachmentCreator attachmentCreator = this.attachmentCreator;
|
||||||
|
if (!canceled && attachmentCreator != null)
|
||||||
|
attachmentCreator.onAttachmentCreationFinished();
|
||||||
|
this.attachmentCreator = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@IoExecutor
|
||||||
|
private void processUri(Uri uri) {
|
||||||
|
if (canceled) return;
|
||||||
|
try {
|
||||||
|
AttachmentHeader h = storeAttachment(uri);
|
||||||
|
AttachmentCreator attachmentCreator = this.attachmentCreator;
|
||||||
|
if (attachmentCreator != null) {
|
||||||
|
attachmentCreator.onAttachmentHeaderReceived(uri, h, needsSize);
|
||||||
|
}
|
||||||
|
} catch (DbException | IOException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
AttachmentCreator attachmentCreator = this.attachmentCreator;
|
||||||
|
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)) {
|
||||||
|
String uriString = uri.toString();
|
||||||
|
throw new UnsupportedMimeTypeException("", contentType, uriString);
|
||||||
|
}
|
||||||
|
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(String mimeType) {
|
||||||
|
for (String supportedType : IMAGE_MIME_TYPES) {
|
||||||
|
if (supportedType.equals(mimeType)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,218 @@
|
|||||||
|
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<>();
|
||||||
|
|
||||||
|
private final MutableLiveData<AttachmentResult> result =
|
||||||
|
new MutableLiveData<>();
|
||||||
|
@Nullable
|
||||||
|
private AttachmentCreationTask task;
|
||||||
|
|
||||||
|
public AttachmentCreator(Application app, @IoExecutor Executor ioExecutor,
|
||||||
|
MessagingManager messagingManager, AttachmentRetriever retriever) {
|
||||||
|
this.app = app;
|
||||||
|
this.ioExecutor = ioExecutor;
|
||||||
|
this.messagingManager = messagingManager;
|
||||||
|
this.retriever = retriever;
|
||||||
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
public LiveData<AttachmentResult> storeAttachments(
|
||||||
|
LiveData<GroupId> groupId, Collection<Uri> newUris) {
|
||||||
|
if (task != 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());
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This should be only called after configuration changes.
|
||||||
|
* In this case we should not create new attachments.
|
||||||
|
* They are already being created and returned by the existing LiveData.
|
||||||
|
*/
|
||||||
|
@UiThread
|
||||||
|
public LiveData<AttachmentResult> getLiveAttachments() {
|
||||||
|
if (task == 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(h, a, needsSize);
|
||||||
|
if (item.hasError()) throw new IOException();
|
||||||
|
AttachmentItemResult itemResult =
|
||||||
|
new AttachmentItemResult(uri, item);
|
||||||
|
itemResults.add(itemResult);
|
||||||
|
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);
|
||||||
|
result.postValue(getResult(false));
|
||||||
|
// expect to receive a cancel from the UI
|
||||||
|
}
|
||||||
|
|
||||||
|
@IoExecutor
|
||||||
|
void onAttachmentCreationFinished() {
|
||||||
|
result.postValue(getResult(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks the attachments as sent and adds the items to the cache for display
|
||||||
|
*
|
||||||
|
* @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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
private void resetState() {
|
||||||
|
task = null;
|
||||||
|
uris.clear();
|
||||||
|
itemResults.clear();
|
||||||
|
result.setValue(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,11 +1,16 @@
|
|||||||
package org.briarproject.briar.android.conversation;
|
package org.briarproject.briar.android.attachment;
|
||||||
|
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.support.annotation.VisibleForTesting;
|
import android.support.annotation.VisibleForTesting;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
|
|
||||||
class AttachmentDimensions {
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
public class AttachmentDimensions {
|
||||||
|
|
||||||
final int defaultSize;
|
final int defaultSize;
|
||||||
final int minWidth, maxWidth;
|
final int minWidth, maxWidth;
|
||||||
@@ -21,7 +26,7 @@ class AttachmentDimensions {
|
|||||||
this.maxHeight = maxHeight;
|
this.maxHeight = maxHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
static AttachmentDimensions getAttachmentDimensions(Resources res) {
|
public static AttachmentDimensions getAttachmentDimensions(Resources res) {
|
||||||
int defaultSize =
|
int defaultSize =
|
||||||
res.getDimensionPixelSize(R.dimen.message_bubble_image_default);
|
res.getDimensionPixelSize(R.dimen.message_bubble_image_default);
|
||||||
int minWidth = res.getDimensionPixelSize(
|
int minWidth = res.getDimensionPixelSize(
|
||||||
@@ -33,7 +38,7 @@ class AttachmentDimensions {
|
|||||||
int maxHeight = res.getDimensionPixelSize(
|
int maxHeight = res.getDimensionPixelSize(
|
||||||
R.dimen.message_bubble_image_max_height);
|
R.dimen.message_bubble_image_max_height);
|
||||||
return new AttachmentDimensions(defaultSize, minWidth, maxWidth,
|
return new AttachmentDimensions(defaultSize, minWidth, maxWidth,
|
||||||
minHeight, minHeight);
|
minHeight, maxHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.briarproject.briar.android.conversation;
|
package org.briarproject.briar.android.attachment;
|
||||||
|
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
@@ -6,18 +6,21 @@ import android.support.annotation.Nullable;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
import static java.util.Objects.requireNonNull;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class AttachmentItem implements Parcelable {
|
public class AttachmentItem implements Parcelable {
|
||||||
|
|
||||||
private final MessageId messageId;
|
private final AttachmentHeader header;
|
||||||
private final int width, height;
|
private final int width, height;
|
||||||
private final String mimeType, extension;
|
private final String extension;
|
||||||
private final int thumbnailWidth, thumbnailHeight;
|
private final int thumbnailWidth, thumbnailHeight;
|
||||||
private final boolean hasError;
|
private final boolean hasError;
|
||||||
private final long instanceId;
|
private final long instanceId;
|
||||||
@@ -37,13 +40,12 @@ public class AttachmentItem implements Parcelable {
|
|||||||
|
|
||||||
private static final AtomicLong NEXT_INSTANCE_ID = new AtomicLong(0);
|
private static final AtomicLong NEXT_INSTANCE_ID = new AtomicLong(0);
|
||||||
|
|
||||||
AttachmentItem(MessageId messageId, int width, int height, String mimeType,
|
AttachmentItem(AttachmentHeader header, int width, int height,
|
||||||
String extension, int thumbnailWidth, int thumbnailHeight,
|
String extension, int thumbnailWidth, int thumbnailHeight,
|
||||||
boolean hasError) {
|
boolean hasError) {
|
||||||
this.messageId = messageId;
|
this.header = header;
|
||||||
this.width = width;
|
this.width = width;
|
||||||
this.height = height;
|
this.height = height;
|
||||||
this.mimeType = mimeType;
|
|
||||||
this.extension = extension;
|
this.extension = extension;
|
||||||
this.thumbnailWidth = thumbnailWidth;
|
this.thumbnailWidth = thumbnailWidth;
|
||||||
this.thumbnailHeight = thumbnailHeight;
|
this.thumbnailHeight = thumbnailHeight;
|
||||||
@@ -54,19 +56,24 @@ public class AttachmentItem implements Parcelable {
|
|||||||
protected AttachmentItem(Parcel in) {
|
protected AttachmentItem(Parcel in) {
|
||||||
byte[] messageIdByte = new byte[MessageId.LENGTH];
|
byte[] messageIdByte = new byte[MessageId.LENGTH];
|
||||||
in.readByteArray(messageIdByte);
|
in.readByteArray(messageIdByte);
|
||||||
messageId = new MessageId(messageIdByte);
|
MessageId messageId = new MessageId(messageIdByte);
|
||||||
width = in.readInt();
|
width = in.readInt();
|
||||||
height = in.readInt();
|
height = in.readInt();
|
||||||
mimeType = in.readString();
|
String mimeType = requireNonNull(in.readString());
|
||||||
extension = in.readString();
|
extension = requireNonNull(in.readString());
|
||||||
thumbnailWidth = in.readInt();
|
thumbnailWidth = in.readInt();
|
||||||
thumbnailHeight = in.readInt();
|
thumbnailHeight = in.readInt();
|
||||||
hasError = in.readByte() != 0;
|
hasError = in.readByte() != 0;
|
||||||
instanceId = in.readLong();
|
instanceId = in.readLong();
|
||||||
|
header = new AttachmentHeader(messageId, mimeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
AttachmentHeader getHeader() {
|
||||||
|
return header;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MessageId getMessageId() {
|
public MessageId getMessageId() {
|
||||||
return messageId;
|
return header.getMessageId();
|
||||||
}
|
}
|
||||||
|
|
||||||
int getWidth() {
|
int getWidth() {
|
||||||
@@ -77,27 +84,27 @@ public class AttachmentItem implements Parcelable {
|
|||||||
return height;
|
return height;
|
||||||
}
|
}
|
||||||
|
|
||||||
String getMimeType() {
|
public String getMimeType() {
|
||||||
return mimeType;
|
return header.getContentType();
|
||||||
}
|
}
|
||||||
|
|
||||||
String getExtension() {
|
public String getExtension() {
|
||||||
return extension;
|
return extension;
|
||||||
}
|
}
|
||||||
|
|
||||||
int getThumbnailWidth() {
|
public int getThumbnailWidth() {
|
||||||
return thumbnailWidth;
|
return thumbnailWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
int getThumbnailHeight() {
|
public int getThumbnailHeight() {
|
||||||
return thumbnailHeight;
|
return thumbnailHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean hasError() {
|
public boolean hasError() {
|
||||||
return hasError;
|
return hasError;
|
||||||
}
|
}
|
||||||
|
|
||||||
String getTransitionName() {
|
public String getTransitionName() {
|
||||||
return String.valueOf(instanceId);
|
return String.valueOf(instanceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,10 +115,10 @@ public class AttachmentItem implements Parcelable {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeToParcel(Parcel dest, int flags) {
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
dest.writeByteArray(messageId.getBytes());
|
dest.writeByteArray(header.getMessageId().getBytes());
|
||||||
dest.writeInt(width);
|
dest.writeInt(width);
|
||||||
dest.writeInt(height);
|
dest.writeInt(height);
|
||||||
dest.writeString(mimeType);
|
dest.writeString(header.getContentType());
|
||||||
dest.writeString(extension);
|
dest.writeString(extension);
|
||||||
dest.writeInt(thumbnailWidth);
|
dest.writeInt(thumbnailWidth);
|
||||||
dest.writeInt(thumbnailHeight);
|
dest.writeInt(thumbnailHeight);
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package org.briarproject.briar.android.attachment;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
public class AttachmentItemResult {
|
||||||
|
|
||||||
|
private final Uri uri;
|
||||||
|
@Nullable
|
||||||
|
private final AttachmentItem item;
|
||||||
|
@Nullable
|
||||||
|
private final String errorMsg;
|
||||||
|
|
||||||
|
AttachmentItemResult(Uri uri, AttachmentItem item) {
|
||||||
|
this.uri = uri;
|
||||||
|
this.item = item;
|
||||||
|
this.errorMsg = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
AttachmentItemResult(Uri uri, @Nullable String errorMsg) {
|
||||||
|
this.uri = uri;
|
||||||
|
this.item = null;
|
||||||
|
this.errorMsg = errorMsg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Uri getUri() {
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public AttachmentItem getItem() {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasError() {
|
||||||
|
return item == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getErrorMsg() {
|
||||||
|
return errorMsg;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package org.briarproject.briar.android.attachment;
|
||||||
|
|
||||||
|
import android.arch.lifecycle.LiveData;
|
||||||
|
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 {
|
||||||
|
|
||||||
|
LiveData<AttachmentResult> storeAttachments(Collection<Uri> uri,
|
||||||
|
boolean restart);
|
||||||
|
|
||||||
|
List<AttachmentHeader> getAttachmentHeadersForSending();
|
||||||
|
|
||||||
|
void cancel();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package org.briarproject.briar.android.attachment;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
public class AttachmentResult {
|
||||||
|
|
||||||
|
private final Collection<AttachmentItemResult> itemResults;
|
||||||
|
private final boolean finished;
|
||||||
|
|
||||||
|
public AttachmentResult(Collection<AttachmentItemResult> itemResults,
|
||||||
|
boolean finished) {
|
||||||
|
this.itemResults = itemResults;
|
||||||
|
this.finished = finished;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<AttachmentItemResult> getItemResults() {
|
||||||
|
return itemResults;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFinished() {
|
||||||
|
return finished;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
package org.briarproject.briar.android.conversation;
|
package org.briarproject.briar.android.attachment;
|
||||||
|
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
import android.graphics.BitmapFactory.Options;
|
import android.graphics.BitmapFactory.Options;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.annotation.VisibleForTesting;
|
||||||
import android.support.media.ExifInterface;
|
import android.support.media.ExifInterface;
|
||||||
import android.webkit.MimeTypeMap;
|
import android.webkit.MimeTypeMap;
|
||||||
|
|
||||||
@@ -13,7 +14,7 @@ import org.briarproject.bramble.api.db.DatabaseExecutor;
|
|||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
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.Attachment;
|
||||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||||
@@ -42,10 +43,10 @@ import static org.briarproject.bramble.util.LogUtils.logException;
|
|||||||
import static org.briarproject.bramble.util.LogUtils.now;
|
import static org.briarproject.bramble.util.LogUtils.now;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class AttachmentController {
|
public class AttachmentRetriever {
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
getLogger(AttachmentController.class.getName());
|
getLogger(AttachmentRetriever.class.getName());
|
||||||
private static final int READ_LIMIT = 1024 * 8192;
|
private static final int READ_LIMIT = 1024 * 8192;
|
||||||
|
|
||||||
private final MessagingManager messagingManager;
|
private final MessagingManager messagingManager;
|
||||||
@@ -57,7 +58,8 @@ class AttachmentController {
|
|||||||
private final Map<MessageId, List<AttachmentItem>> attachmentCache =
|
private final Map<MessageId, List<AttachmentItem>> attachmentCache =
|
||||||
new ConcurrentHashMap<>();
|
new ConcurrentHashMap<>();
|
||||||
|
|
||||||
AttachmentController(MessagingManager messagingManager,
|
@VisibleForTesting
|
||||||
|
AttachmentRetriever(MessagingManager messagingManager,
|
||||||
AttachmentDimensions dimensions, ImageHelper imageHelper) {
|
AttachmentDimensions dimensions, ImageHelper imageHelper) {
|
||||||
this.messagingManager = messagingManager;
|
this.messagingManager = messagingManager;
|
||||||
this.imageHelper = imageHelper;
|
this.imageHelper = imageHelper;
|
||||||
@@ -68,7 +70,7 @@ class AttachmentController {
|
|||||||
maxHeight = dimensions.maxHeight;
|
maxHeight = dimensions.maxHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
AttachmentController(MessagingManager messagingManager,
|
public AttachmentRetriever(MessagingManager messagingManager,
|
||||||
AttachmentDimensions dimensions) {
|
AttachmentDimensions dimensions) {
|
||||||
this(messagingManager, dimensions, new ImageHelper() {
|
this(messagingManager, dimensions, new ImageHelper() {
|
||||||
@Override
|
@Override
|
||||||
@@ -91,17 +93,17 @@ class AttachmentController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void put(MessageId messageId, List<AttachmentItem> attachments) {
|
public void cachePut(MessageId messageId, List<AttachmentItem> attachments) {
|
||||||
attachmentCache.put(messageId, attachments);
|
attachmentCache.put(messageId, attachments);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
List<AttachmentItem> get(MessageId messageId) {
|
public List<AttachmentItem> cacheGet(MessageId messageId) {
|
||||||
return attachmentCache.get(messageId);
|
return attachmentCache.get(messageId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@DatabaseExecutor
|
@DatabaseExecutor
|
||||||
List<Pair<AttachmentHeader, Attachment>> getMessageAttachments(
|
public List<Pair<AttachmentHeader, Attachment>> getMessageAttachments(
|
||||||
List<AttachmentHeader> headers) throws DbException {
|
List<AttachmentHeader> headers) throws DbException {
|
||||||
long start = now();
|
long start = now();
|
||||||
List<Pair<AttachmentHeader, Attachment>> attachments =
|
List<Pair<AttachmentHeader, Attachment>> attachments =
|
||||||
@@ -110,16 +112,21 @@ class AttachmentController {
|
|||||||
Attachment a = messagingManager.getAttachment(h.getMessageId());
|
Attachment a = messagingManager.getAttachment(h.getMessageId());
|
||||||
attachments.add(new Pair<>(h, a));
|
attachments.add(new Pair<>(h, a));
|
||||||
}
|
}
|
||||||
logDuration(LOG, "Loading attachment", start);
|
logDuration(LOG, "Loading attachments", start);
|
||||||
return attachments;
|
return attachments;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@DatabaseExecutor
|
||||||
|
Attachment getMessageAttachment(AttachmentHeader h) throws DbException {
|
||||||
|
return messagingManager.getAttachment(h.getMessageId());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates {@link AttachmentItem}s from the passed headers and Attachments.
|
* Creates {@link AttachmentItem}s from the passed headers and Attachments.
|
||||||
* <p>
|
* <p>
|
||||||
* Note: This closes the {@link Attachment}'s {@link InputStream}.
|
* Note: This closes the {@link Attachment}'s {@link InputStream}.
|
||||||
*/
|
*/
|
||||||
List<AttachmentItem> getAttachmentItems(
|
public List<AttachmentItem> getAttachmentItems(
|
||||||
List<Pair<AttachmentHeader, Attachment>> attachments) {
|
List<Pair<AttachmentHeader, Attachment>> attachments) {
|
||||||
boolean needsSize = attachments.size() == 1;
|
boolean needsSize = attachments.size() == 1;
|
||||||
List<AttachmentItem> items = new ArrayList<>(attachments.size());
|
List<AttachmentItem> items = new ArrayList<>(attachments.size());
|
||||||
@@ -137,17 +144,15 @@ class AttachmentController {
|
|||||||
*/
|
*/
|
||||||
AttachmentItem getAttachmentItem(AttachmentHeader h, Attachment a,
|
AttachmentItem getAttachmentItem(AttachmentHeader h, Attachment a,
|
||||||
boolean needsSize) {
|
boolean needsSize) {
|
||||||
MessageId messageId = h.getMessageId();
|
|
||||||
if (!needsSize) {
|
if (!needsSize) {
|
||||||
String mimeType = h.getContentType();
|
String extension =
|
||||||
String extension = imageHelper.getExtensionFromMimeType(mimeType);
|
imageHelper.getExtensionFromMimeType(h.getContentType());
|
||||||
boolean hasError = false;
|
boolean hasError = false;
|
||||||
if (extension == null) {
|
if (extension == null) {
|
||||||
extension = "";
|
extension = "";
|
||||||
hasError = true;
|
hasError = true;
|
||||||
}
|
}
|
||||||
return new AttachmentItem(messageId, 0, 0, mimeType, extension, 0,
|
return new AttachmentItem(h, 0, 0, extension, 0, 0, hasError);
|
||||||
0, hasError);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Size size = new Size();
|
Size size = new Size();
|
||||||
@@ -185,10 +190,17 @@ class AttachmentController {
|
|||||||
// get file extension
|
// get file extension
|
||||||
String extension = imageHelper.getExtensionFromMimeType(size.mimeType);
|
String extension = imageHelper.getExtensionFromMimeType(size.mimeType);
|
||||||
boolean hasError = extension == null || size.error;
|
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 = "";
|
if (extension == null) extension = "";
|
||||||
return new AttachmentItem(messageId, size.width, size.height,
|
return new AttachmentItem(h, size.width, size.height, extension,
|
||||||
size.mimeType, extension, thumbnailSize.width,
|
thumbnailSize.width, thumbnailSize.height, hasError);
|
||||||
thumbnailSize.height, hasError);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.briarproject.briar.android.conversation;
|
package org.briarproject.briar.android.attachment;
|
||||||
|
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package org.briarproject.briar.android.blog;
|
package org.briarproject.briar.android.blog;
|
||||||
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -21,6 +20,7 @@ import org.briarproject.briar.android.fragment.BaseFragment;
|
|||||||
import org.briarproject.briar.android.view.TextInputView;
|
import org.briarproject.briar.android.view.TextInputView;
|
||||||
import org.briarproject.briar.android.view.TextSendController;
|
import org.briarproject.briar.android.view.TextSendController;
|
||||||
import org.briarproject.briar.android.view.TextSendController.SendListener;
|
import org.briarproject.briar.android.view.TextSendController.SendListener;
|
||||||
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -121,7 +121,8 @@ public class ReblogFragment extends BaseFragment implements SendListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSendClick(@Nullable String text, List<Uri> imageUris) {
|
public void onSendClick(@Nullable String text,
|
||||||
|
List<AttachmentHeader> headers) {
|
||||||
ui.input.hideSoftKeyboard();
|
ui.input.hideSoftKeyboard();
|
||||||
feedController.repeatPost(item, text,
|
feedController.repeatPost(item, text,
|
||||||
new UiExceptionHandler<DbException>(this) {
|
new UiExceptionHandler<DbException>(this) {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.briarproject.briar.android.blog;
|
package org.briarproject.briar.android.blog;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
@@ -27,6 +26,7 @@ import org.briarproject.briar.api.android.AndroidNotificationManager;
|
|||||||
import org.briarproject.briar.api.blog.BlogManager;
|
import org.briarproject.briar.api.blog.BlogManager;
|
||||||
import org.briarproject.briar.api.blog.BlogPost;
|
import org.briarproject.briar.api.blog.BlogPost;
|
||||||
import org.briarproject.briar.api.blog.BlogPostFactory;
|
import org.briarproject.briar.api.blog.BlogPostFactory;
|
||||||
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
|
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -120,7 +120,8 @@ public class WriteBlogPostActivity extends BriarActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSendClick(@Nullable String text, List<Uri> imageUris) {
|
public void onSendClick(@Nullable String text,
|
||||||
|
List<AttachmentHeader> headers) {
|
||||||
if (isNullOrEmpty(text)) throw new AssertionError();
|
if (isNullOrEmpty(text)) throw new AssertionError();
|
||||||
|
|
||||||
// hide publish button, show progress bar
|
// hide publish button, show progress bar
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import android.arch.lifecycle.ViewModelProvider;
|
|||||||
import android.arch.lifecycle.ViewModelProviders;
|
import android.arch.lifecycle.ViewModelProviders;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
@@ -53,6 +52,8 @@ import org.briarproject.bramble.api.sync.event.MessagesSentEvent;
|
|||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
import org.briarproject.briar.android.activity.BriarActivity;
|
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.blog.BlogActivity;
|
||||||
import org.briarproject.briar.android.conversation.ConversationVisitor.AttachmentCache;
|
import org.briarproject.briar.android.conversation.ConversationVisitor.AttachmentCache;
|
||||||
import org.briarproject.briar.android.conversation.ConversationVisitor.TextCache;
|
import org.briarproject.briar.android.conversation.ConversationVisitor.TextCache;
|
||||||
@@ -184,7 +185,7 @@ public class ConversationActivity extends BriarActivity
|
|||||||
loadMessages();
|
loadMessages();
|
||||||
};
|
};
|
||||||
|
|
||||||
private AttachmentController attachmentController;
|
private AttachmentRetriever attachmentRetriever;
|
||||||
private ConversationViewModel viewModel;
|
private ConversationViewModel viewModel;
|
||||||
private ConversationVisitor visitor;
|
private ConversationVisitor visitor;
|
||||||
private ConversationAdapter adapter;
|
private ConversationAdapter adapter;
|
||||||
@@ -219,7 +220,7 @@ public class ConversationActivity extends BriarActivity
|
|||||||
|
|
||||||
viewModel = ViewModelProviders.of(this, viewModelFactory)
|
viewModel = ViewModelProviders.of(this, viewModelFactory)
|
||||||
.get(ConversationViewModel.class);
|
.get(ConversationViewModel.class);
|
||||||
attachmentController = viewModel.getAttachmentController();
|
attachmentRetriever = viewModel.getAttachmentRetriever();
|
||||||
|
|
||||||
setContentView(R.layout.activity_conversation);
|
setContentView(R.layout.activity_conversation);
|
||||||
|
|
||||||
@@ -242,7 +243,7 @@ public class ConversationActivity extends BriarActivity
|
|||||||
requireNonNull(deleted);
|
requireNonNull(deleted);
|
||||||
if (deleted) finish();
|
if (deleted) finish();
|
||||||
});
|
});
|
||||||
viewModel.getAddedPrivateMessage().observe(this,
|
viewModel.getAddedPrivateMessage().observeEvent(this,
|
||||||
this::onAddedPrivateMessage);
|
this::onAddedPrivateMessage);
|
||||||
|
|
||||||
setTransitionName(toolbarAvatar, getAvatarTransitionName(contactId));
|
setTransitionName(toolbarAvatar, getAvatarTransitionName(contactId));
|
||||||
@@ -264,7 +265,7 @@ public class ConversationActivity extends BriarActivity
|
|||||||
if (FEATURE_FLAG_IMAGE_ATTACHMENTS) {
|
if (FEATURE_FLAG_IMAGE_ATTACHMENTS) {
|
||||||
ImagePreview imagePreview = findViewById(R.id.imagePreview);
|
ImagePreview imagePreview = findViewById(R.id.imagePreview);
|
||||||
sendController = new TextAttachmentController(textInputView,
|
sendController = new TextAttachmentController(textInputView,
|
||||||
imagePreview, this, this);
|
imagePreview, this, this, viewModel);
|
||||||
observeOnce(viewModel.hasImageSupport(), this, hasSupport -> {
|
observeOnce(viewModel.hasImageSupport(), this, hasSupport -> {
|
||||||
if (hasSupport != null && hasSupport) {
|
if (hasSupport != null && hasSupport) {
|
||||||
// remove cast when removing FEATURE_FLAG_IMAGE_ATTACHMENTS
|
// remove cast when removing FEATURE_FLAG_IMAGE_ATTACHMENTS
|
||||||
@@ -457,13 +458,13 @@ public class ConversationActivity extends BriarActivity
|
|||||||
// If the message has a single image, load its size - for multiple
|
// If the message has a single image, load its size - for multiple
|
||||||
// images we use a grid so the size is fixed
|
// images we use a grid so the size is fixed
|
||||||
if (h.getAttachmentHeaders().size() == 1) {
|
if (h.getAttachmentHeaders().size() == 1) {
|
||||||
List<AttachmentItem> items = attachmentController.get(id);
|
List<AttachmentItem> items = attachmentRetriever.cacheGet(id);
|
||||||
if (items == null) {
|
if (items == null) {
|
||||||
LOG.info("Eagerly loading image size for latest message");
|
LOG.info("Eagerly loading image size for latest message");
|
||||||
items = attachmentController.getAttachmentItems(
|
items = attachmentRetriever.getAttachmentItems(
|
||||||
attachmentController.getMessageAttachments(
|
attachmentRetriever.getMessageAttachments(
|
||||||
h.getAttachmentHeaders()));
|
h.getAttachmentHeaders()));
|
||||||
attachmentController.put(id, items);
|
attachmentRetriever.cachePut(id, items);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -545,10 +546,10 @@ public class ConversationActivity extends BriarActivity
|
|||||||
runOnDbThread(() -> {
|
runOnDbThread(() -> {
|
||||||
try {
|
try {
|
||||||
List<Pair<AttachmentHeader, Attachment>> attachments =
|
List<Pair<AttachmentHeader, Attachment>> attachments =
|
||||||
attachmentController.getMessageAttachments(headers);
|
attachmentRetriever.getMessageAttachments(headers);
|
||||||
// TODO move getting the items off to IoExecutor, if size == 1
|
// TODO move getting the items off to IoExecutor, if size == 1
|
||||||
List<AttachmentItem> items =
|
List<AttachmentItem> items =
|
||||||
attachmentController.getAttachmentItems(attachments);
|
attachmentRetriever.getAttachmentItems(attachments);
|
||||||
displayMessageAttachments(messageId, items);
|
displayMessageAttachments(messageId, items);
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
@@ -559,7 +560,7 @@ public class ConversationActivity extends BriarActivity
|
|||||||
private void displayMessageAttachments(MessageId m,
|
private void displayMessageAttachments(MessageId m,
|
||||||
List<AttachmentItem> items) {
|
List<AttachmentItem> items) {
|
||||||
runOnUiThreadUnlessDestroyed(() -> {
|
runOnUiThreadUnlessDestroyed(() -> {
|
||||||
attachmentController.put(m, items);
|
attachmentRetriever.cachePut(m, items);
|
||||||
Pair<Integer, ConversationMessageItem> pair =
|
Pair<Integer, ConversationMessageItem> pair =
|
||||||
adapter.getMessageItem(m);
|
adapter.getMessageItem(m);
|
||||||
if (pair != null) {
|
if (pair != null) {
|
||||||
@@ -658,12 +659,13 @@ public class ConversationActivity extends BriarActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSendClick(@Nullable String text, List<Uri> imageUris) {
|
public void onSendClick(@Nullable String text,
|
||||||
if (isNullOrEmpty(text) && imageUris.isEmpty())
|
List<AttachmentHeader> attachmentHeaders) {
|
||||||
|
if (isNullOrEmpty(text) && attachmentHeaders.isEmpty())
|
||||||
throw new AssertionError();
|
throw new AssertionError();
|
||||||
long timestamp = System.currentTimeMillis();
|
long timestamp = System.currentTimeMillis();
|
||||||
timestamp = Math.max(timestamp, getMinTimestampForNewMessage());
|
timestamp = Math.max(timestamp, getMinTimestampForNewMessage());
|
||||||
viewModel.sendMessage(text, imageUris, timestamp);
|
viewModel.sendMessage(text, attachmentHeaders, timestamp);
|
||||||
textInputView.clearText();
|
textInputView.clearText();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -676,7 +678,6 @@ public class ConversationActivity extends BriarActivity
|
|||||||
private void onAddedPrivateMessage(@Nullable PrivateMessageHeader h) {
|
private void onAddedPrivateMessage(@Nullable PrivateMessageHeader h) {
|
||||||
if (h == null) return;
|
if (h == null) return;
|
||||||
addConversationItem(h.accept(visitor));
|
addConversationItem(h.accept(visitor));
|
||||||
viewModel.onAddedPrivateMessageSeen();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void askToRemoveContact() {
|
private void askToRemoveContact() {
|
||||||
@@ -903,7 +904,7 @@ public class ConversationActivity extends BriarActivity
|
|||||||
@Override
|
@Override
|
||||||
public List<AttachmentItem> getAttachmentItems(MessageId m,
|
public List<AttachmentItem> getAttachmentItems(MessageId m,
|
||||||
List<AttachmentHeader> headers) {
|
List<AttachmentHeader> headers) {
|
||||||
List<AttachmentItem> attachments = attachmentController.get(m);
|
List<AttachmentItem> attachments = attachmentRetriever.cacheGet(m);
|
||||||
if (attachments == null) {
|
if (attachments == null) {
|
||||||
loadMessageAttachments(m, headers);
|
loadMessageAttachments(m, headers);
|
||||||
return emptyList();
|
return emptyList();
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.support.annotation.UiThread;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package org.briarproject.briar.android.conversation;
|
|||||||
import android.support.annotation.LayoutRes;
|
import android.support.annotation.LayoutRes;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||||
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import android.view.ViewGroup;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
|
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||||
|
|
||||||
import static android.support.constraint.ConstraintSet.WRAP_CONTENT;
|
import static android.support.constraint.ConstraintSet.WRAP_CONTENT;
|
||||||
import static android.support.v4.content.ContextCompat.getColor;
|
import static android.support.v4.content.ContextCompat.getColor;
|
||||||
|
|||||||
@@ -5,13 +5,11 @@ import android.arch.lifecycle.AndroidViewModel;
|
|||||||
import android.arch.lifecycle.LiveData;
|
import android.arch.lifecycle.LiveData;
|
||||||
import android.arch.lifecycle.MutableLiveData;
|
import android.arch.lifecycle.MutableLiveData;
|
||||||
import android.arch.lifecycle.Transformations;
|
import android.arch.lifecycle.Transformations;
|
||||||
import android.content.ContentResolver;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.annotation.UiThread;
|
import android.support.annotation.UiThread;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
import org.briarproject.bramble.api.FormatException;
|
||||||
import org.briarproject.bramble.api.Pair;
|
|
||||||
import org.briarproject.bramble.api.contact.Contact;
|
import org.briarproject.bramble.api.contact.Contact;
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.contact.ContactManager;
|
import org.briarproject.bramble.api.contact.ContactManager;
|
||||||
@@ -21,25 +19,26 @@ import org.briarproject.bramble.api.db.DbException;
|
|||||||
import org.briarproject.bramble.api.db.NoSuchContactException;
|
import org.briarproject.bramble.api.db.NoSuchContactException;
|
||||||
import org.briarproject.bramble.api.db.TransactionManager;
|
import org.briarproject.bramble.api.db.TransactionManager;
|
||||||
import org.briarproject.bramble.api.identity.AuthorId;
|
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.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.settings.Settings;
|
import org.briarproject.bramble.api.settings.Settings;
|
||||||
import org.briarproject.bramble.api.settings.SettingsManager;
|
import org.briarproject.bramble.api.settings.SettingsManager;
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
import org.briarproject.bramble.api.sync.Message;
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
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.util.UiUtils;
|
||||||
import org.briarproject.briar.android.viewmodel.LiveEvent;
|
import org.briarproject.briar.android.viewmodel.LiveEvent;
|
||||||
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
|
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
|
||||||
import org.briarproject.briar.api.messaging.Attachment;
|
|
||||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||||
import org.briarproject.briar.api.messaging.PrivateMessage;
|
import org.briarproject.briar.api.messaging.PrivateMessage;
|
||||||
import org.briarproject.briar.api.messaging.PrivateMessageFactory;
|
import org.briarproject.briar.api.messaging.PrivateMessageFactory;
|
||||||
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
@@ -50,16 +49,16 @@ import javax.inject.Inject;
|
|||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static java.util.logging.Logger.getLogger;
|
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.logDuration;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
import static org.briarproject.bramble.util.LogUtils.now;
|
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.settings.SettingsFragment.SETTINGS_NAMESPACE;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce;
|
import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class ConversationViewModel extends AndroidViewModel {
|
public class ConversationViewModel extends AndroidViewModel
|
||||||
|
implements AttachmentManager {
|
||||||
|
|
||||||
private static Logger LOG =
|
private static Logger LOG =
|
||||||
getLogger(ConversationViewModel.class.getName());
|
getLogger(ConversationViewModel.class.getName());
|
||||||
@@ -77,7 +76,8 @@ public class ConversationViewModel extends AndroidViewModel {
|
|||||||
private final ContactManager contactManager;
|
private final ContactManager contactManager;
|
||||||
private final SettingsManager settingsManager;
|
private final SettingsManager settingsManager;
|
||||||
private final PrivateMessageFactory privateMessageFactory;
|
private final PrivateMessageFactory privateMessageFactory;
|
||||||
private final AttachmentController attachmentController;
|
private final AttachmentRetriever attachmentRetriever;
|
||||||
|
private final AttachmentCreator attachmentCreator;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private ContactId contactId = null;
|
private ContactId contactId = null;
|
||||||
@@ -86,6 +86,7 @@ public class ConversationViewModel extends AndroidViewModel {
|
|||||||
Transformations.map(contact, c -> c.getAuthor().getId());
|
Transformations.map(contact, c -> c.getAuthor().getId());
|
||||||
private final LiveData<String> contactName =
|
private final LiveData<String> contactName =
|
||||||
Transformations.map(contact, UiUtils::getContactDisplayName);
|
Transformations.map(contact, UiUtils::getContactDisplayName);
|
||||||
|
private final LiveData<GroupId> messagingGroupId;
|
||||||
private final MutableLiveData<Boolean> imageSupport =
|
private final MutableLiveData<Boolean> imageSupport =
|
||||||
new MutableLiveData<>();
|
new MutableLiveData<>();
|
||||||
private final MutableLiveEvent<Boolean> showImageOnboarding =
|
private final MutableLiveEvent<Boolean> showImageOnboarding =
|
||||||
@@ -96,15 +97,14 @@ public class ConversationViewModel extends AndroidViewModel {
|
|||||||
new MutableLiveData<>();
|
new MutableLiveData<>();
|
||||||
private final MutableLiveData<Boolean> contactDeleted =
|
private final MutableLiveData<Boolean> contactDeleted =
|
||||||
new MutableLiveData<>();
|
new MutableLiveData<>();
|
||||||
private final MutableLiveData<GroupId> messagingGroupId =
|
private final MutableLiveEvent<PrivateMessageHeader> addedHeader =
|
||||||
new MutableLiveData<>();
|
new MutableLiveEvent<>();
|
||||||
private final MutableLiveData<PrivateMessageHeader> addedHeader =
|
|
||||||
new MutableLiveData<>();
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ConversationViewModel(Application application,
|
ConversationViewModel(Application application,
|
||||||
@DatabaseExecutor Executor dbExecutor,
|
@DatabaseExecutor Executor dbExecutor,
|
||||||
@CryptoExecutor Executor cryptoExecutor, TransactionManager db,
|
@CryptoExecutor Executor cryptoExecutor,
|
||||||
|
@IoExecutor Executor ioExecutor, TransactionManager db,
|
||||||
MessagingManager messagingManager, ContactManager contactManager,
|
MessagingManager messagingManager, ContactManager contactManager,
|
||||||
SettingsManager settingsManager,
|
SettingsManager settingsManager,
|
||||||
PrivateMessageFactory privateMessageFactory) {
|
PrivateMessageFactory privateMessageFactory) {
|
||||||
@@ -116,11 +116,21 @@ public class ConversationViewModel extends AndroidViewModel {
|
|||||||
this.contactManager = contactManager;
|
this.contactManager = contactManager;
|
||||||
this.settingsManager = settingsManager;
|
this.settingsManager = settingsManager;
|
||||||
this.privateMessageFactory = privateMessageFactory;
|
this.privateMessageFactory = privateMessageFactory;
|
||||||
this.attachmentController = new AttachmentController(messagingManager,
|
this.attachmentRetriever = new AttachmentRetriever(messagingManager,
|
||||||
getAttachmentDimensions(application.getResources()));
|
getAttachmentDimensions(application.getResources()));
|
||||||
|
this.attachmentCreator = new AttachmentCreator(getApplication(),
|
||||||
|
ioExecutor, messagingManager, attachmentRetriever);
|
||||||
|
messagingGroupId = Transformations
|
||||||
|
.map(contact, c -> messagingManager.getContactGroup(c).getId());
|
||||||
contactDeleted.setValue(false);
|
contactDeleted.setValue(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCleared() {
|
||||||
|
super.onCleared();
|
||||||
|
attachmentCreator.deleteUnsentAttachments();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setting the {@link ContactId} automatically triggers loading of other
|
* Setting the {@link ContactId} automatically triggers loading of other
|
||||||
* data.
|
* data.
|
||||||
@@ -176,25 +186,37 @@ public class ConversationViewModel extends AndroidViewModel {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendMessage(@Nullable String text, List<Uri> uris, long timestamp) {
|
void sendMessage(@Nullable String text,
|
||||||
if (messagingGroupId.getValue() == null) loadGroupId();
|
List<AttachmentHeader> attachmentHeaders, long timestamp) {
|
||||||
|
// messagingGroupId is loaded with the contact
|
||||||
observeForeverOnce(messagingGroupId, groupId -> {
|
observeForeverOnce(messagingGroupId, groupId -> {
|
||||||
if (groupId == null) return;
|
if (groupId == null) throw new IllegalStateException();
|
||||||
// calls through to creating and storing the message
|
createMessage(groupId, text, attachmentHeaders, timestamp);
|
||||||
storeAttachments(groupId, text, uris, timestamp);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadGroupId() {
|
@Override
|
||||||
if (contactId == null) throw new IllegalStateException();
|
@UiThread
|
||||||
dbExecutor.execute(() -> {
|
public LiveData<AttachmentResult> storeAttachments(Collection<Uri> uris,
|
||||||
try {
|
boolean restart) {
|
||||||
messagingGroupId.postValue(
|
if (restart) {
|
||||||
messagingManager.getConversationId(contactId));
|
return attachmentCreator.getLiveAttachments();
|
||||||
} catch (DbException e) {
|
} else {
|
||||||
logException(LOG, WARNING, e);
|
// messagingGroupId is loaded with the contact
|
||||||
}
|
return attachmentCreator.storeAttachments(messagingGroupId, uris);
|
||||||
});
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@UiThread
|
||||||
|
public List<AttachmentHeader> getAttachmentHeadersForSending() {
|
||||||
|
return attachmentCreator.getAttachmentHeadersForSending();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@UiThread
|
||||||
|
public void cancel() {
|
||||||
|
attachmentCreator.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
@DatabaseExecutor
|
@DatabaseExecutor
|
||||||
@@ -252,58 +274,8 @@ public class ConversationViewModel extends AndroidViewModel {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void storeAttachments(GroupId groupId, @Nullable String text,
|
|
||||||
List<Uri> uris, long timestamp) {
|
|
||||||
dbExecutor.execute(() -> {
|
|
||||||
long start = now();
|
|
||||||
List<AttachmentHeader> attachments = new ArrayList<>();
|
|
||||||
List<AttachmentItem> items = new ArrayList<>();
|
|
||||||
boolean needsSize = uris.size() == 1;
|
|
||||||
for (Uri uri : uris) {
|
|
||||||
Pair<AttachmentHeader, AttachmentItem> pair =
|
|
||||||
createAttachmentHeader(groupId, uri, timestamp,
|
|
||||||
needsSize);
|
|
||||||
if (pair == null) continue;
|
|
||||||
attachments.add(pair.getFirst());
|
|
||||||
items.add(pair.getSecond());
|
|
||||||
}
|
|
||||||
logDuration(LOG, "Storing attachments", start);
|
|
||||||
createMessage(groupId, text, attachments, items, timestamp);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@DatabaseExecutor
|
|
||||||
private Pair<AttachmentHeader, AttachmentItem> createAttachmentHeader(
|
|
||||||
GroupId groupId, Uri uri, long timestamp, boolean needsSize) {
|
|
||||||
InputStream is = null;
|
|
||||||
try {
|
|
||||||
ContentResolver contentResolver =
|
|
||||||
getApplication().getContentResolver();
|
|
||||||
is = contentResolver.openInputStream(uri);
|
|
||||||
if (is == null) throw new IOException();
|
|
||||||
String contentType = contentResolver.getType(uri);
|
|
||||||
if (contentType == null) throw new IOException("null content type");
|
|
||||||
AttachmentHeader h = messagingManager
|
|
||||||
.addLocalAttachment(groupId, timestamp, contentType, is);
|
|
||||||
is.close();
|
|
||||||
// re-open stream to get AttachmentItem
|
|
||||||
is = contentResolver.openInputStream(uri);
|
|
||||||
if (is == null) throw new IOException();
|
|
||||||
AttachmentItem item = attachmentController
|
|
||||||
.getAttachmentItem(h, new Attachment(is), needsSize);
|
|
||||||
return new Pair<>(h, item);
|
|
||||||
} catch (DbException | IOException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
return null;
|
|
||||||
} finally {
|
|
||||||
if (is != null) tryToClose(is, LOG, WARNING);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createMessage(GroupId groupId, @Nullable String text,
|
private void createMessage(GroupId groupId, @Nullable String text,
|
||||||
List<AttachmentHeader> attachments, List<AttachmentItem> aItems,
|
List<AttachmentHeader> attachments, long timestamp) {
|
||||||
long timestamp) {
|
|
||||||
cryptoExecutor.execute(() -> {
|
cryptoExecutor.execute(() -> {
|
||||||
try {
|
try {
|
||||||
// TODO remove when text can be null in the backend
|
// TODO remove when text can be null in the backend
|
||||||
@@ -311,7 +283,6 @@ public class ConversationViewModel extends AndroidViewModel {
|
|||||||
PrivateMessage pm = privateMessageFactory
|
PrivateMessage pm = privateMessageFactory
|
||||||
.createPrivateMessage(groupId, timestamp, msgText,
|
.createPrivateMessage(groupId, timestamp, msgText,
|
||||||
attachments);
|
attachments);
|
||||||
attachmentController.put(pm.getMessage().getId(), aItems);
|
|
||||||
storeMessage(pm, msgText, attachments);
|
storeMessage(pm, msgText, attachments);
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
@@ -321,6 +292,7 @@ public class ConversationViewModel extends AndroidViewModel {
|
|||||||
|
|
||||||
private void storeMessage(PrivateMessage m, @Nullable String text,
|
private void storeMessage(PrivateMessage m, @Nullable String text,
|
||||||
List<AttachmentHeader> attachments) {
|
List<AttachmentHeader> attachments) {
|
||||||
|
attachmentCreator.onAttachmentsSent(m.getMessage().getId());
|
||||||
dbExecutor.execute(() -> {
|
dbExecutor.execute(() -> {
|
||||||
try {
|
try {
|
||||||
long start = now();
|
long start = now();
|
||||||
@@ -332,20 +304,15 @@ public class ConversationViewModel extends AndroidViewModel {
|
|||||||
message.getTimestamp(), true, true, false, false,
|
message.getTimestamp(), true, true, false, false,
|
||||||
text != null, attachments);
|
text != null, attachments);
|
||||||
// TODO add text to cache when available here
|
// TODO add text to cache when available here
|
||||||
addedHeader.postValue(h);
|
addedHeader.postEvent(h);
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@UiThread
|
AttachmentRetriever getAttachmentRetriever() {
|
||||||
void onAddedPrivateMessageSeen() {
|
return attachmentRetriever;
|
||||||
addedHeader.setValue(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
AttachmentController getAttachmentController() {
|
|
||||||
return attachmentController;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LiveData<Contact> getContact() {
|
LiveData<Contact> getContact() {
|
||||||
@@ -380,7 +347,7 @@ public class ConversationViewModel extends AndroidViewModel {
|
|||||||
return contactDeleted;
|
return contactDeleted;
|
||||||
}
|
}
|
||||||
|
|
||||||
LiveData<PrivateMessageHeader> getAddedPrivateMessage() {
|
LiveEvent<PrivateMessageHeader> getAddedPrivateMessage() {
|
||||||
return addedHeader;
|
return addedHeader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import android.support.annotation.UiThread;
|
|||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.briar.R;
|
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.BlogInvitationRequest;
|
||||||
import org.briarproject.briar.api.blog.BlogInvitationResponse;
|
import org.briarproject.briar.api.blog.BlogInvitationResponse;
|
||||||
import org.briarproject.briar.api.conversation.ConversationMessageVisitor;
|
import org.briarproject.briar.api.conversation.ConversationMessageVisitor;
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
|||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
import org.briarproject.briar.android.activity.BriarActivity;
|
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.util.BriarSnackbarBuilder;
|
||||||
import org.briarproject.briar.android.view.PullDownLayout;
|
import org.briarproject.briar.android.view.PullDownLayout;
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import android.view.WindowManager;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
|
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||||
import org.briarproject.briar.android.conversation.glide.Radii;
|
import org.briarproject.briar.android.conversation.glide.Radii;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import com.github.chrisbanes.photoview.PhotoView;
|
|||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.activity.BaseActivity;
|
import org.briarproject.briar.android.activity.BaseActivity;
|
||||||
|
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||||
import org.briarproject.briar.android.conversation.glide.GlideApp;
|
import org.briarproject.briar.android.conversation.glide.GlideApp;
|
||||||
|
|
||||||
import javax.annotation.ParametersAreNonnullByDefault;
|
import javax.annotation.ParametersAreNonnullByDefault;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import com.bumptech.glide.load.Transformation;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
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.BriarImageTransformation;
|
||||||
import org.briarproject.briar.android.conversation.glide.GlideApp;
|
import org.briarproject.briar.android.conversation.glide.GlideApp;
|
||||||
import org.briarproject.briar.android.conversation.glide.Radii;
|
import org.briarproject.briar.android.conversation.glide.Radii;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import org.briarproject.bramble.api.db.DbException;
|
|||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
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.LiveEvent;
|
||||||
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
|
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
|
||||||
import org.briarproject.briar.api.messaging.Attachment;
|
import org.briarproject.briar.api.messaging.Attachment;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import org.briarproject.bramble.api.db.DatabaseExecutor;
|
|||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
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 org.briarproject.briar.api.messaging.MessagingManager;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package org.briarproject.briar.android.conversation.glide;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
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 org.briarproject.briar.api.messaging.MessagingManager;
|
||||||
|
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import com.bumptech.glide.module.AppGlideModule;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.android.BriarApplication;
|
import org.briarproject.briar.android.BriarApplication;
|
||||||
import org.briarproject.briar.android.conversation.AttachmentItem;
|
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import com.bumptech.glide.signature.ObjectKey;
|
|||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.briar.android.BriarApplication;
|
import org.briarproject.briar.android.BriarApplication;
|
||||||
import org.briarproject.briar.android.conversation.AttachmentItem;
|
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import com.bumptech.glide.load.model.MultiModelLoaderFactory;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.android.BriarApplication;
|
import org.briarproject.briar.android.BriarApplication;
|
||||||
import org.briarproject.briar.android.conversation.AttachmentItem;
|
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.briarproject.briar.android.introduction;
|
package org.briarproject.briar.android.introduction;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v7.app.ActionBar;
|
import android.support.v7.app.ActionBar;
|
||||||
@@ -26,6 +25,7 @@ import org.briarproject.briar.android.view.TextInputView;
|
|||||||
import org.briarproject.briar.android.view.TextSendController;
|
import org.briarproject.briar.android.view.TextSendController;
|
||||||
import org.briarproject.briar.android.view.TextSendController.SendListener;
|
import org.briarproject.briar.android.view.TextSendController.SendListener;
|
||||||
import org.briarproject.briar.api.introduction.IntroductionManager;
|
import org.briarproject.briar.api.introduction.IntroductionManager;
|
||||||
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
@@ -193,7 +193,8 @@ public class IntroductionMessageFragment extends BaseFragment
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSendClick(@Nullable String text, List<Uri> imageUris) {
|
public void onSendClick(@Nullable String text,
|
||||||
|
List<AttachmentHeader> headers) {
|
||||||
// disable button to prevent accidental double invitations
|
// disable button to prevent accidental double invitations
|
||||||
ui.message.setReady(false);
|
ui.message.setReady(false);
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.briarproject.briar.android.sharing;
|
package org.briarproject.briar.android.sharing;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.annotation.StringRes;
|
import android.support.annotation.StringRes;
|
||||||
@@ -19,6 +18,7 @@ import org.briarproject.briar.android.fragment.BaseFragment;
|
|||||||
import org.briarproject.briar.android.view.LargeTextInputView;
|
import org.briarproject.briar.android.view.LargeTextInputView;
|
||||||
import org.briarproject.briar.android.view.TextSendController;
|
import org.briarproject.briar.android.view.TextSendController;
|
||||||
import org.briarproject.briar.android.view.TextSendController.SendListener;
|
import org.briarproject.briar.android.view.TextSendController.SendListener;
|
||||||
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -83,7 +83,8 @@ public abstract class BaseMessageFragment extends BaseFragment
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSendClick(@Nullable String text, List<Uri> imageUris) {
|
public void onSendClick(@Nullable String text,
|
||||||
|
List<AttachmentHeader> headers) {
|
||||||
// disable button to prevent accidental double actions
|
// disable button to prevent accidental double actions
|
||||||
sendController.setReady(false);
|
sendController.setReady(false);
|
||||||
message.hideSoftKeyboard();
|
message.hideSoftKeyboard();
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.briarproject.briar.android.threaded;
|
package org.briarproject.briar.android.threaded;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
import android.support.annotation.CallSuper;
|
import android.support.annotation.CallSuper;
|
||||||
@@ -34,6 +33,7 @@ import org.briarproject.briar.android.view.TextSendController;
|
|||||||
import org.briarproject.briar.android.view.TextSendController.SendListener;
|
import org.briarproject.briar.android.view.TextSendController.SendListener;
|
||||||
import org.briarproject.briar.android.view.UnreadMessageButton;
|
import org.briarproject.briar.android.view.UnreadMessageButton;
|
||||||
import org.briarproject.briar.api.client.NamedGroup;
|
import org.briarproject.briar.api.client.NamedGroup;
|
||||||
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -341,7 +341,8 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSendClick(@Nullable String text, List<Uri> imageUris) {
|
public void onSendClick(@Nullable String text,
|
||||||
|
List<AttachmentHeader> headers) {
|
||||||
if (isNullOrEmpty(text)) throw new AssertionError();
|
if (isNullOrEmpty(text)) throw new AssertionError();
|
||||||
|
|
||||||
I replyItem = adapter.getHighlightedItem();
|
I replyItem = adapter.getHighlightedItem();
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.briarproject.briar.android.view;
|
package org.briarproject.briar.android.view;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.Uri;
|
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.constraint.ConstraintLayout;
|
import android.support.constraint.ConstraintLayout;
|
||||||
import android.support.v7.widget.RecyclerView;
|
import android.support.v7.widget.RecyclerView;
|
||||||
@@ -10,11 +9,13 @@ import android.view.LayoutInflater;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
|
import org.briarproject.briar.android.attachment.AttachmentItemResult;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
import static android.content.Context.LAYOUT_INFLATER_SERVICE;
|
import static android.content.Context.LAYOUT_INFLATER_SERVICE;
|
||||||
import static android.support.v4.content.ContextCompat.getColor;
|
import static android.support.v4.content.ContextCompat.getColor;
|
||||||
|
import static android.support.v7.widget.RecyclerView.NO_POSITION;
|
||||||
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
|
|
||||||
@@ -60,34 +61,28 @@ public class ImagePreview extends ConstraintLayout {
|
|||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
void showPreview(Collection<Uri> imageUris) {
|
void showPreview(Collection<ImagePreviewItem> items) {
|
||||||
if (listener == null) throw new IllegalStateException();
|
if (listener == null) throw new IllegalStateException();
|
||||||
if (imageUris.size() == 1) {
|
if (items.size() == 1) {
|
||||||
LayoutParams params = (LayoutParams) imageList.getLayoutParams();
|
LayoutParams params = (LayoutParams) imageList.getLayoutParams();
|
||||||
params.width = MATCH_PARENT;
|
params.width = MATCH_PARENT;
|
||||||
imageList.setLayoutParams(params);
|
imageList.setLayoutParams(params);
|
||||||
}
|
}
|
||||||
setVisibility(VISIBLE);
|
setVisibility(VISIBLE);
|
||||||
imageList.setAdapter(new ImagePreviewAdapter(imageUris, listener));
|
ImagePreviewAdapter adapter = new ImagePreviewAdapter(items);
|
||||||
|
imageList.setAdapter(adapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeUri(Uri uri) {
|
void loadPreviewImage(AttachmentItemResult result) {
|
||||||
ImagePreviewAdapter adapter =
|
ImagePreviewAdapter adapter =
|
||||||
(ImagePreviewAdapter) imageList.getAdapter();
|
((ImagePreviewAdapter) imageList.getAdapter());
|
||||||
requireNonNull(adapter).removeUri(uri);
|
int pos = requireNonNull(adapter).loadItemPreview(result);
|
||||||
|
if (pos != NO_POSITION) {
|
||||||
|
imageList.smoothScrollToPosition(pos);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ImagePreviewListener {
|
interface ImagePreviewListener {
|
||||||
|
|
||||||
void onPreviewLoaded();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when Glide can't load a preview image.
|
|
||||||
*
|
|
||||||
* Warning: Glide may call this multiple times.
|
|
||||||
*/
|
|
||||||
void onUriError(Uri uri);
|
|
||||||
|
|
||||||
void onCancel();
|
void onCancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package org.briarproject.briar.android.view;
|
package org.briarproject.briar.android.view;
|
||||||
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.support.annotation.LayoutRes;
|
import android.support.annotation.LayoutRes;
|
||||||
import android.support.v7.widget.RecyclerView.Adapter;
|
import android.support.v7.widget.RecyclerView.Adapter;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@@ -9,25 +8,24 @@ import android.view.ViewGroup;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.view.ImagePreview.ImagePreviewListener;
|
import org.briarproject.briar.android.attachment.AttachmentItemResult;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import static android.support.v7.widget.RecyclerView.NO_POSITION;
|
||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class ImagePreviewAdapter extends Adapter<ImagePreviewViewHolder> {
|
class ImagePreviewAdapter extends Adapter<ImagePreviewViewHolder> {
|
||||||
|
|
||||||
private final List<Uri> items;
|
private final List<ImagePreviewItem> items;
|
||||||
private final ImagePreviewListener listener;
|
|
||||||
@LayoutRes
|
@LayoutRes
|
||||||
private final int layout;
|
private final int layout;
|
||||||
|
|
||||||
ImagePreviewAdapter(Collection<Uri> items, ImagePreviewListener listener) {
|
ImagePreviewAdapter(Collection<ImagePreviewItem> items) {
|
||||||
this.items = new ArrayList<>(items);
|
this.items = new ArrayList<>(items);
|
||||||
this.listener = listener;
|
|
||||||
this.layout = items.size() == 1 ?
|
this.layout = items.size() == 1 ?
|
||||||
R.layout.list_item_image_preview_single :
|
R.layout.list_item_image_preview_single :
|
||||||
R.layout.list_item_image_preview;
|
R.layout.list_item_image_preview;
|
||||||
@@ -38,7 +36,7 @@ class ImagePreviewAdapter extends Adapter<ImagePreviewViewHolder> {
|
|||||||
int type) {
|
int type) {
|
||||||
View v = LayoutInflater.from(viewGroup.getContext())
|
View v = LayoutInflater.from(viewGroup.getContext())
|
||||||
.inflate(layout, viewGroup, false);
|
.inflate(layout, viewGroup, false);
|
||||||
return new ImagePreviewViewHolder(v, requireNonNull(listener));
|
return new ImagePreviewViewHolder(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -52,11 +50,17 @@ class ImagePreviewAdapter extends Adapter<ImagePreviewViewHolder> {
|
|||||||
return items.size();
|
return items.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeUri(Uri uri) {
|
int loadItemPreview(AttachmentItemResult result) {
|
||||||
int pos = items.indexOf(uri);
|
ImagePreviewItem newItem = new ImagePreviewItem(result.getUri());
|
||||||
if (pos == -1) return;
|
int pos = items.indexOf(newItem);
|
||||||
items.remove(uri);
|
if (pos == NO_POSITION) throw new AssertionError();
|
||||||
notifyItemRemoved(pos);
|
ImagePreviewItem item = items.get(pos);
|
||||||
|
if (item.getItem() == null) {
|
||||||
|
item.setItem(requireNonNull(result.getItem()));
|
||||||
|
notifyItemChanged(pos, item);
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
return NO_POSITION;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package org.briarproject.briar.android.view;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
class ImagePreviewItem {
|
||||||
|
|
||||||
|
private final Uri uri;
|
||||||
|
@Nullable
|
||||||
|
private AttachmentItem item;
|
||||||
|
|
||||||
|
ImagePreviewItem(Uri uri) {
|
||||||
|
this.uri = uri;
|
||||||
|
this.item = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<ImagePreviewItem> fromUris(Collection<Uri> uris) {
|
||||||
|
List<ImagePreviewItem> items = new ArrayList<>(uris.size());
|
||||||
|
for (Uri uri : uris) {
|
||||||
|
items.add(new ImagePreviewItem(uri));
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setItem(AttachmentItem item) {
|
||||||
|
this.item = item;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public AttachmentItem getItem() {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(@Nullable Object o) {
|
||||||
|
return o instanceof ImagePreviewItem &&
|
||||||
|
uri.equals(((ImagePreviewItem) o).uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return uri.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.briarproject.briar.android.view;
|
package org.briarproject.briar.android.view;
|
||||||
|
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.net.Uri;
|
|
||||||
import android.support.annotation.DrawableRes;
|
import android.support.annotation.DrawableRes;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v7.widget.RecyclerView.ViewHolder;
|
import android.support.v7.widget.RecyclerView.ViewHolder;
|
||||||
@@ -17,9 +16,9 @@ import com.bumptech.glide.request.target.Target;
|
|||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.conversation.glide.GlideApp;
|
import org.briarproject.briar.android.conversation.glide.GlideApp;
|
||||||
import org.briarproject.briar.android.view.ImagePreview.ImagePreviewListener;
|
|
||||||
|
|
||||||
import static android.view.View.INVISIBLE;
|
import static android.view.View.INVISIBLE;
|
||||||
|
import static android.view.View.VISIBLE;
|
||||||
import static com.bumptech.glide.load.engine.DiskCacheStrategy.NONE;
|
import static com.bumptech.glide.load.engine.DiskCacheStrategy.NONE;
|
||||||
import static com.bumptech.glide.load.resource.bitmap.DownsampleStrategy.FIT_CENTER;
|
import static com.bumptech.glide.load.resource.bitmap.DownsampleStrategy.FIT_CENTER;
|
||||||
import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade;
|
import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade;
|
||||||
@@ -30,45 +29,47 @@ class ImagePreviewViewHolder extends ViewHolder {
|
|||||||
@DrawableRes
|
@DrawableRes
|
||||||
private static final int ERROR_RES = R.drawable.ic_image_broken;
|
private static final int ERROR_RES = R.drawable.ic_image_broken;
|
||||||
|
|
||||||
private final ImagePreviewListener listener;
|
|
||||||
|
|
||||||
private final ImageView imageView;
|
private final ImageView imageView;
|
||||||
private final ProgressBar progressBar;
|
private final ProgressBar progressBar;
|
||||||
|
|
||||||
ImagePreviewViewHolder(View v, ImagePreviewListener listener) {
|
ImagePreviewViewHolder(View v) {
|
||||||
super(v);
|
super(v);
|
||||||
this.listener = listener;
|
|
||||||
this.imageView = v.findViewById(R.id.imageView);
|
this.imageView = v.findViewById(R.id.imageView);
|
||||||
this.progressBar = v.findViewById(R.id.progressBar);
|
this.progressBar = v.findViewById(R.id.progressBar);
|
||||||
}
|
}
|
||||||
|
|
||||||
void bind(Uri uri) {
|
void bind(ImagePreviewItem item) {
|
||||||
GlideApp.with(imageView)
|
if (item.getItem() == null) {
|
||||||
.load(uri)
|
progressBar.setVisibility(VISIBLE);
|
||||||
.diskCacheStrategy(NONE)
|
GlideApp.with(imageView)
|
||||||
.error(ERROR_RES)
|
.clear(imageView);
|
||||||
.downsample(FIT_CENTER)
|
} else {
|
||||||
.transition(withCrossFade())
|
GlideApp.with(imageView)
|
||||||
.addListener(new RequestListener<Drawable>() {
|
.load(item.getItem())
|
||||||
@Override
|
.diskCacheStrategy(NONE)
|
||||||
public boolean onLoadFailed(@Nullable GlideException e,
|
.error(ERROR_RES)
|
||||||
Object model, Target<Drawable> target,
|
.downsample(FIT_CENTER)
|
||||||
boolean isFirstResource) {
|
.transition(withCrossFade())
|
||||||
progressBar.setVisibility(INVISIBLE);
|
.addListener(new RequestListener<Drawable>() {
|
||||||
listener.onUriError(uri);
|
@Override
|
||||||
return false;
|
public boolean onLoadFailed(@Nullable GlideException e,
|
||||||
}
|
Object model, Target<Drawable> target,
|
||||||
|
boolean isFirstResource) {
|
||||||
|
progressBar.setVisibility(INVISIBLE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onResourceReady(Drawable resource,
|
public boolean onResourceReady(Drawable resource,
|
||||||
Object model, Target<Drawable> target,
|
Object model, Target<Drawable> target,
|
||||||
DataSource dataSource, boolean isFirstResource) {
|
DataSource dataSource,
|
||||||
progressBar.setVisibility(INVISIBLE);
|
boolean isFirstResource) {
|
||||||
listener.onPreviewLoaded();
|
progressBar.setVisibility(INVISIBLE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.into(imageView);
|
.into(imageView);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package org.briarproject.briar.android.view;
|
package org.briarproject.briar.android.view;
|
||||||
|
|
||||||
import android.app.Activity;
|
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.ClipData;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
@@ -15,26 +18,32 @@ import android.widget.Toast;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
|
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.android.view.ImagePreview.ImagePreviewListener;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt;
|
import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt;
|
||||||
import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.PromptStateChangeListener;
|
import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.PromptStateChangeListener;
|
||||||
|
|
||||||
|
import static android.arch.lifecycle.Lifecycle.State.DESTROYED;
|
||||||
import static android.content.Intent.ACTION_GET_CONTENT;
|
import static android.content.Intent.ACTION_GET_CONTENT;
|
||||||
import static android.content.Intent.ACTION_OPEN_DOCUMENT;
|
import static android.content.Intent.ACTION_OPEN_DOCUMENT;
|
||||||
import static android.content.Intent.CATEGORY_OPENABLE;
|
import static android.content.Intent.CATEGORY_OPENABLE;
|
||||||
import static android.content.Intent.EXTRA_ALLOW_MULTIPLE;
|
import static android.content.Intent.EXTRA_ALLOW_MULTIPLE;
|
||||||
|
import static android.content.Intent.EXTRA_MIME_TYPES;
|
||||||
import static android.os.Build.VERSION.SDK_INT;
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
import static android.support.v4.content.ContextCompat.getColor;
|
import static android.support.v4.content.ContextCompat.getColor;
|
||||||
import static android.support.v4.view.AbsSavedState.EMPTY_STATE;
|
import static android.support.v4.view.AbsSavedState.EMPTY_STATE;
|
||||||
import static android.view.View.GONE;
|
import static android.view.View.GONE;
|
||||||
import static android.widget.Toast.LENGTH_LONG;
|
import static android.widget.Toast.LENGTH_LONG;
|
||||||
import static java.util.Collections.emptyList;
|
|
||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.resolveColorAttribute;
|
import static org.briarproject.briar.android.util.UiUtils.resolveColorAttribute;
|
||||||
|
import static org.briarproject.briar.api.messaging.MessagingConstants.IMAGE_MIME_TYPES;
|
||||||
import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.STATE_DISMISSED;
|
import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.STATE_DISMISSED;
|
||||||
import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.STATE_FINISHED;
|
import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.STATE_FINISHED;
|
||||||
|
|
||||||
@@ -46,17 +55,19 @@ public class TextAttachmentController extends TextSendController
|
|||||||
private final ImagePreview imagePreview;
|
private final ImagePreview imagePreview;
|
||||||
private final AttachImageListener imageListener;
|
private final AttachImageListener imageListener;
|
||||||
private final CompositeSendButton sendButton;
|
private final CompositeSendButton sendButton;
|
||||||
|
private final AttachmentManager attachmentManager;
|
||||||
|
|
||||||
private CharSequence textHint;
|
private final List<Uri> imageUris = new ArrayList<>();
|
||||||
private List<Uri> imageUris = emptyList();
|
private final CharSequence textHint;
|
||||||
private int previewsLoaded = 0;
|
private boolean loadingUris = false;
|
||||||
private boolean loadingPreviews = false;
|
|
||||||
|
|
||||||
public TextAttachmentController(TextInputView v, ImagePreview imagePreview,
|
public TextAttachmentController(TextInputView v, ImagePreview imagePreview,
|
||||||
SendListener listener, AttachImageListener imageListener) {
|
SendListener listener, AttachImageListener imageListener,
|
||||||
|
AttachmentManager attachmentManager) {
|
||||||
super(v, listener, false);
|
super(v, listener, false);
|
||||||
this.imageListener = imageListener;
|
this.imageListener = imageListener;
|
||||||
this.imagePreview = imagePreview;
|
this.imagePreview = imagePreview;
|
||||||
|
this.attachmentManager = attachmentManager;
|
||||||
this.imagePreview.setImagePreviewListener(this);
|
this.imagePreview.setImagePreviewListener(this);
|
||||||
|
|
||||||
sendButton = (CompositeSendButton) compositeSendButton;
|
sendButton = (CompositeSendButton) compositeSendButton;
|
||||||
@@ -67,10 +78,10 @@ public class TextAttachmentController extends TextSendController
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void updateViewState() {
|
protected void updateViewState() {
|
||||||
textInput.setEnabled(ready && !loadingPreviews);
|
textInput.setEnabled(ready && !loadingUris);
|
||||||
boolean sendEnabled = ready && !loadingPreviews &&
|
boolean sendEnabled = ready && !loadingUris &&
|
||||||
(!textIsEmpty || canSendEmptyText());
|
(!textIsEmpty || canSendEmptyText());
|
||||||
if (loadingPreviews) {
|
if (loadingUris) {
|
||||||
sendButton.showProgress(true);
|
sendButton.showProgress(true);
|
||||||
} else if (imageUris.isEmpty()) {
|
} else if (imageUris.isEmpty()) {
|
||||||
sendButton.showProgress(false);
|
sendButton.showProgress(false);
|
||||||
@@ -84,7 +95,9 @@ public class TextAttachmentController extends TextSendController
|
|||||||
@Override
|
@Override
|
||||||
public void onSendEvent() {
|
public void onSendEvent() {
|
||||||
if (canSend()) {
|
if (canSend()) {
|
||||||
listener.onSendClick(textInput.getText(), imageUris);
|
if (loadingUris) throw new AssertionError();
|
||||||
|
listener.onSendClick(textInput.getText(),
|
||||||
|
attachmentManager.getAttachmentHeadersForSending());
|
||||||
reset();
|
reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -110,36 +123,95 @@ public class TextAttachmentController extends TextSendController
|
|||||||
builder.show();
|
builder.show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Intent intent = new Intent(SDK_INT >= 19 ?
|
Intent intent = getAttachFileIntent();
|
||||||
ACTION_OPEN_DOCUMENT : ACTION_GET_CONTENT);
|
if (imageListener.getLifecycle().getCurrentState() != DESTROYED) {
|
||||||
intent.addCategory(CATEGORY_OPENABLE);
|
requireNonNull(imageListener).onAttachImage(intent);
|
||||||
intent.setType("image/*");
|
|
||||||
if (SDK_INT >= 18) intent.putExtra(EXTRA_ALLOW_MULTIPLE, true);
|
|
||||||
requireNonNull(imageListener).onAttachImage(intent);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onImageReceived(@Nullable Intent resultData) {
|
|
||||||
if (resultData == null) return;
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
onNewUris();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onNewUris() {
|
private Intent getAttachFileIntent() {
|
||||||
|
Intent intent = new Intent(SDK_INT >= 19 ?
|
||||||
|
ACTION_OPEN_DOCUMENT : ACTION_GET_CONTENT);
|
||||||
|
intent.setType("image/*");
|
||||||
|
intent.addCategory(CATEGORY_OPENABLE);
|
||||||
|
if (SDK_INT >= 19) intent.putExtra(EXTRA_MIME_TYPES, IMAGE_MIME_TYPES);
|
||||||
|
if (SDK_INT >= 18) intent.putExtra(EXTRA_ALLOW_MULTIPLE, true);
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is called with the result Intent
|
||||||
|
* returned by the Activity started with {@link #getAttachFileIntent()}.
|
||||||
|
* <p>
|
||||||
|
* This method must be called at most once per call to
|
||||||
|
* {@link AttachImageListener#onAttachImage(Intent)}.
|
||||||
|
* Normally, this is true if called from
|
||||||
|
* {@link Activity#onActivityResult(int, int, Intent)} since this is called
|
||||||
|
* at most once per call to {@link Activity#startActivityForResult(Intent, int)}.
|
||||||
|
*/
|
||||||
|
public void onImageReceived(@Nullable Intent resultData) {
|
||||||
|
if (resultData == null) return;
|
||||||
|
if (loadingUris || !imageUris.isEmpty()) throw new AssertionError();
|
||||||
|
if (resultData.getData() != null) {
|
||||||
|
imageUris.add(resultData.getData());
|
||||||
|
onNewUris(false);
|
||||||
|
} else if (SDK_INT >= 18 && resultData.getClipData() != null) {
|
||||||
|
ClipData clipData = resultData.getClipData();
|
||||||
|
for (int i = 0; i < clipData.getItemCount(); i++) {
|
||||||
|
imageUris.add(clipData.getItemAt(i).getUri());
|
||||||
|
}
|
||||||
|
onNewUris(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onNewUris(boolean restart) {
|
||||||
if (imageUris.isEmpty()) return;
|
if (imageUris.isEmpty()) return;
|
||||||
loadingPreviews = true;
|
if (loadingUris) throw new AssertionError();
|
||||||
|
loadingUris = true;
|
||||||
updateViewState();
|
updateViewState();
|
||||||
textInput.setHint(R.string.image_caption_hint);
|
textInput.setHint(R.string.image_caption_hint);
|
||||||
imagePreview.showPreview(imageUris);
|
List<ImagePreviewItem> items = ImagePreviewItem.fromUris(imageUris);
|
||||||
|
imagePreview.showPreview(items);
|
||||||
|
// store attachments and show preview when successful
|
||||||
|
LiveData<AttachmentResult> result =
|
||||||
|
attachmentManager.storeAttachments(imageUris, restart);
|
||||||
|
result.observe(imageListener, new Observer<AttachmentResult>() {
|
||||||
|
@Override
|
||||||
|
public void onChanged(@Nullable AttachmentResult attachmentResult) {
|
||||||
|
if (attachmentResult == null) {
|
||||||
|
// The fresh LiveData was deliberately set to null.
|
||||||
|
// This means that we can stop observing it.
|
||||||
|
result.removeObserver(this);
|
||||||
|
} else {
|
||||||
|
boolean noError = onNewAttachmentItemResults(
|
||||||
|
attachmentResult.getItemResults());
|
||||||
|
if (noError && attachmentResult.isFinished()) {
|
||||||
|
onAllAttachmentsCreated();
|
||||||
|
result.removeObserver(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean onNewAttachmentItemResults(
|
||||||
|
Collection<AttachmentItemResult> itemResults) {
|
||||||
|
if (!loadingUris) throw new AssertionError();
|
||||||
|
for (AttachmentItemResult result : itemResults) {
|
||||||
|
if (result.hasError()) {
|
||||||
|
onError(result.getErrorMsg());
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
imagePreview.loadPreviewImage(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onAllAttachmentsCreated() {
|
||||||
|
if (!loadingUris) throw new AssertionError();
|
||||||
|
loadingUris = false;
|
||||||
|
updateViewState();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reset() {
|
private void reset() {
|
||||||
@@ -148,10 +220,9 @@ public class TextAttachmentController extends TextSendController
|
|||||||
// hide image layout
|
// hide image layout
|
||||||
imagePreview.setVisibility(GONE);
|
imagePreview.setVisibility(GONE);
|
||||||
// reset image URIs
|
// reset image URIs
|
||||||
imageUris = emptyList();
|
imageUris.clear();
|
||||||
// no preview has been loaded
|
// definitely not loading anymore
|
||||||
previewsLoaded = 0;
|
loadingUris = false;
|
||||||
loadingPreviews = false;
|
|
||||||
// show the image button again, so images can get attached
|
// show the image button again, so images can get attached
|
||||||
updateViewState();
|
updateViewState();
|
||||||
}
|
}
|
||||||
@@ -168,45 +239,29 @@ public class TextAttachmentController extends TextSendController
|
|||||||
@Nullable
|
@Nullable
|
||||||
public Parcelable onRestoreInstanceState(Parcelable inState) {
|
public Parcelable onRestoreInstanceState(Parcelable inState) {
|
||||||
SavedState state = (SavedState) inState;
|
SavedState state = (SavedState) inState;
|
||||||
imageUris = requireNonNull(state.imageUris);
|
if (!imageUris.isEmpty()) throw new AssertionError();
|
||||||
onNewUris();
|
if (state.imageUris != null) imageUris.addAll(state.imageUris);
|
||||||
|
onNewUris(true);
|
||||||
return state.getSuperState();
|
return state.getSuperState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@UiThread
|
||||||
public void onPreviewLoaded() {
|
private void onError(@Nullable String errorMsg) {
|
||||||
previewsLoaded++;
|
if (errorMsg == null) {
|
||||||
checkAllPreviewsLoaded();
|
errorMsg = imagePreview.getContext()
|
||||||
}
|
.getString(R.string.image_attach_error);
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onUriError(Uri uri) {
|
|
||||||
boolean removed = imageUris.remove(uri);
|
|
||||||
if (!removed) {
|
|
||||||
// we have removed this Uri already, do not remove it again
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
imagePreview.removeUri(uri);
|
Toast.makeText(textInput.getContext(), errorMsg, LENGTH_LONG).show();
|
||||||
if (imageUris.isEmpty()) onCancel();
|
onCancel();
|
||||||
Toast.makeText(textInput.getContext(), R.string.image_attach_error,
|
|
||||||
LENGTH_LONG).show();
|
|
||||||
checkAllPreviewsLoaded();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCancel() {
|
public void onCancel() {
|
||||||
textInput.clearText();
|
textInput.clearText();
|
||||||
|
attachmentManager.cancel();
|
||||||
reset();
|
reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkAllPreviewsLoaded() {
|
|
||||||
if (previewsLoaded == imageUris.size()) {
|
|
||||||
loadingPreviews = false;
|
|
||||||
// all previews were loaded
|
|
||||||
updateViewState();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void showImageOnboarding(Activity activity,
|
public void showImageOnboarding(Activity activity,
|
||||||
Runnable onOnboardingSeen) {
|
Runnable onOnboardingSeen) {
|
||||||
PromptStateChangeListener listener = (prompt, state) -> {
|
PromptStateChangeListener listener = (prompt, state) -> {
|
||||||
@@ -261,7 +316,7 @@ public class TextAttachmentController extends TextSendController
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface AttachImageListener {
|
public interface AttachImageListener extends LifecycleOwner {
|
||||||
void onAttachImage(Intent intent);
|
void onAttachImage(Intent intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package org.briarproject.briar.android.view;
|
package org.briarproject.briar.android.view;
|
||||||
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.annotation.UiThread;
|
import android.support.annotation.UiThread;
|
||||||
@@ -10,6 +9,7 @@ import android.view.View;
|
|||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.view.EmojiTextInputView.TextInputListener;
|
import org.briarproject.briar.android.view.EmojiTextInputView.TextInputListener;
|
||||||
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -85,7 +85,7 @@ public class TextSendController implements TextInputListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public interface SendListener {
|
public interface SendListener {
|
||||||
void onSendClick(@Nullable String text, List<Uri> imageUris);
|
void onSendClick(@Nullable String text, List<AttachmentHeader> headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,7 +129,9 @@
|
|||||||
<string name="message_hint">Type message</string>
|
<string name="message_hint">Type message</string>
|
||||||
<string name="image_caption_hint">Add a caption (optional)</string>
|
<string name="image_caption_hint">Add a caption (optional)</string>
|
||||||
<string name="image_attach">Attach image</string>
|
<string name="image_attach">Attach image</string>
|
||||||
<string name="image_attach_error">Could not attach image</string>
|
<string name="image_attach_error">Could not attach image(s)</string>
|
||||||
|
<string name="image_attach_error_too_big">Image too big. Limit is %d MB.</string>
|
||||||
|
<string name="image_attach_error_invalid_mime_type">Image format unsupported: %s</string>
|
||||||
<string name="set_contact_alias">Change contact name</string>
|
<string name="set_contact_alias">Change contact name</string>
|
||||||
<string name="set_contact_alias_hint">Contact name</string>
|
<string name="set_contact_alias_hint">Contact name</string>
|
||||||
<string name="set_alias_button">Change</string>
|
<string name="set_alias_button">Change</string>
|
||||||
|
|||||||
@@ -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.api.sync.MessageId;
|
||||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
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.Attachment;
|
||||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
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.assertFalse;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
public class AttachmentControllerTest extends BrambleMockTestCase {
|
public class AttachmentRetrieverTest extends BrambleMockTestCase {
|
||||||
|
|
||||||
private final AttachmentDimensions dimensions = new AttachmentDimensions(
|
private final AttachmentDimensions dimensions = new AttachmentDimensions(
|
||||||
100, 50, 200, 75, 300
|
100, 50, 200, 75, 300
|
||||||
@@ -32,8 +32,8 @@ public class AttachmentControllerTest extends BrambleMockTestCase {
|
|||||||
private final MessagingManager messagingManager =
|
private final MessagingManager messagingManager =
|
||||||
context.mock(MessagingManager.class);
|
context.mock(MessagingManager.class);
|
||||||
private final ImageHelper imageHelper = context.mock(ImageHelper.class);
|
private final ImageHelper imageHelper = context.mock(ImageHelper.class);
|
||||||
private final AttachmentController controller =
|
private final AttachmentRetriever controller =
|
||||||
new AttachmentController(
|
new AttachmentRetriever(
|
||||||
messagingManager,
|
messagingManager,
|
||||||
dimensions,
|
dimensions,
|
||||||
imageHelper
|
imageHelper
|
||||||
@@ -94,23 +94,6 @@ public class AttachmentControllerTest extends BrambleMockTestCase {
|
|||||||
assertFalse(item.hasError());
|
assertFalse(item.hasError());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testImageHealsWrongMimeType() {
|
|
||||||
AttachmentHeader h = getAttachmentHeader("image/png");
|
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
|
||||||
oneOf(imageHelper).decodeStream(with(any(InputStream.class)));
|
|
||||||
will(returnValue(new DecodeResult(160, 240, "image/jpeg")));
|
|
||||||
oneOf(imageHelper).getExtensionFromMimeType("image/jpeg");
|
|
||||||
will(returnValue("jpg"));
|
|
||||||
}});
|
|
||||||
|
|
||||||
AttachmentItem item = controller.getAttachmentItem(h, attachment, true);
|
|
||||||
assertEquals("image/jpeg", item.getMimeType());
|
|
||||||
assertEquals("jpg", item.getExtension());
|
|
||||||
assertFalse(item.hasError());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBigJpegImage() {
|
public void testBigJpegImage() {
|
||||||
String mimeType = "image/jpeg";
|
String mimeType = "image/jpeg";
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.briarproject.briar.android.conversation;
|
package org.briarproject.briar.android.attachment;
|
||||||
|
|
||||||
import com.bumptech.glide.util.MarkEnforcingInputStream;
|
import com.bumptech.glide.util.MarkEnforcingInputStream;
|
||||||
|
|
||||||
@@ -25,4 +25,15 @@ public class AttachmentHeader {
|
|||||||
return contentType;
|
return contentType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
return o instanceof AttachmentHeader &&
|
||||||
|
messageId.equals(((AttachmentHeader) o).messageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return messageId.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package org.briarproject.briar.api.messaging;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class FileTooBigException extends IOException {
|
||||||
|
}
|
||||||
@@ -8,4 +8,20 @@ public interface MessagingConstants {
|
|||||||
* The maximum length of a private message's text in UTF-8 bytes.
|
* The maximum length of a private message's text in UTF-8 bytes.
|
||||||
*/
|
*/
|
||||||
int MAX_PRIVATE_MESSAGE_TEXT_LENGTH = MAX_MESSAGE_BODY_LENGTH - 1024;
|
int MAX_PRIVATE_MESSAGE_TEXT_LENGTH = MAX_MESSAGE_BODY_LENGTH - 1024;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The supported mime types for image attachments.
|
||||||
|
*/
|
||||||
|
String[] IMAGE_MIME_TYPES = {
|
||||||
|
"image/jpeg",
|
||||||
|
"image/png",
|
||||||
|
"image/gif",
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum allowed size of image attachments.
|
||||||
|
* TODO: Different limit for GIFs?
|
||||||
|
*/
|
||||||
|
int MAX_IMAGE_SIZE = MAX_MESSAGE_BODY_LENGTH; // 6 * 1024 * 1024;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,10 +37,17 @@ public interface MessagingManager extends ConversationClient {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores a local attachment message.
|
* Stores a local attachment message.
|
||||||
|
*
|
||||||
|
* @throws FileTooBigException
|
||||||
*/
|
*/
|
||||||
AttachmentHeader addLocalAttachment(GroupId groupId, long timestamp,
|
AttachmentHeader addLocalAttachment(GroupId groupId, long timestamp,
|
||||||
String contentType, InputStream is) throws DbException, IOException;
|
String contentType, InputStream is) throws DbException, IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes an unsent attachment.
|
||||||
|
*/
|
||||||
|
void removeAttachment(AttachmentHeader header) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the ID of the contact with the given private conversation.
|
* Returns the ID of the contact with the given private conversation.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -18,14 +18,17 @@ import org.briarproject.bramble.api.sync.Group;
|
|||||||
import org.briarproject.bramble.api.sync.Group.Visibility;
|
import org.briarproject.bramble.api.sync.Group.Visibility;
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
import org.briarproject.bramble.api.sync.Message;
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
|
import org.briarproject.bramble.api.sync.MessageFactory;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.bramble.api.sync.MessageStatus;
|
import org.briarproject.bramble.api.sync.MessageStatus;
|
||||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
||||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook;
|
import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook;
|
||||||
|
import org.briarproject.bramble.util.IoUtils;
|
||||||
import org.briarproject.briar.api.client.MessageTracker;
|
import org.briarproject.briar.api.client.MessageTracker;
|
||||||
import org.briarproject.briar.api.conversation.ConversationMessageHeader;
|
import org.briarproject.briar.api.conversation.ConversationMessageHeader;
|
||||||
import org.briarproject.briar.api.messaging.Attachment;
|
import org.briarproject.briar.api.messaging.Attachment;
|
||||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
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.MessagingManager;
|
||||||
import org.briarproject.briar.api.messaging.PrivateMessage;
|
import org.briarproject.briar.api.messaging.PrivateMessage;
|
||||||
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
||||||
@@ -33,18 +36,18 @@ import org.briarproject.briar.api.messaging.event.PrivateMessageReceivedEvent;
|
|||||||
import org.briarproject.briar.client.ConversationClientImpl;
|
import org.briarproject.briar.client.ConversationClientImpl;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.EOFException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static java.util.Collections.emptyList;
|
import static java.util.Collections.emptyList;
|
||||||
import static org.briarproject.bramble.util.StringUtils.fromHexString;
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
||||||
import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ;
|
import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@@ -55,15 +58,18 @@ class MessagingManagerImpl extends ConversationClientImpl
|
|||||||
|
|
||||||
private final ClientVersioningManager clientVersioningManager;
|
private final ClientVersioningManager clientVersioningManager;
|
||||||
private final ContactGroupFactory contactGroupFactory;
|
private final ContactGroupFactory contactGroupFactory;
|
||||||
|
private final MessageFactory messageFactory;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
MessagingManagerImpl(DatabaseComponent db, ClientHelper clientHelper,
|
MessagingManagerImpl(DatabaseComponent db, ClientHelper clientHelper,
|
||||||
ClientVersioningManager clientVersioningManager,
|
ClientVersioningManager clientVersioningManager,
|
||||||
MetadataParser metadataParser, MessageTracker messageTracker,
|
MetadataParser metadataParser, MessageTracker messageTracker,
|
||||||
ContactGroupFactory contactGroupFactory) {
|
ContactGroupFactory contactGroupFactory,
|
||||||
|
MessageFactory messageFactory) {
|
||||||
super(db, clientHelper, metadataParser, messageTracker);
|
super(db, clientHelper, metadataParser, messageTracker);
|
||||||
this.clientVersioningManager = clientVersioningManager;
|
this.clientVersioningManager = clientVersioningManager;
|
||||||
this.contactGroupFactory = contactGroupFactory;
|
this.contactGroupFactory = contactGroupFactory;
|
||||||
|
this.messageFactory = messageFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -158,12 +164,29 @@ class MessagingManagerImpl extends ConversationClientImpl
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AttachmentHeader addLocalAttachment(GroupId groupId, long timestamp,
|
public AttachmentHeader addLocalAttachment(GroupId groupId, long timestamp,
|
||||||
String contentType, InputStream is) throws IOException {
|
String contentType, InputStream is)
|
||||||
|
throws DbException, IOException {
|
||||||
// TODO add real implementation
|
// TODO add real implementation
|
||||||
if (is.available() == 0) throw new IOException();
|
byte[] body = new byte[MAX_MESSAGE_BODY_LENGTH];
|
||||||
byte[] b = new byte[MessageId.LENGTH];
|
try {
|
||||||
new Random().nextBytes(b);
|
IoUtils.read(is, body);
|
||||||
return new AttachmentHeader(new MessageId(b), "image/png");
|
} catch (EOFException ignored) {
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeAttachment(AttachmentHeader header) throws DbException {
|
||||||
|
db.transaction(false,
|
||||||
|
txn -> db.removeMessage(txn, header.getMessageId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private ContactId getContactId(Transaction txn, GroupId g)
|
private ContactId getContactId(Transaction txn, GroupId g)
|
||||||
@@ -242,12 +265,10 @@ class MessagingManagerImpl extends ConversationClientImpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Attachment getAttachment(MessageId m) {
|
public Attachment getAttachment(MessageId mId) throws DbException {
|
||||||
// TODO add real implementation
|
// TODO add real implementation
|
||||||
byte[] bytes = fromHexString("89504E470D0A1A0A0000000D49484452" +
|
Message m = clientHelper.getMessage(mId);
|
||||||
"000000010000000108060000001F15C4" +
|
byte[] bytes = m.getBody();
|
||||||
"890000000A49444154789C6300010000" +
|
|
||||||
"0500010D0A2DB40000000049454E44AE426082");
|
|
||||||
return new Attachment(new ByteArrayInputStream(bytes));
|
return new Attachment(new ByteArrayInputStream(bytes));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user