mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 02:39:05 +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.support.test.InstrumentationRegistry;
|
||||
@@ -21,7 +21,7 @@ import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class AttachmentControllerIntegrationTest {
|
||||
public class AttachmentRetrieverIntegrationTest {
|
||||
|
||||
private static final String smallKitten =
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/0/06/Kitten_in_Rizal_Park%2C_Manila.jpg/160px-Kitten_in_Rizal_Park%2C_Manila.jpg";
|
||||
@@ -47,15 +47,15 @@ public class AttachmentControllerIntegrationTest {
|
||||
);
|
||||
private final MessageId msgId = new MessageId(getRandomId());
|
||||
|
||||
private final AttachmentController controller =
|
||||
new AttachmentController(null, dimensions);
|
||||
private final AttachmentRetriever retriever =
|
||||
new AttachmentRetriever(null, dimensions);
|
||||
|
||||
@Test
|
||||
public void testSmallJpegImage() throws Exception {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||
InputStream is = getUrlInputStream(smallKitten);
|
||||
Attachment a = new Attachment(is);
|
||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||
assertEquals(msgId, item.getMessageId());
|
||||
assertEquals(160, item.getWidth());
|
||||
assertEquals(240, item.getHeight());
|
||||
@@ -71,7 +71,7 @@ public class AttachmentControllerIntegrationTest {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||
InputStream is = getUrlInputStream(originalKitten);
|
||||
Attachment a = new Attachment(is);
|
||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||
assertEquals(msgId, item.getMessageId());
|
||||
assertEquals(1728, item.getWidth());
|
||||
assertEquals(2592, item.getHeight());
|
||||
@@ -87,7 +87,7 @@ public class AttachmentControllerIntegrationTest {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/png");
|
||||
InputStream is = getUrlInputStream(pngKitten);
|
||||
Attachment a = new Attachment(is);
|
||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||
assertEquals(msgId, item.getMessageId());
|
||||
assertEquals(737, item.getWidth());
|
||||
assertEquals(510, item.getHeight());
|
||||
@@ -103,7 +103,7 @@ public class AttachmentControllerIntegrationTest {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||
InputStream is = getUrlInputStream(uberGif);
|
||||
Attachment a = new Attachment(is);
|
||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||
assertEquals(1, item.getWidth());
|
||||
assertEquals(1, item.getHeight());
|
||||
assertEquals(dimensions.minHeight, item.getThumbnailWidth());
|
||||
@@ -118,7 +118,7 @@ public class AttachmentControllerIntegrationTest {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||
InputStream is = getUrlInputStream(lottaPixel);
|
||||
Attachment a = new Attachment(is);
|
||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||
assertEquals(64250, item.getWidth());
|
||||
assertEquals(64250, item.getHeight());
|
||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||
@@ -133,7 +133,7 @@ public class AttachmentControllerIntegrationTest {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||
InputStream is = getUrlInputStream(imageIoCrash);
|
||||
Attachment a = new Attachment(is);
|
||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||
assertEquals(1184, item.getWidth());
|
||||
assertEquals(448, item.getHeight());
|
||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||
@@ -148,7 +148,7 @@ public class AttachmentControllerIntegrationTest {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||
InputStream is = getUrlInputStream(gimpCrash);
|
||||
Attachment a = new Attachment(is);
|
||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||
assertEquals(1, item.getWidth());
|
||||
assertEquals(1, item.getHeight());
|
||||
assertEquals(dimensions.minHeight, item.getThumbnailWidth());
|
||||
@@ -163,7 +163,7 @@ public class AttachmentControllerIntegrationTest {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||
InputStream is = getUrlInputStream(optiPngAfl);
|
||||
Attachment a = new Attachment(is);
|
||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||
assertEquals(32, item.getWidth());
|
||||
assertEquals(32, item.getHeight());
|
||||
assertEquals(dimensions.minHeight, item.getThumbnailWidth());
|
||||
@@ -178,7 +178,7 @@ public class AttachmentControllerIntegrationTest {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||
InputStream is = getUrlInputStream(librawError);
|
||||
Attachment a = new Attachment(is);
|
||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||
assertTrue(item.hasError());
|
||||
}
|
||||
|
||||
@@ -187,7 +187,7 @@ public class AttachmentControllerIntegrationTest {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
|
||||
InputStream is = getAssetInputStream("animated.gif");
|
||||
Attachment a = new Attachment(is);
|
||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||
assertEquals(65535, item.getWidth());
|
||||
assertEquals(65535, item.getHeight());
|
||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||
@@ -202,7 +202,7 @@ public class AttachmentControllerIntegrationTest {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
|
||||
InputStream is = getAssetInputStream("animated2.gif");
|
||||
Attachment a = new Attachment(is);
|
||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||
assertEquals(10000, item.getWidth());
|
||||
assertEquals(10000, item.getHeight());
|
||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||
@@ -217,7 +217,7 @@ public class AttachmentControllerIntegrationTest {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
|
||||
InputStream is = getAssetInputStream("error_large.gif");
|
||||
Attachment a = new Attachment(is);
|
||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||
assertEquals(16384, item.getWidth());
|
||||
assertEquals(16384, item.getHeight());
|
||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||
@@ -232,7 +232,7 @@ public class AttachmentControllerIntegrationTest {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||
InputStream is = getAssetInputStream("error_high.jpg");
|
||||
Attachment a = new Attachment(is);
|
||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||
assertEquals(1, item.getWidth());
|
||||
assertEquals(10000, item.getHeight());
|
||||
assertEquals(dimensions.minWidth, item.getThumbnailWidth());
|
||||
@@ -247,7 +247,7 @@ public class AttachmentControllerIntegrationTest {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||
InputStream is = getAssetInputStream("error_wide.jpg");
|
||||
Attachment a = new Attachment(is);
|
||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||
assertEquals(1920, item.getWidth());
|
||||
assertEquals(1, item.getHeight());
|
||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||
@@ -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.support.annotation.VisibleForTesting;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
|
||||
class AttachmentDimensions {
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class AttachmentDimensions {
|
||||
|
||||
final int defaultSize;
|
||||
final int minWidth, maxWidth;
|
||||
@@ -21,7 +26,7 @@ class AttachmentDimensions {
|
||||
this.maxHeight = maxHeight;
|
||||
}
|
||||
|
||||
static AttachmentDimensions getAttachmentDimensions(Resources res) {
|
||||
public static AttachmentDimensions getAttachmentDimensions(Resources res) {
|
||||
int defaultSize =
|
||||
res.getDimensionPixelSize(R.dimen.message_bubble_image_default);
|
||||
int minWidth = res.getDimensionPixelSize(
|
||||
@@ -33,7 +38,7 @@ class AttachmentDimensions {
|
||||
int maxHeight = res.getDimensionPixelSize(
|
||||
R.dimen.message_bubble_image_max_height);
|
||||
return new AttachmentDimensions(defaultSize, minWidth, maxWidth,
|
||||
minHeight, minHeight);
|
||||
minHeight, maxHeight);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.briar.android.conversation;
|
||||
package org.briarproject.briar.android.attachment;
|
||||
|
||||
import android.os.Parcel;
|
||||
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.sync.MessageId;
|
||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class AttachmentItem implements Parcelable {
|
||||
|
||||
private final MessageId messageId;
|
||||
private final AttachmentHeader header;
|
||||
private final int width, height;
|
||||
private final String mimeType, extension;
|
||||
private final String extension;
|
||||
private final int thumbnailWidth, thumbnailHeight;
|
||||
private final boolean hasError;
|
||||
private final long instanceId;
|
||||
@@ -37,13 +40,12 @@ public class AttachmentItem implements Parcelable {
|
||||
|
||||
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,
|
||||
boolean hasError) {
|
||||
this.messageId = messageId;
|
||||
this.header = header;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.mimeType = mimeType;
|
||||
this.extension = extension;
|
||||
this.thumbnailWidth = thumbnailWidth;
|
||||
this.thumbnailHeight = thumbnailHeight;
|
||||
@@ -54,19 +56,24 @@ public class AttachmentItem implements Parcelable {
|
||||
protected AttachmentItem(Parcel in) {
|
||||
byte[] messageIdByte = new byte[MessageId.LENGTH];
|
||||
in.readByteArray(messageIdByte);
|
||||
messageId = new MessageId(messageIdByte);
|
||||
MessageId messageId = new MessageId(messageIdByte);
|
||||
width = in.readInt();
|
||||
height = in.readInt();
|
||||
mimeType = in.readString();
|
||||
extension = in.readString();
|
||||
String mimeType = requireNonNull(in.readString());
|
||||
extension = requireNonNull(in.readString());
|
||||
thumbnailWidth = in.readInt();
|
||||
thumbnailHeight = in.readInt();
|
||||
hasError = in.readByte() != 0;
|
||||
instanceId = in.readLong();
|
||||
header = new AttachmentHeader(messageId, mimeType);
|
||||
}
|
||||
|
||||
AttachmentHeader getHeader() {
|
||||
return header;
|
||||
}
|
||||
|
||||
public MessageId getMessageId() {
|
||||
return messageId;
|
||||
return header.getMessageId();
|
||||
}
|
||||
|
||||
int getWidth() {
|
||||
@@ -77,27 +84,27 @@ public class AttachmentItem implements Parcelable {
|
||||
return height;
|
||||
}
|
||||
|
||||
String getMimeType() {
|
||||
return mimeType;
|
||||
public String getMimeType() {
|
||||
return header.getContentType();
|
||||
}
|
||||
|
||||
String getExtension() {
|
||||
public String getExtension() {
|
||||
return extension;
|
||||
}
|
||||
|
||||
int getThumbnailWidth() {
|
||||
public int getThumbnailWidth() {
|
||||
return thumbnailWidth;
|
||||
}
|
||||
|
||||
int getThumbnailHeight() {
|
||||
public int getThumbnailHeight() {
|
||||
return thumbnailHeight;
|
||||
}
|
||||
|
||||
boolean hasError() {
|
||||
public boolean hasError() {
|
||||
return hasError;
|
||||
}
|
||||
|
||||
String getTransitionName() {
|
||||
public String getTransitionName() {
|
||||
return String.valueOf(instanceId);
|
||||
}
|
||||
|
||||
@@ -108,10 +115,10 @@ public class AttachmentItem implements Parcelable {
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeByteArray(messageId.getBytes());
|
||||
dest.writeByteArray(header.getMessageId().getBytes());
|
||||
dest.writeInt(width);
|
||||
dest.writeInt(height);
|
||||
dest.writeString(mimeType);
|
||||
dest.writeString(header.getContentType());
|
||||
dest.writeString(extension);
|
||||
dest.writeInt(thumbnailWidth);
|
||||
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.Options;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.VisibleForTesting;
|
||||
import android.support.media.ExifInterface;
|
||||
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.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.android.conversation.ImageHelper.DecodeResult;
|
||||
import org.briarproject.briar.android.attachment.ImageHelper.DecodeResult;
|
||||
import org.briarproject.briar.api.messaging.Attachment;
|
||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||
@@ -42,10 +43,10 @@ import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static org.briarproject.bramble.util.LogUtils.now;
|
||||
|
||||
@NotNullByDefault
|
||||
class AttachmentController {
|
||||
public class AttachmentRetriever {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(AttachmentController.class.getName());
|
||||
getLogger(AttachmentRetriever.class.getName());
|
||||
private static final int READ_LIMIT = 1024 * 8192;
|
||||
|
||||
private final MessagingManager messagingManager;
|
||||
@@ -57,7 +58,8 @@ class AttachmentController {
|
||||
private final Map<MessageId, List<AttachmentItem>> attachmentCache =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
AttachmentController(MessagingManager messagingManager,
|
||||
@VisibleForTesting
|
||||
AttachmentRetriever(MessagingManager messagingManager,
|
||||
AttachmentDimensions dimensions, ImageHelper imageHelper) {
|
||||
this.messagingManager = messagingManager;
|
||||
this.imageHelper = imageHelper;
|
||||
@@ -68,7 +70,7 @@ class AttachmentController {
|
||||
maxHeight = dimensions.maxHeight;
|
||||
}
|
||||
|
||||
AttachmentController(MessagingManager messagingManager,
|
||||
public AttachmentRetriever(MessagingManager messagingManager,
|
||||
AttachmentDimensions dimensions) {
|
||||
this(messagingManager, dimensions, new ImageHelper() {
|
||||
@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);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
List<AttachmentItem> get(MessageId messageId) {
|
||||
public List<AttachmentItem> cacheGet(MessageId messageId) {
|
||||
return attachmentCache.get(messageId);
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
List<Pair<AttachmentHeader, Attachment>> getMessageAttachments(
|
||||
public List<Pair<AttachmentHeader, Attachment>> getMessageAttachments(
|
||||
List<AttachmentHeader> headers) throws DbException {
|
||||
long start = now();
|
||||
List<Pair<AttachmentHeader, Attachment>> attachments =
|
||||
@@ -110,16 +112,21 @@ class AttachmentController {
|
||||
Attachment a = messagingManager.getAttachment(h.getMessageId());
|
||||
attachments.add(new Pair<>(h, a));
|
||||
}
|
||||
logDuration(LOG, "Loading attachment", start);
|
||||
logDuration(LOG, "Loading attachments", start);
|
||||
return attachments;
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
Attachment getMessageAttachment(AttachmentHeader h) throws DbException {
|
||||
return messagingManager.getAttachment(h.getMessageId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates {@link AttachmentItem}s from the passed headers and Attachments.
|
||||
* <p>
|
||||
* Note: This closes the {@link Attachment}'s {@link InputStream}.
|
||||
*/
|
||||
List<AttachmentItem> getAttachmentItems(
|
||||
public List<AttachmentItem> getAttachmentItems(
|
||||
List<Pair<AttachmentHeader, Attachment>> attachments) {
|
||||
boolean needsSize = attachments.size() == 1;
|
||||
List<AttachmentItem> items = new ArrayList<>(attachments.size());
|
||||
@@ -137,17 +144,15 @@ class AttachmentController {
|
||||
*/
|
||||
AttachmentItem getAttachmentItem(AttachmentHeader h, Attachment a,
|
||||
boolean needsSize) {
|
||||
MessageId messageId = h.getMessageId();
|
||||
if (!needsSize) {
|
||||
String mimeType = h.getContentType();
|
||||
String extension = imageHelper.getExtensionFromMimeType(mimeType);
|
||||
String extension =
|
||||
imageHelper.getExtensionFromMimeType(h.getContentType());
|
||||
boolean hasError = false;
|
||||
if (extension == null) {
|
||||
extension = "";
|
||||
hasError = true;
|
||||
}
|
||||
return new AttachmentItem(messageId, 0, 0, mimeType, extension, 0,
|
||||
0, hasError);
|
||||
return new AttachmentItem(h, 0, 0, extension, 0, 0, hasError);
|
||||
}
|
||||
|
||||
Size size = new Size();
|
||||
@@ -185,10 +190,17 @@ class AttachmentController {
|
||||
// get file extension
|
||||
String extension = imageHelper.getExtensionFromMimeType(size.mimeType);
|
||||
boolean hasError = extension == null || size.error;
|
||||
if (!h.getContentType().equals(size.mimeType)) {
|
||||
if (LOG.isLoggable(WARNING)) {
|
||||
LOG.warning("Header has different mime type (" +
|
||||
h.getContentType() + ") than image (" + size.mimeType +
|
||||
").");
|
||||
}
|
||||
hasError = true;
|
||||
}
|
||||
if (extension == null) extension = "";
|
||||
return new AttachmentItem(messageId, size.width, size.height,
|
||||
size.mimeType, extension, thumbnailSize.width,
|
||||
thumbnailSize.height, hasError);
|
||||
return new AttachmentItem(h, size.width, size.height, extension,
|
||||
thumbnailSize.width, thumbnailSize.height, hasError);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.briar.android.conversation;
|
||||
package org.briarproject.briar.android.attachment;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.briarproject.briar.android.blog;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
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.TextSendController;
|
||||
import org.briarproject.briar.android.view.TextSendController.SendListener;
|
||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -121,7 +121,8 @@ public class ReblogFragment extends BaseFragment implements SendListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSendClick(@Nullable String text, List<Uri> imageUris) {
|
||||
public void onSendClick(@Nullable String text,
|
||||
List<AttachmentHeader> headers) {
|
||||
ui.input.hideSoftKeyboard();
|
||||
feedController.repeatPost(item, text,
|
||||
new UiExceptionHandler<DbException>(this) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.briarproject.briar.android.blog;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
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.BlogPost;
|
||||
import org.briarproject.briar.api.blog.BlogPostFactory;
|
||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.List;
|
||||
@@ -120,7 +120,8 @@ public class WriteBlogPostActivity extends BriarActivity
|
||||
}
|
||||
|
||||
@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();
|
||||
|
||||
// hide publish button, show progress bar
|
||||
|
||||
@@ -6,7 +6,6 @@ import android.arch.lifecycle.ViewModelProvider;
|
||||
import android.arch.lifecycle.ViewModelProviders;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
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.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.activity.BriarActivity;
|
||||
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||
import org.briarproject.briar.android.attachment.AttachmentRetriever;
|
||||
import org.briarproject.briar.android.blog.BlogActivity;
|
||||
import org.briarproject.briar.android.conversation.ConversationVisitor.AttachmentCache;
|
||||
import org.briarproject.briar.android.conversation.ConversationVisitor.TextCache;
|
||||
@@ -184,7 +185,7 @@ public class ConversationActivity extends BriarActivity
|
||||
loadMessages();
|
||||
};
|
||||
|
||||
private AttachmentController attachmentController;
|
||||
private AttachmentRetriever attachmentRetriever;
|
||||
private ConversationViewModel viewModel;
|
||||
private ConversationVisitor visitor;
|
||||
private ConversationAdapter adapter;
|
||||
@@ -219,7 +220,7 @@ public class ConversationActivity extends BriarActivity
|
||||
|
||||
viewModel = ViewModelProviders.of(this, viewModelFactory)
|
||||
.get(ConversationViewModel.class);
|
||||
attachmentController = viewModel.getAttachmentController();
|
||||
attachmentRetriever = viewModel.getAttachmentRetriever();
|
||||
|
||||
setContentView(R.layout.activity_conversation);
|
||||
|
||||
@@ -242,7 +243,7 @@ public class ConversationActivity extends BriarActivity
|
||||
requireNonNull(deleted);
|
||||
if (deleted) finish();
|
||||
});
|
||||
viewModel.getAddedPrivateMessage().observe(this,
|
||||
viewModel.getAddedPrivateMessage().observeEvent(this,
|
||||
this::onAddedPrivateMessage);
|
||||
|
||||
setTransitionName(toolbarAvatar, getAvatarTransitionName(contactId));
|
||||
@@ -264,7 +265,7 @@ public class ConversationActivity extends BriarActivity
|
||||
if (FEATURE_FLAG_IMAGE_ATTACHMENTS) {
|
||||
ImagePreview imagePreview = findViewById(R.id.imagePreview);
|
||||
sendController = new TextAttachmentController(textInputView,
|
||||
imagePreview, this, this);
|
||||
imagePreview, this, this, viewModel);
|
||||
observeOnce(viewModel.hasImageSupport(), this, hasSupport -> {
|
||||
if (hasSupport != null && hasSupport) {
|
||||
// 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
|
||||
// images we use a grid so the size is fixed
|
||||
if (h.getAttachmentHeaders().size() == 1) {
|
||||
List<AttachmentItem> items = attachmentController.get(id);
|
||||
List<AttachmentItem> items = attachmentRetriever.cacheGet(id);
|
||||
if (items == null) {
|
||||
LOG.info("Eagerly loading image size for latest message");
|
||||
items = attachmentController.getAttachmentItems(
|
||||
attachmentController.getMessageAttachments(
|
||||
items = attachmentRetriever.getAttachmentItems(
|
||||
attachmentRetriever.getMessageAttachments(
|
||||
h.getAttachmentHeaders()));
|
||||
attachmentController.put(id, items);
|
||||
attachmentRetriever.cachePut(id, items);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -545,10 +546,10 @@ public class ConversationActivity extends BriarActivity
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
List<Pair<AttachmentHeader, Attachment>> attachments =
|
||||
attachmentController.getMessageAttachments(headers);
|
||||
attachmentRetriever.getMessageAttachments(headers);
|
||||
// TODO move getting the items off to IoExecutor, if size == 1
|
||||
List<AttachmentItem> items =
|
||||
attachmentController.getAttachmentItems(attachments);
|
||||
attachmentRetriever.getAttachmentItems(attachments);
|
||||
displayMessageAttachments(messageId, items);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
@@ -559,7 +560,7 @@ public class ConversationActivity extends BriarActivity
|
||||
private void displayMessageAttachments(MessageId m,
|
||||
List<AttachmentItem> items) {
|
||||
runOnUiThreadUnlessDestroyed(() -> {
|
||||
attachmentController.put(m, items);
|
||||
attachmentRetriever.cachePut(m, items);
|
||||
Pair<Integer, ConversationMessageItem> pair =
|
||||
adapter.getMessageItem(m);
|
||||
if (pair != null) {
|
||||
@@ -658,12 +659,13 @@ public class ConversationActivity extends BriarActivity
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSendClick(@Nullable String text, List<Uri> imageUris) {
|
||||
if (isNullOrEmpty(text) && imageUris.isEmpty())
|
||||
public void onSendClick(@Nullable String text,
|
||||
List<AttachmentHeader> attachmentHeaders) {
|
||||
if (isNullOrEmpty(text) && attachmentHeaders.isEmpty())
|
||||
throw new AssertionError();
|
||||
long timestamp = System.currentTimeMillis();
|
||||
timestamp = Math.max(timestamp, getMinTimestampForNewMessage());
|
||||
viewModel.sendMessage(text, imageUris, timestamp);
|
||||
viewModel.sendMessage(text, attachmentHeaders, timestamp);
|
||||
textInputView.clearText();
|
||||
}
|
||||
|
||||
@@ -676,7 +678,6 @@ public class ConversationActivity extends BriarActivity
|
||||
private void onAddedPrivateMessage(@Nullable PrivateMessageHeader h) {
|
||||
if (h == null) return;
|
||||
addConversationItem(h.accept(visitor));
|
||||
viewModel.onAddedPrivateMessageSeen();
|
||||
}
|
||||
|
||||
private void askToRemoveContact() {
|
||||
@@ -903,7 +904,7 @@ public class ConversationActivity extends BriarActivity
|
||||
@Override
|
||||
public List<AttachmentItem> getAttachmentItems(MessageId m,
|
||||
List<AttachmentHeader> headers) {
|
||||
List<AttachmentItem> attachments = attachmentController.get(m);
|
||||
List<AttachmentItem> attachments = attachmentRetriever.cacheGet(m);
|
||||
if (attachments == null) {
|
||||
loadMessageAttachments(m, headers);
|
||||
return emptyList();
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.support.annotation.UiThread;
|
||||
import android.view.View;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||
|
||||
@UiThread
|
||||
@NotNullByDefault
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.briarproject.briar.android.conversation;
|
||||
import android.support.annotation.LayoutRes;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -9,6 +9,7 @@ import android.view.ViewGroup;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||
|
||||
import static android.support.constraint.ConstraintSet.WRAP_CONTENT;
|
||||
import static android.support.v4.content.ContextCompat.getColor;
|
||||
|
||||
@@ -5,13 +5,11 @@ import android.arch.lifecycle.AndroidViewModel;
|
||||
import android.arch.lifecycle.LiveData;
|
||||
import android.arch.lifecycle.MutableLiveData;
|
||||
import android.arch.lifecycle.Transformations;
|
||||
import android.content.ContentResolver;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.UiThread;
|
||||
|
||||
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.ContactId;
|
||||
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.TransactionManager;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.settings.Settings;
|
||||
import org.briarproject.bramble.api.settings.SettingsManager;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.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.viewmodel.LiveEvent;
|
||||
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.MessagingManager;
|
||||
import org.briarproject.briar.api.messaging.PrivateMessage;
|
||||
import org.briarproject.briar.api.messaging.PrivateMessageFactory;
|
||||
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
@@ -50,16 +49,16 @@ import javax.inject.Inject;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.util.IoUtils.tryToClose;
|
||||
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static org.briarproject.bramble.util.LogUtils.now;
|
||||
import static org.briarproject.briar.android.conversation.AttachmentDimensions.getAttachmentDimensions;
|
||||
import static org.briarproject.briar.android.attachment.AttachmentDimensions.getAttachmentDimensions;
|
||||
import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
|
||||
import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce;
|
||||
|
||||
@NotNullByDefault
|
||||
public class ConversationViewModel extends AndroidViewModel {
|
||||
public class ConversationViewModel extends AndroidViewModel
|
||||
implements AttachmentManager {
|
||||
|
||||
private static Logger LOG =
|
||||
getLogger(ConversationViewModel.class.getName());
|
||||
@@ -77,7 +76,8 @@ public class ConversationViewModel extends AndroidViewModel {
|
||||
private final ContactManager contactManager;
|
||||
private final SettingsManager settingsManager;
|
||||
private final PrivateMessageFactory privateMessageFactory;
|
||||
private final AttachmentController attachmentController;
|
||||
private final AttachmentRetriever attachmentRetriever;
|
||||
private final AttachmentCreator attachmentCreator;
|
||||
|
||||
@Nullable
|
||||
private ContactId contactId = null;
|
||||
@@ -86,6 +86,7 @@ public class ConversationViewModel extends AndroidViewModel {
|
||||
Transformations.map(contact, c -> c.getAuthor().getId());
|
||||
private final LiveData<String> contactName =
|
||||
Transformations.map(contact, UiUtils::getContactDisplayName);
|
||||
private final LiveData<GroupId> messagingGroupId;
|
||||
private final MutableLiveData<Boolean> imageSupport =
|
||||
new MutableLiveData<>();
|
||||
private final MutableLiveEvent<Boolean> showImageOnboarding =
|
||||
@@ -96,15 +97,14 @@ public class ConversationViewModel extends AndroidViewModel {
|
||||
new MutableLiveData<>();
|
||||
private final MutableLiveData<Boolean> contactDeleted =
|
||||
new MutableLiveData<>();
|
||||
private final MutableLiveData<GroupId> messagingGroupId =
|
||||
new MutableLiveData<>();
|
||||
private final MutableLiveData<PrivateMessageHeader> addedHeader =
|
||||
new MutableLiveData<>();
|
||||
private final MutableLiveEvent<PrivateMessageHeader> addedHeader =
|
||||
new MutableLiveEvent<>();
|
||||
|
||||
@Inject
|
||||
ConversationViewModel(Application application,
|
||||
@DatabaseExecutor Executor dbExecutor,
|
||||
@CryptoExecutor Executor cryptoExecutor, TransactionManager db,
|
||||
@CryptoExecutor Executor cryptoExecutor,
|
||||
@IoExecutor Executor ioExecutor, TransactionManager db,
|
||||
MessagingManager messagingManager, ContactManager contactManager,
|
||||
SettingsManager settingsManager,
|
||||
PrivateMessageFactory privateMessageFactory) {
|
||||
@@ -116,11 +116,21 @@ public class ConversationViewModel extends AndroidViewModel {
|
||||
this.contactManager = contactManager;
|
||||
this.settingsManager = settingsManager;
|
||||
this.privateMessageFactory = privateMessageFactory;
|
||||
this.attachmentController = new AttachmentController(messagingManager,
|
||||
this.attachmentRetriever = new AttachmentRetriever(messagingManager,
|
||||
getAttachmentDimensions(application.getResources()));
|
||||
this.attachmentCreator = new AttachmentCreator(getApplication(),
|
||||
ioExecutor, messagingManager, attachmentRetriever);
|
||||
messagingGroupId = Transformations
|
||||
.map(contact, c -> messagingManager.getContactGroup(c).getId());
|
||||
contactDeleted.setValue(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCleared() {
|
||||
super.onCleared();
|
||||
attachmentCreator.deleteUnsentAttachments();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setting the {@link ContactId} automatically triggers loading of other
|
||||
* data.
|
||||
@@ -176,25 +186,37 @@ public class ConversationViewModel extends AndroidViewModel {
|
||||
});
|
||||
}
|
||||
|
||||
void sendMessage(@Nullable String text, List<Uri> uris, long timestamp) {
|
||||
if (messagingGroupId.getValue() == null) loadGroupId();
|
||||
void sendMessage(@Nullable String text,
|
||||
List<AttachmentHeader> attachmentHeaders, long timestamp) {
|
||||
// messagingGroupId is loaded with the contact
|
||||
observeForeverOnce(messagingGroupId, groupId -> {
|
||||
if (groupId == null) return;
|
||||
// calls through to creating and storing the message
|
||||
storeAttachments(groupId, text, uris, timestamp);
|
||||
if (groupId == null) throw new IllegalStateException();
|
||||
createMessage(groupId, text, attachmentHeaders, timestamp);
|
||||
});
|
||||
}
|
||||
|
||||
private void loadGroupId() {
|
||||
if (contactId == null) throw new IllegalStateException();
|
||||
dbExecutor.execute(() -> {
|
||||
try {
|
||||
messagingGroupId.postValue(
|
||||
messagingManager.getConversationId(contactId));
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
});
|
||||
@Override
|
||||
@UiThread
|
||||
public LiveData<AttachmentResult> storeAttachments(Collection<Uri> uris,
|
||||
boolean restart) {
|
||||
if (restart) {
|
||||
return attachmentCreator.getLiveAttachments();
|
||||
} else {
|
||||
// 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
|
||||
@@ -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,
|
||||
List<AttachmentHeader> attachments, List<AttachmentItem> aItems,
|
||||
long timestamp) {
|
||||
List<AttachmentHeader> attachments, long timestamp) {
|
||||
cryptoExecutor.execute(() -> {
|
||||
try {
|
||||
// TODO remove when text can be null in the backend
|
||||
@@ -311,7 +283,6 @@ public class ConversationViewModel extends AndroidViewModel {
|
||||
PrivateMessage pm = privateMessageFactory
|
||||
.createPrivateMessage(groupId, timestamp, msgText,
|
||||
attachments);
|
||||
attachmentController.put(pm.getMessage().getId(), aItems);
|
||||
storeMessage(pm, msgText, attachments);
|
||||
} catch (FormatException e) {
|
||||
throw new RuntimeException(e);
|
||||
@@ -321,6 +292,7 @@ public class ConversationViewModel extends AndroidViewModel {
|
||||
|
||||
private void storeMessage(PrivateMessage m, @Nullable String text,
|
||||
List<AttachmentHeader> attachments) {
|
||||
attachmentCreator.onAttachmentsSent(m.getMessage().getId());
|
||||
dbExecutor.execute(() -> {
|
||||
try {
|
||||
long start = now();
|
||||
@@ -332,20 +304,15 @@ public class ConversationViewModel extends AndroidViewModel {
|
||||
message.getTimestamp(), true, true, false, false,
|
||||
text != null, attachments);
|
||||
// TODO add text to cache when available here
|
||||
addedHeader.postValue(h);
|
||||
addedHeader.postEvent(h);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@UiThread
|
||||
void onAddedPrivateMessageSeen() {
|
||||
addedHeader.setValue(null);
|
||||
}
|
||||
|
||||
AttachmentController getAttachmentController() {
|
||||
return attachmentController;
|
||||
AttachmentRetriever getAttachmentRetriever() {
|
||||
return attachmentRetriever;
|
||||
}
|
||||
|
||||
LiveData<Contact> getContact() {
|
||||
@@ -380,7 +347,7 @@ public class ConversationViewModel extends AndroidViewModel {
|
||||
return contactDeleted;
|
||||
}
|
||||
|
||||
LiveData<PrivateMessageHeader> getAddedPrivateMessage() {
|
||||
LiveEvent<PrivateMessageHeader> getAddedPrivateMessage() {
|
||||
return addedHeader;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.support.annotation.UiThread;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||
import org.briarproject.briar.api.blog.BlogInvitationRequest;
|
||||
import org.briarproject.briar.api.blog.BlogInvitationResponse;
|
||||
import org.briarproject.briar.api.conversation.ConversationMessageVisitor;
|
||||
|
||||
@@ -31,6 +31,7 @@ import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.activity.BriarActivity;
|
||||
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||
import org.briarproject.briar.android.util.BriarSnackbarBuilder;
|
||||
import org.briarproject.briar.android.view.PullDownLayout;
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import android.view.WindowManager;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||
import org.briarproject.briar.android.conversation.glide.Radii;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.github.chrisbanes.photoview.PhotoView;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.BaseActivity;
|
||||
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||
import org.briarproject.briar.android.conversation.glide.GlideApp;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
|
||||
@@ -11,6 +11,7 @@ import com.bumptech.glide.load.Transformation;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||
import org.briarproject.briar.android.conversation.glide.BriarImageTransformation;
|
||||
import org.briarproject.briar.android.conversation.glide.GlideApp;
|
||||
import org.briarproject.briar.android.conversation.glide.Radii;
|
||||
|
||||
@@ -13,6 +13,7 @@ import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||
import org.briarproject.briar.android.viewmodel.LiveEvent;
|
||||
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
|
||||
import org.briarproject.briar.api.messaging.Attachment;
|
||||
|
||||
@@ -10,7 +10,7 @@ import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.android.conversation.AttachmentItem;
|
||||
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
@@ -2,7 +2,7 @@ package org.briarproject.briar.android.conversation.glide;
|
||||
|
||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.android.conversation.AttachmentItem;
|
||||
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
@@ -10,7 +10,7 @@ import com.bumptech.glide.module.AppGlideModule;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.android.BriarApplication;
|
||||
import org.briarproject.briar.android.conversation.AttachmentItem;
|
||||
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import com.bumptech.glide.signature.ObjectKey;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.android.BriarApplication;
|
||||
import org.briarproject.briar.android.conversation.AttachmentItem;
|
||||
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import com.bumptech.glide.load.model.MultiModelLoaderFactory;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.android.BriarApplication;
|
||||
import org.briarproject.briar.android.conversation.AttachmentItem;
|
||||
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.briarproject.briar.android.introduction;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
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.SendListener;
|
||||
import org.briarproject.briar.api.introduction.IntroductionManager;
|
||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
@@ -193,7 +193,8 @@ public class IntroductionMessageFragment extends BaseFragment
|
||||
}
|
||||
|
||||
@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
|
||||
ui.message.setReady(false);
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.briarproject.briar.android.sharing;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
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.TextSendController;
|
||||
import org.briarproject.briar.android.view.TextSendController.SendListener;
|
||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -83,7 +83,8 @@ public abstract class BaseMessageFragment extends BaseFragment
|
||||
}
|
||||
|
||||
@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
|
||||
sendController.setReady(false);
|
||||
message.hideSoftKeyboard();
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.briarproject.briar.android.threaded;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
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.UnreadMessageButton;
|
||||
import org.briarproject.briar.api.client.NamedGroup;
|
||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@@ -341,7 +341,8 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
|
||||
}
|
||||
|
||||
@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();
|
||||
|
||||
I replyItem = adapter.getHighlightedItem();
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.briarproject.briar.android.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.constraint.ConstraintLayout;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
@@ -10,11 +9,13 @@ import android.view.LayoutInflater;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.attachment.AttachmentItemResult;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import static android.content.Context.LAYOUT_INFLATER_SERVICE;
|
||||
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 java.util.Objects.requireNonNull;
|
||||
|
||||
@@ -60,34 +61,28 @@ public class ImagePreview extends ConstraintLayout {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
void showPreview(Collection<Uri> imageUris) {
|
||||
void showPreview(Collection<ImagePreviewItem> items) {
|
||||
if (listener == null) throw new IllegalStateException();
|
||||
if (imageUris.size() == 1) {
|
||||
if (items.size() == 1) {
|
||||
LayoutParams params = (LayoutParams) imageList.getLayoutParams();
|
||||
params.width = MATCH_PARENT;
|
||||
imageList.setLayoutParams(params);
|
||||
}
|
||||
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) imageList.getAdapter();
|
||||
requireNonNull(adapter).removeUri(uri);
|
||||
((ImagePreviewAdapter) imageList.getAdapter());
|
||||
int pos = requireNonNull(adapter).loadItemPreview(result);
|
||||
if (pos != NO_POSITION) {
|
||||
imageList.smoothScrollToPosition(pos);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.briarproject.briar.android.view;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.LayoutRes;
|
||||
import android.support.v7.widget.RecyclerView.Adapter;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -9,25 +8,24 @@ import android.view.ViewGroup;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
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.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import static android.support.v7.widget.RecyclerView.NO_POSITION;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
@NotNullByDefault
|
||||
class ImagePreviewAdapter extends Adapter<ImagePreviewViewHolder> {
|
||||
|
||||
private final List<Uri> items;
|
||||
private final ImagePreviewListener listener;
|
||||
private final List<ImagePreviewItem> items;
|
||||
@LayoutRes
|
||||
private final int layout;
|
||||
|
||||
ImagePreviewAdapter(Collection<Uri> items, ImagePreviewListener listener) {
|
||||
ImagePreviewAdapter(Collection<ImagePreviewItem> items) {
|
||||
this.items = new ArrayList<>(items);
|
||||
this.listener = listener;
|
||||
this.layout = items.size() == 1 ?
|
||||
R.layout.list_item_image_preview_single :
|
||||
R.layout.list_item_image_preview;
|
||||
@@ -38,7 +36,7 @@ class ImagePreviewAdapter extends Adapter<ImagePreviewViewHolder> {
|
||||
int type) {
|
||||
View v = LayoutInflater.from(viewGroup.getContext())
|
||||
.inflate(layout, viewGroup, false);
|
||||
return new ImagePreviewViewHolder(v, requireNonNull(listener));
|
||||
return new ImagePreviewViewHolder(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -52,11 +50,17 @@ class ImagePreviewAdapter extends Adapter<ImagePreviewViewHolder> {
|
||||
return items.size();
|
||||
}
|
||||
|
||||
void removeUri(Uri uri) {
|
||||
int pos = items.indexOf(uri);
|
||||
if (pos == -1) return;
|
||||
items.remove(uri);
|
||||
notifyItemRemoved(pos);
|
||||
int loadItemPreview(AttachmentItemResult result) {
|
||||
ImagePreviewItem newItem = new ImagePreviewItem(result.getUri());
|
||||
int pos = items.indexOf(newItem);
|
||||
if (pos == NO_POSITION) throw new AssertionError();
|
||||
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;
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.DrawableRes;
|
||||
import android.support.annotation.Nullable;
|
||||
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.briar.R;
|
||||
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.VISIBLE;
|
||||
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.drawable.DrawableTransitionOptions.withCrossFade;
|
||||
@@ -30,45 +29,47 @@ class ImagePreviewViewHolder extends ViewHolder {
|
||||
@DrawableRes
|
||||
private static final int ERROR_RES = R.drawable.ic_image_broken;
|
||||
|
||||
private final ImagePreviewListener listener;
|
||||
|
||||
private final ImageView imageView;
|
||||
private final ProgressBar progressBar;
|
||||
|
||||
ImagePreviewViewHolder(View v, ImagePreviewListener listener) {
|
||||
ImagePreviewViewHolder(View v) {
|
||||
super(v);
|
||||
this.listener = listener;
|
||||
this.imageView = v.findViewById(R.id.imageView);
|
||||
this.progressBar = v.findViewById(R.id.progressBar);
|
||||
}
|
||||
|
||||
void bind(Uri uri) {
|
||||
GlideApp.with(imageView)
|
||||
.load(uri)
|
||||
.diskCacheStrategy(NONE)
|
||||
.error(ERROR_RES)
|
||||
.downsample(FIT_CENTER)
|
||||
.transition(withCrossFade())
|
||||
.addListener(new RequestListener<Drawable>() {
|
||||
@Override
|
||||
public boolean onLoadFailed(@Nullable GlideException e,
|
||||
Object model, Target<Drawable> target,
|
||||
boolean isFirstResource) {
|
||||
progressBar.setVisibility(INVISIBLE);
|
||||
listener.onUriError(uri);
|
||||
return false;
|
||||
}
|
||||
void bind(ImagePreviewItem item) {
|
||||
if (item.getItem() == null) {
|
||||
progressBar.setVisibility(VISIBLE);
|
||||
GlideApp.with(imageView)
|
||||
.clear(imageView);
|
||||
} else {
|
||||
GlideApp.with(imageView)
|
||||
.load(item.getItem())
|
||||
.diskCacheStrategy(NONE)
|
||||
.error(ERROR_RES)
|
||||
.downsample(FIT_CENTER)
|
||||
.transition(withCrossFade())
|
||||
.addListener(new RequestListener<Drawable>() {
|
||||
@Override
|
||||
public boolean onLoadFailed(@Nullable GlideException e,
|
||||
Object model, Target<Drawable> target,
|
||||
boolean isFirstResource) {
|
||||
progressBar.setVisibility(INVISIBLE);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onResourceReady(Drawable resource,
|
||||
Object model, Target<Drawable> target,
|
||||
DataSource dataSource, boolean isFirstResource) {
|
||||
progressBar.setVisibility(INVISIBLE);
|
||||
listener.onPreviewLoaded();
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.into(imageView);
|
||||
@Override
|
||||
public boolean onResourceReady(Drawable resource,
|
||||
Object model, Target<Drawable> target,
|
||||
DataSource dataSource,
|
||||
boolean isFirstResource) {
|
||||
progressBar.setVisibility(INVISIBLE);
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.into(imageView);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package org.briarproject.briar.android.view;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.arch.lifecycle.LifecycleOwner;
|
||||
import android.arch.lifecycle.LiveData;
|
||||
import android.arch.lifecycle.Observer;
|
||||
import android.content.ClipData;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@@ -15,26 +18,32 @@ import android.widget.Toast;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
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 java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt;
|
||||
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_OPEN_DOCUMENT;
|
||||
import static android.content.Intent.CATEGORY_OPENABLE;
|
||||
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.support.v4.content.ContextCompat.getColor;
|
||||
import static android.support.v4.view.AbsSavedState.EMPTY_STATE;
|
||||
import static android.view.View.GONE;
|
||||
import static android.widget.Toast.LENGTH_LONG;
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.briarproject.briar.android.util.UiUtils.resolveColorAttribute;
|
||||
import static org.briarproject.briar.api.messaging.MessagingConstants.IMAGE_MIME_TYPES;
|
||||
import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.STATE_DISMISSED;
|
||||
import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.STATE_FINISHED;
|
||||
|
||||
@@ -46,17 +55,19 @@ public class TextAttachmentController extends TextSendController
|
||||
private final ImagePreview imagePreview;
|
||||
private final AttachImageListener imageListener;
|
||||
private final CompositeSendButton sendButton;
|
||||
private final AttachmentManager attachmentManager;
|
||||
|
||||
private CharSequence textHint;
|
||||
private List<Uri> imageUris = emptyList();
|
||||
private int previewsLoaded = 0;
|
||||
private boolean loadingPreviews = false;
|
||||
private final List<Uri> imageUris = new ArrayList<>();
|
||||
private final CharSequence textHint;
|
||||
private boolean loadingUris = false;
|
||||
|
||||
public TextAttachmentController(TextInputView v, ImagePreview imagePreview,
|
||||
SendListener listener, AttachImageListener imageListener) {
|
||||
SendListener listener, AttachImageListener imageListener,
|
||||
AttachmentManager attachmentManager) {
|
||||
super(v, listener, false);
|
||||
this.imageListener = imageListener;
|
||||
this.imagePreview = imagePreview;
|
||||
this.attachmentManager = attachmentManager;
|
||||
this.imagePreview.setImagePreviewListener(this);
|
||||
|
||||
sendButton = (CompositeSendButton) compositeSendButton;
|
||||
@@ -67,10 +78,10 @@ public class TextAttachmentController extends TextSendController
|
||||
|
||||
@Override
|
||||
protected void updateViewState() {
|
||||
textInput.setEnabled(ready && !loadingPreviews);
|
||||
boolean sendEnabled = ready && !loadingPreviews &&
|
||||
textInput.setEnabled(ready && !loadingUris);
|
||||
boolean sendEnabled = ready && !loadingUris &&
|
||||
(!textIsEmpty || canSendEmptyText());
|
||||
if (loadingPreviews) {
|
||||
if (loadingUris) {
|
||||
sendButton.showProgress(true);
|
||||
} else if (imageUris.isEmpty()) {
|
||||
sendButton.showProgress(false);
|
||||
@@ -84,7 +95,9 @@ public class TextAttachmentController extends TextSendController
|
||||
@Override
|
||||
public void onSendEvent() {
|
||||
if (canSend()) {
|
||||
listener.onSendClick(textInput.getText(), imageUris);
|
||||
if (loadingUris) throw new AssertionError();
|
||||
listener.onSendClick(textInput.getText(),
|
||||
attachmentManager.getAttachmentHeadersForSending());
|
||||
reset();
|
||||
}
|
||||
}
|
||||
@@ -110,36 +123,95 @@ public class TextAttachmentController extends TextSendController
|
||||
builder.show();
|
||||
return;
|
||||
}
|
||||
Intent intent = new Intent(SDK_INT >= 19 ?
|
||||
ACTION_OPEN_DOCUMENT : ACTION_GET_CONTENT);
|
||||
intent.addCategory(CATEGORY_OPENABLE);
|
||||
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();
|
||||
Intent intent = getAttachFileIntent();
|
||||
if (imageListener.getLifecycle().getCurrentState() != DESTROYED) {
|
||||
requireNonNull(imageListener).onAttachImage(intent);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
loadingPreviews = true;
|
||||
if (loadingUris) throw new AssertionError();
|
||||
loadingUris = true;
|
||||
updateViewState();
|
||||
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() {
|
||||
@@ -148,10 +220,9 @@ public class TextAttachmentController extends TextSendController
|
||||
// hide image layout
|
||||
imagePreview.setVisibility(GONE);
|
||||
// reset image URIs
|
||||
imageUris = emptyList();
|
||||
// no preview has been loaded
|
||||
previewsLoaded = 0;
|
||||
loadingPreviews = false;
|
||||
imageUris.clear();
|
||||
// definitely not loading anymore
|
||||
loadingUris = false;
|
||||
// show the image button again, so images can get attached
|
||||
updateViewState();
|
||||
}
|
||||
@@ -168,45 +239,29 @@ public class TextAttachmentController extends TextSendController
|
||||
@Nullable
|
||||
public Parcelable onRestoreInstanceState(Parcelable inState) {
|
||||
SavedState state = (SavedState) inState;
|
||||
imageUris = requireNonNull(state.imageUris);
|
||||
onNewUris();
|
||||
if (!imageUris.isEmpty()) throw new AssertionError();
|
||||
if (state.imageUris != null) imageUris.addAll(state.imageUris);
|
||||
onNewUris(true);
|
||||
return state.getSuperState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPreviewLoaded() {
|
||||
previewsLoaded++;
|
||||
checkAllPreviewsLoaded();
|
||||
}
|
||||
|
||||
@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;
|
||||
@UiThread
|
||||
private void onError(@Nullable String errorMsg) {
|
||||
if (errorMsg == null) {
|
||||
errorMsg = imagePreview.getContext()
|
||||
.getString(R.string.image_attach_error);
|
||||
}
|
||||
imagePreview.removeUri(uri);
|
||||
if (imageUris.isEmpty()) onCancel();
|
||||
Toast.makeText(textInput.getContext(), R.string.image_attach_error,
|
||||
LENGTH_LONG).show();
|
||||
checkAllPreviewsLoaded();
|
||||
Toast.makeText(textInput.getContext(), errorMsg, LENGTH_LONG).show();
|
||||
onCancel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel() {
|
||||
textInput.clearText();
|
||||
attachmentManager.cancel();
|
||||
reset();
|
||||
}
|
||||
|
||||
private void checkAllPreviewsLoaded() {
|
||||
if (previewsLoaded == imageUris.size()) {
|
||||
loadingPreviews = false;
|
||||
// all previews were loaded
|
||||
updateViewState();
|
||||
}
|
||||
}
|
||||
|
||||
public void showImageOnboarding(Activity activity,
|
||||
Runnable onOnboardingSeen) {
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.briarproject.briar.android.view;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.UiThread;
|
||||
@@ -10,6 +9,7 @@ import android.view.View;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.view.EmojiTextInputView.TextInputListener;
|
||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -85,7 +85,7 @@ public class TextSendController implements TextInputListener {
|
||||
}
|
||||
|
||||
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="image_caption_hint">Add a caption (optional)</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_hint">Contact name</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.test.BrambleMockTestCase;
|
||||
import org.briarproject.briar.android.conversation.ImageHelper.DecodeResult;
|
||||
import org.briarproject.briar.android.attachment.ImageHelper.DecodeResult;
|
||||
import org.briarproject.briar.api.messaging.Attachment;
|
||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||
@@ -19,7 +19,7 @@ import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class AttachmentControllerTest extends BrambleMockTestCase {
|
||||
public class AttachmentRetrieverTest extends BrambleMockTestCase {
|
||||
|
||||
private final AttachmentDimensions dimensions = new AttachmentDimensions(
|
||||
100, 50, 200, 75, 300
|
||||
@@ -32,8 +32,8 @@ public class AttachmentControllerTest extends BrambleMockTestCase {
|
||||
private final MessagingManager messagingManager =
|
||||
context.mock(MessagingManager.class);
|
||||
private final ImageHelper imageHelper = context.mock(ImageHelper.class);
|
||||
private final AttachmentController controller =
|
||||
new AttachmentController(
|
||||
private final AttachmentRetriever controller =
|
||||
new AttachmentRetriever(
|
||||
messagingManager,
|
||||
dimensions,
|
||||
imageHelper
|
||||
@@ -94,23 +94,6 @@ public class AttachmentControllerTest extends BrambleMockTestCase {
|
||||
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
|
||||
public void testBigJpegImage() {
|
||||
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;
|
||||
|
||||
@@ -25,4 +25,15 @@ public class AttachmentHeader {
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @throws FileTooBigException
|
||||
*/
|
||||
AttachmentHeader addLocalAttachment(GroupId groupId, long timestamp,
|
||||
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.
|
||||
*/
|
||||
|
||||
@@ -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.GroupId;
|
||||
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.MessageStatus;
|
||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
||||
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.conversation.ConversationMessageHeader;
|
||||
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.briarproject.briar.api.messaging.PrivateMessage;
|
||||
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 java.io.ByteArrayInputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
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;
|
||||
|
||||
@Immutable
|
||||
@@ -55,15 +58,18 @@ class MessagingManagerImpl extends ConversationClientImpl
|
||||
|
||||
private final ClientVersioningManager clientVersioningManager;
|
||||
private final ContactGroupFactory contactGroupFactory;
|
||||
private final MessageFactory messageFactory;
|
||||
|
||||
@Inject
|
||||
MessagingManagerImpl(DatabaseComponent db, ClientHelper clientHelper,
|
||||
ClientVersioningManager clientVersioningManager,
|
||||
MetadataParser metadataParser, MessageTracker messageTracker,
|
||||
ContactGroupFactory contactGroupFactory) {
|
||||
ContactGroupFactory contactGroupFactory,
|
||||
MessageFactory messageFactory) {
|
||||
super(db, clientHelper, metadataParser, messageTracker);
|
||||
this.clientVersioningManager = clientVersioningManager;
|
||||
this.contactGroupFactory = contactGroupFactory;
|
||||
this.messageFactory = messageFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -158,12 +164,29 @@ class MessagingManagerImpl extends ConversationClientImpl
|
||||
|
||||
@Override
|
||||
public AttachmentHeader addLocalAttachment(GroupId groupId, long timestamp,
|
||||
String contentType, InputStream is) throws IOException {
|
||||
String contentType, InputStream is)
|
||||
throws DbException, IOException {
|
||||
// TODO add real implementation
|
||||
if (is.available() == 0) throw new IOException();
|
||||
byte[] b = new byte[MessageId.LENGTH];
|
||||
new Random().nextBytes(b);
|
||||
return new AttachmentHeader(new MessageId(b), "image/png");
|
||||
byte[] body = new byte[MAX_MESSAGE_BODY_LENGTH];
|
||||
try {
|
||||
IoUtils.read(is, body);
|
||||
} 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)
|
||||
@@ -242,12 +265,10 @@ class MessagingManagerImpl extends ConversationClientImpl
|
||||
}
|
||||
|
||||
@Override
|
||||
public Attachment getAttachment(MessageId m) {
|
||||
public Attachment getAttachment(MessageId mId) throws DbException {
|
||||
// TODO add real implementation
|
||||
byte[] bytes = fromHexString("89504E470D0A1A0A0000000D49484452" +
|
||||
"000000010000000108060000001F15C4" +
|
||||
"890000000A49444154789C6300010000" +
|
||||
"0500010D0A2DB40000000049454E44AE426082");
|
||||
Message m = clientHelper.getMessage(mId);
|
||||
byte[] bytes = m.getBody();
|
||||
return new Attachment(new ByteArrayInputStream(bytes));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user