Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • briar/briar
  • goapunk/briar
  • johndoe4221/briar
  • thomas/briar
4 results
Show changes
Commits on Source (4)
Showing
with 224 additions and 224 deletions
package org.briarproject.briar.android.attachment; package org.briarproject.briar.android.attachment;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MutableLiveData;
import android.arch.lifecycle.Observer;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.Nullable;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.briar.api.messaging.Attachment;
import org.briarproject.briar.api.messaging.AttachmentHeader; import org.briarproject.briar.api.messaging.AttachmentHeader;
import org.briarproject.briar.api.messaging.MessagingManager; import org.briarproject.briar.api.messaging.MessagingManager;
import org.jsoup.UnsupportedMimeTypeException; import org.jsoup.UnsupportedMimeTypeException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.IoUtils.tryToClose; import static org.briarproject.bramble.util.IoUtils.tryToClose;
...@@ -25,64 +36,97 @@ import static org.briarproject.bramble.util.LogUtils.logException; ...@@ -25,64 +36,97 @@ import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now; import static org.briarproject.bramble.util.LogUtils.now;
import static org.briarproject.briar.api.messaging.MessagingConstants.IMAGE_MIME_TYPES; import static org.briarproject.briar.api.messaging.MessagingConstants.IMAGE_MIME_TYPES;
@ThreadSafe
@NotNullByDefault @NotNullByDefault
class AttachmentCreationTask { class AttachmentCreationTask {
private static Logger LOG = private static Logger LOG =
getLogger(AttachmentCreationTask.class.getName()); getLogger(AttachmentCreationTask.class.getName());
private final Executor ioExecutor;
private final MessagingManager messagingManager; private final MessagingManager messagingManager;
private final ContentResolver contentResolver; private final ContentResolver contentResolver;
private final AttachmentRetriever retriever;
private final GroupId groupId; private final GroupId groupId;
private final Collection<Uri> uris; private final Collection<Uri> uris;
private final boolean needsSize; private final boolean needsSize;
@Nullable private final MutableLiveData<AttachmentResult> result;
private volatile AttachmentCreator attachmentCreator;
private volatile boolean canceled = false; private volatile boolean canceled = false;
AttachmentCreationTask(MessagingManager messagingManager, AttachmentCreationTask(Executor ioExecutor,
ContentResolver contentResolver, MessagingManager messagingManager, ContentResolver contentResolver,
AttachmentCreator attachmentCreator, GroupId groupId, AttachmentRetriever retriever, GroupId groupId,
Collection<Uri> uris, boolean needsSize) { Collection<Uri> uris, boolean needsSize) {
this.ioExecutor = ioExecutor;
this.messagingManager = messagingManager; this.messagingManager = messagingManager;
this.contentResolver = contentResolver; this.contentResolver = contentResolver;
this.retriever = retriever;
this.groupId = groupId; this.groupId = groupId;
this.uris = uris; this.uris = uris;
this.needsSize = needsSize; this.needsSize = needsSize;
this.attachmentCreator = attachmentCreator; result = new MutableLiveData<>();
}
LiveData<AttachmentResult> getResult() {
return result;
} }
public void cancel() { /**
* Cancels the task, asynchronously waits for it to finish, and deletes any
* created attachments.
*/
void cancel() {
canceled = true; canceled = true;
attachmentCreator = null; // Observe the task until it finishes (which may already have happened)
result.observeForever(new Observer<AttachmentResult>() {
@Override
public void onChanged(@Nullable AttachmentResult attachmentResult) {
requireNonNull(attachmentResult);
if (attachmentResult.isFinished()) {
deleteUnsentAttachments(attachmentResult.getItemResults());
result.removeObserver(this);
}
}
});
} }
@IoExecutor /**
public void storeAttachments() { * Asynchronously creates and stores the attachments.
for (Uri uri: uris) processUri(uri); */
AttachmentCreator attachmentCreator = this.attachmentCreator; void storeAttachments() {
if (!canceled && attachmentCreator != null) ioExecutor.execute(() -> {
attachmentCreator.onAttachmentCreationFinished(); if (LOG.isLoggable(INFO))
this.attachmentCreator = null; LOG.info("Storing " + uris.size() + " attachments");
List<AttachmentItemResult> results = new ArrayList<>();
for (Uri uri : uris) {
if (canceled) break;
results.add(processUri(uri));
result.postValue(new AttachmentResult(new ArrayList<>(results),
false, false));
}
result.postValue(new AttachmentResult(new ArrayList<>(results),
true, !canceled));
});
} }
@IoExecutor @IoExecutor
private void processUri(Uri uri) { private AttachmentItemResult processUri(Uri uri) {
if (canceled) return; AttachmentHeader header = null;
try { try {
AttachmentHeader h = storeAttachment(uri); header = storeAttachment(uri);
AttachmentCreator attachmentCreator = this.attachmentCreator; Attachment a = retriever.getMessageAttachment(header);
if (attachmentCreator != null) { AttachmentItem item =
attachmentCreator.onAttachmentHeaderReceived(uri, h, needsSize); retriever.getAttachmentItem(header, a, needsSize);
} if (item.hasError()) throw new IOException();
if (needsSize) retriever.cachePut(item);
return new AttachmentItemResult(uri, item);
} catch (DbException | IOException e) { } catch (DbException | IOException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
AttachmentCreator attachmentCreator = this.attachmentCreator; // If the attachment was already stored, delete it
if (attachmentCreator != null) { tryToRemove(header);
attachmentCreator.onAttachmentError(uri, e);
}
canceled = true; canceled = true;
return new AttachmentItemResult(uri, e);
} }
} }
...@@ -113,4 +157,31 @@ class AttachmentCreationTask { ...@@ -113,4 +157,31 @@ class AttachmentCreationTask {
return false; return false;
} }
private void tryToRemove(@Nullable AttachmentHeader h) {
try {
if (h != null) messagingManager.removeAttachment(h);
} catch (DbException e1) {
logException(LOG, WARNING, e1);
}
}
private void deleteUnsentAttachments(
Collection<AttachmentItemResult> itemResults) {
List<AttachmentHeader> headers = new ArrayList<>(itemResults.size());
for (AttachmentItemResult itemResult : itemResults) {
AttachmentItem item = itemResult.getItem();
if (item != null) headers.add(item.getHeader());
}
if (LOG.isLoggable(INFO))
LOG.info("Deleting " + headers.size() + " unsent attachments");
ioExecutor.execute(() -> {
for (AttachmentHeader header : headers) {
try {
messagingManager.removeAttachment(header);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}
});
}
} }
package org.briarproject.briar.android.attachment; package org.briarproject.briar.android.attachment;
import android.app.Application; import android.app.Application;
import android.arch.lifecycle.LiveData; import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MutableLiveData;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.annotation.UiThread; import android.support.annotation.UiThread;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId; 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.AttachmentHeader;
import org.briarproject.briar.api.messaging.FileTooBigException;
import org.briarproject.briar.api.messaging.MessagingManager; import org.briarproject.briar.api.messaging.MessagingManager;
import org.jsoup.UnsupportedMimeTypeException;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger;
import static java.util.logging.Level.WARNING; import static java.util.Collections.emptyList;
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 @NotNullByDefault
public class AttachmentCreator { public class AttachmentCreator {
private static Logger LOG = getLogger(AttachmentCreator.class.getName());
private final Application app; private final Application app;
@IoExecutor @IoExecutor
private final Executor ioExecutor; private final Executor ioExecutor;
private final MessagingManager messagingManager; private final MessagingManager messagingManager;
private final AttachmentRetriever retriever; 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 @Nullable
private AttachmentCreationTask task; private AttachmentCreationTask task;
...@@ -62,20 +39,19 @@ public class AttachmentCreator { ...@@ -62,20 +39,19 @@ public class AttachmentCreator {
this.retriever = retriever; this.retriever = retriever;
} }
/**
* Starts a background task to create attachments from the given URIs and
* returns a LiveData to monitor the progress of the task.
*/
@UiThread @UiThread
public LiveData<AttachmentResult> storeAttachments( public LiveData<AttachmentResult> storeAttachments(GroupId groupId,
LiveData<GroupId> groupId, Collection<Uri> newUris) { Collection<Uri> uris) {
if (task != null || !uris.isEmpty()) if (task != null) throw new IllegalStateException();
throw new IllegalStateException(); boolean needsSize = uris.size() == 1;
uris.addAll(newUris); task = new AttachmentCreationTask(ioExecutor, messagingManager,
observeForeverOnce(groupId, id -> { app.getContentResolver(), retriever, groupId, uris, needsSize);
if (id == null) throw new IllegalStateException(); task.storeAttachments();
boolean needsSize = uris.size() == 1; return task.getResult();
task = new AttachmentCreationTask(messagingManager,
app.getContentResolver(), this, id, uris, needsSize);
ioExecutor.execute(() -> task.storeAttachments());
});
return result;
} }
/** /**
...@@ -85,134 +61,49 @@ public class AttachmentCreator { ...@@ -85,134 +61,49 @@ public class AttachmentCreator {
*/ */
@UiThread @UiThread
public LiveData<AttachmentResult> getLiveAttachments() { public LiveData<AttachmentResult> getLiveAttachments() {
if (task == null || uris.isEmpty()) if (task == null) throw new IllegalStateException();
throw new IllegalStateException();
// A task is already running. It will update the result LiveData. // A task is already running. It will update the result LiveData.
// So nothing more to do here. // So nothing more to do here.
return result; return task.getResult();
}
@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));
} }
/**
* Returns the headers of any attachments created by
* {@link #storeAttachments(GroupId, Collection)}, unless
* {@link #onAttachmentsSent()} or {@link #cancel()} has been called.
*/
@UiThread @UiThread
public List<AttachmentHeader> getAttachmentHeadersForSending() { public List<AttachmentHeader> getAttachmentHeadersForSending() {
List<AttachmentHeader> headers = new ArrayList<>(itemResults.size()); if (task == null) return emptyList();
for (AttachmentItemResult itemResult : itemResults) { AttachmentResult result = task.getResult().getValue();
// check if we are trying to send attachment items with errors if (result == null) return emptyList();
if (itemResult.getItem() == null) throw new IllegalStateException(); List<AttachmentHeader> headers = new ArrayList<>();
headers.add(itemResult.getItem().getHeader()); for (AttachmentItemResult itemResult : result.getItemResults()) {
AttachmentItem item = itemResult.getItem();
if (item != null) headers.add(item.getHeader());
} }
return headers; return headers;
} }
/** /**
* Marks the attachments as sent and adds the items to the cache for display * Informs the AttachmentCreator that the attachments created by
* * {@link #storeAttachments(GroupId, Collection)} will be sent.
* @param id The MessageId of the sent message.
*/ */
@UiThread @UiThread
public void onAttachmentsSent(MessageId id) { public void onAttachmentsSent() {
List<AttachmentItem> items = new ArrayList<>(itemResults.size()); task = null; // Prevent cancel() from cancelling the task
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. * Cancels the task started by
* {@link #storeAttachments(GroupId, Collection)}, if any, unless
* {@link #onAttachmentsSent()} has been called.
*/ */
@UiThread @UiThread
public void cancel() { public void cancel() {
if (task == null) throw new AssertionError(); if (task != null) {
task.cancel(); task.cancel();
deleteUnsentAttachments(); task = null;
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);
}
} }
...@@ -15,18 +15,18 @@ public class AttachmentItemResult { ...@@ -15,18 +15,18 @@ public class AttachmentItemResult {
@Nullable @Nullable
private final AttachmentItem item; private final AttachmentItem item;
@Nullable @Nullable
private final String errorMsg; private final Exception exception;
AttachmentItemResult(Uri uri, AttachmentItem item) { AttachmentItemResult(Uri uri, AttachmentItem item) {
this.uri = uri; this.uri = uri;
this.item = item; this.item = item;
this.errorMsg = null; this.exception = null;
} }
AttachmentItemResult(Uri uri, @Nullable String errorMsg) { AttachmentItemResult(Uri uri, Exception exception) {
this.uri = uri; this.uri = uri;
this.item = null; this.item = null;
this.errorMsg = errorMsg; this.exception = exception;
} }
public Uri getUri() { public Uri getUri() {
...@@ -43,8 +43,8 @@ public class AttachmentItemResult { ...@@ -43,8 +43,8 @@ public class AttachmentItemResult {
} }
@Nullable @Nullable
public String getErrorMsg() { public Exception getException() {
return errorMsg; return exception;
} }
} }
...@@ -12,11 +12,13 @@ public class AttachmentResult { ...@@ -12,11 +12,13 @@ public class AttachmentResult {
private final Collection<AttachmentItemResult> itemResults; private final Collection<AttachmentItemResult> itemResults;
private final boolean finished; private final boolean finished;
private final boolean success;
public AttachmentResult(Collection<AttachmentItemResult> itemResults, AttachmentResult(Collection<AttachmentItemResult> itemResults,
boolean finished) { boolean finished, boolean success) {
this.itemResults = itemResults; this.itemResults = itemResults;
this.finished = finished; this.finished = finished;
this.success = success;
} }
public Collection<AttachmentItemResult> getItemResults() { public Collection<AttachmentItemResult> getItemResults() {
...@@ -27,4 +29,7 @@ public class AttachmentResult { ...@@ -27,4 +29,7 @@ public class AttachmentResult {
return finished; return finished;
} }
public boolean isSuccess() {
return success;
}
} }
...@@ -47,6 +47,7 @@ public class AttachmentRetriever { ...@@ -47,6 +47,7 @@ public class AttachmentRetriever {
private static final Logger LOG = private static final Logger LOG =
getLogger(AttachmentRetriever.class.getName()); getLogger(AttachmentRetriever.class.getName());
private static final int READ_LIMIT = 1024 * 8192; private static final int READ_LIMIT = 1024 * 8192;
private final MessagingManager messagingManager; private final MessagingManager messagingManager;
...@@ -55,7 +56,7 @@ public class AttachmentRetriever { ...@@ -55,7 +56,7 @@ public class AttachmentRetriever {
private final int minWidth, maxWidth; private final int minWidth, maxWidth;
private final int minHeight, maxHeight; private final int minHeight, maxHeight;
private final Map<MessageId, List<AttachmentItem>> attachmentCache = private final Map<MessageId, AttachmentItem> attachmentCache =
new ConcurrentHashMap<>(); new ConcurrentHashMap<>();
@VisibleForTesting @VisibleForTesting
...@@ -93,13 +94,13 @@ public class AttachmentRetriever { ...@@ -93,13 +94,13 @@ public class AttachmentRetriever {
}); });
} }
public void cachePut(MessageId messageId, List<AttachmentItem> attachments) { public void cachePut(AttachmentItem item) {
attachmentCache.put(messageId, attachments); attachmentCache.put(item.getMessageId(), item);
} }
@Nullable @Nullable
public List<AttachmentItem> cacheGet(MessageId messageId) { public AttachmentItem cacheGet(MessageId attachmentId) {
return attachmentCache.get(messageId); return attachmentCache.get(attachmentId);
} }
@DatabaseExecutor @DatabaseExecutor
...@@ -116,7 +117,6 @@ public class AttachmentRetriever { ...@@ -116,7 +117,6 @@ public class AttachmentRetriever {
return attachments; return attachments;
} }
@DatabaseExecutor
Attachment getMessageAttachment(AttachmentHeader h) throws DbException { Attachment getMessageAttachment(AttachmentHeader h) throws DbException {
return messagingManager.getAttachment(h.getMessageId()); return messagingManager.getAttachment(h.getMessageId());
} }
......
...@@ -448,14 +448,16 @@ public class ConversationActivity extends BriarActivity ...@@ -448,14 +448,16 @@ public class ConversationActivity extends BriarActivity
} }
// If the message has a single image, load its size - for multiple // If the message has a single image, load its size - for multiple
// images we use a grid so the size is fixed // images we use a grid so the size is fixed
if (h.getAttachmentHeaders().size() == 1) { List<AttachmentHeader> headers = h.getAttachmentHeaders();
List<AttachmentItem> items = attachmentRetriever.cacheGet(id); if (headers.size() == 1) {
if (items == null) { MessageId attachmentId = headers.get(0).getMessageId();
AttachmentItem item = attachmentRetriever.cacheGet(attachmentId);
if (item == null) {
LOG.info("Eagerly loading image size for latest message"); LOG.info("Eagerly loading image size for latest message");
items = attachmentRetriever.getAttachmentItems( item = attachmentRetriever.getAttachmentItems(
attachmentRetriever.getMessageAttachments( attachmentRetriever.getMessageAttachments(headers))
h.getAttachmentHeaders())); .get(0);
attachmentRetriever.cachePut(id, items); attachmentRetriever.cachePut(item);
} }
} }
} }
...@@ -543,6 +545,8 @@ public class ConversationActivity extends BriarActivity ...@@ -543,6 +545,8 @@ public class ConversationActivity extends BriarActivity
// TODO move getting the items off to IoExecutor, if size == 1 // TODO move getting the items off to IoExecutor, if size == 1
List<AttachmentItem> items = List<AttachmentItem> items =
attachmentRetriever.getAttachmentItems(attachments); attachmentRetriever.getAttachmentItems(attachments);
if (items.size() == 1)
attachmentRetriever.cachePut(items.get(0));
displayMessageAttachments(messageId, items); displayMessageAttachments(messageId, items);
} catch (DbException e) { } catch (DbException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
...@@ -553,7 +557,6 @@ public class ConversationActivity extends BriarActivity ...@@ -553,7 +557,6 @@ public class ConversationActivity extends BriarActivity
private void displayMessageAttachments(MessageId m, private void displayMessageAttachments(MessageId m,
List<AttachmentItem> items) { List<AttachmentItem> items) {
runOnUiThreadUnlessDestroyed(() -> { runOnUiThreadUnlessDestroyed(() -> {
attachmentRetriever.cachePut(m, items);
Pair<Integer, ConversationMessageItem> pair = Pair<Integer, ConversationMessageItem> pair =
adapter.getMessageItem(m); adapter.getMessageItem(m);
if (pair != null) { if (pair != null) {
...@@ -905,12 +908,17 @@ public class ConversationActivity extends BriarActivity ...@@ -905,12 +908,17 @@ public class ConversationActivity extends BriarActivity
@Override @Override
public List<AttachmentItem> getAttachmentItems(MessageId m, public List<AttachmentItem> getAttachmentItems(MessageId m,
List<AttachmentHeader> headers) { List<AttachmentHeader> headers) {
List<AttachmentItem> attachments = attachmentRetriever.cacheGet(m); List<AttachmentItem> items = new ArrayList<>(headers.size());
if (attachments == null) { for (AttachmentHeader header : headers) {
loadMessageAttachments(m, headers); AttachmentItem item =
return emptyList(); attachmentRetriever.cacheGet(header.getMessageId());
if (item == null) {
loadMessageAttachments(m, headers);
return emptyList();
}
items.add(item);
} }
return attachments; return items;
} }
} }
...@@ -4,6 +4,7 @@ import android.app.Application; ...@@ -4,6 +4,7 @@ import android.app.Application;
import android.arch.lifecycle.AndroidViewModel; import android.arch.lifecycle.AndroidViewModel;
import android.arch.lifecycle.LiveData; import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MutableLiveData; import android.arch.lifecycle.MutableLiveData;
import android.arch.lifecycle.Observer;
import android.arch.lifecycle.Transformations; import android.arch.lifecycle.Transformations;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
...@@ -124,7 +125,7 @@ public class ConversationViewModel extends AndroidViewModel ...@@ -124,7 +125,7 @@ public class ConversationViewModel extends AndroidViewModel
@Override @Override
protected void onCleared() { protected void onCleared() {
super.onCleared(); super.onCleared();
attachmentCreator.deleteUnsentAttachments(); attachmentCreator.cancel();
} }
/** /**
...@@ -200,12 +201,23 @@ public class ConversationViewModel extends AndroidViewModel ...@@ -200,12 +201,23 @@ public class ConversationViewModel extends AndroidViewModel
@UiThread @UiThread
public LiveData<AttachmentResult> storeAttachments(Collection<Uri> uris, public LiveData<AttachmentResult> storeAttachments(Collection<Uri> uris,
boolean restart) { boolean restart) {
if (restart) { MutableLiveData<AttachmentResult> delegate = new MutableLiveData<>();
return attachmentCreator.getLiveAttachments(); // messagingGroupId is loaded with the contact
} else { observeForeverOnce(messagingGroupId, groupId -> {
// messagingGroupId is loaded with the contact requireNonNull(groupId);
return attachmentCreator.storeAttachments(messagingGroupId, uris); LiveData<AttachmentResult> result;
} if (restart) result = attachmentCreator.getLiveAttachments();
else result = attachmentCreator.storeAttachments(groupId, uris);
result.observeForever(new Observer<AttachmentResult>() {
@Override
public void onChanged(@Nullable AttachmentResult value) {
requireNonNull(value);
if (value.isFinished()) result.removeObserver(this);
delegate.setValue(value);
}
});
});
return delegate;
} }
@Override @Override
...@@ -294,7 +306,7 @@ public class ConversationViewModel extends AndroidViewModel ...@@ -294,7 +306,7 @@ public class ConversationViewModel extends AndroidViewModel
} }
private void storeMessage(PrivateMessage m) { private void storeMessage(PrivateMessage m) {
attachmentCreator.onAttachmentsSent(m.getMessage().getId()); attachmentCreator.onAttachmentsSent();
dbExecutor.execute(() -> { dbExecutor.execute(() -> {
try { try {
long start = now(); long start = now();
......
...@@ -22,6 +22,8 @@ import org.briarproject.briar.android.attachment.AttachmentItemResult; ...@@ -22,6 +22,8 @@ import org.briarproject.briar.android.attachment.AttachmentItemResult;
import org.briarproject.briar.android.attachment.AttachmentManager; import org.briarproject.briar.android.attachment.AttachmentManager;
import org.briarproject.briar.android.attachment.AttachmentResult; import org.briarproject.briar.android.attachment.AttachmentResult;
import org.briarproject.briar.android.view.ImagePreview.ImagePreviewListener; import org.briarproject.briar.android.view.ImagePreview.ImagePreviewListener;
import org.briarproject.briar.api.messaging.FileTooBigException;
import org.jsoup.UnsupportedMimeTypeException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
...@@ -41,9 +43,11 @@ import static android.support.v4.content.ContextCompat.getColor; ...@@ -41,9 +43,11 @@ import static android.support.v4.content.ContextCompat.getColor;
import static android.support.v4.view.AbsSavedState.EMPTY_STATE; import static android.support.v4.view.AbsSavedState.EMPTY_STATE;
import static android.view.View.GONE; import static android.view.View.GONE;
import static android.widget.Toast.LENGTH_LONG; import static android.widget.Toast.LENGTH_LONG;
import static java.util.Objects.requireNonNull;
import static org.briarproject.briar.android.util.UiUtils.resolveColorAttribute; import static org.briarproject.briar.android.util.UiUtils.resolveColorAttribute;
import static org.briarproject.briar.api.messaging.MessagingConstants.IMAGE_MIME_TYPES; import static org.briarproject.briar.api.messaging.MessagingConstants.IMAGE_MIME_TYPES;
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_ATTACHMENTS_PER_MESSAGE; import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_ATTACHMENTS_PER_MESSAGE;
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_IMAGE_SIZE;
import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.STATE_DISMISSED; import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.STATE_DISMISSED;
import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.STATE_FINISHED; import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.STATE_FINISHED;
...@@ -186,18 +190,16 @@ public class TextAttachmentController extends TextSendController ...@@ -186,18 +190,16 @@ public class TextAttachmentController extends TextSendController
result.observe(attachmentListener, new Observer<AttachmentResult>() { result.observe(attachmentListener, new Observer<AttachmentResult>() {
@Override @Override
public void onChanged(@Nullable AttachmentResult attachmentResult) { public void onChanged(@Nullable AttachmentResult attachmentResult) {
if (attachmentResult == null) { requireNonNull(attachmentResult);
// The fresh LiveData was deliberately set to null. boolean finished = attachmentResult.isFinished();
// This means that we can stop observing it. boolean success = attachmentResult.isSuccess();
if (finished) {
result.removeObserver(this); result.removeObserver(this);
} else { if (!success) return;
boolean noError = onNewAttachmentItemResults(
attachmentResult.getItemResults());
if (noError && attachmentResult.isFinished()) {
onAllAttachmentsCreated();
result.removeObserver(this);
}
} }
boolean noError = onNewAttachmentItemResults(
attachmentResult.getItemResults());
if (noError && success) onAllAttachmentsCreated();
} }
}); });
} }
...@@ -207,7 +209,7 @@ public class TextAttachmentController extends TextSendController ...@@ -207,7 +209,7 @@ public class TextAttachmentController extends TextSendController
if (!loadingUris) throw new AssertionError(); if (!loadingUris) throw new AssertionError();
for (AttachmentItemResult result : itemResults) { for (AttachmentItemResult result : itemResults) {
if (result.hasError()) { if (result.hasError()) {
onError(result.getErrorMsg()); onError(requireNonNull(result.getException()));
return false; return false;
} else { } else {
imagePreview.loadPreviewImage(result); imagePreview.loadPreviewImage(result);
...@@ -253,12 +255,20 @@ public class TextAttachmentController extends TextSendController ...@@ -253,12 +255,20 @@ public class TextAttachmentController extends TextSendController
} }
@UiThread @UiThread
private void onError(@Nullable String errorMsg) { private void onError(Exception e) {
if (errorMsg == null) { String errorMsg;
errorMsg = imagePreview.getContext() Context ctx = imagePreview.getContext();
.getString(R.string.image_attach_error); if (e instanceof UnsupportedMimeTypeException) {
String mimeType = ((UnsupportedMimeTypeException) e).getMimeType();
errorMsg = ctx.getString(
R.string.image_attach_error_invalid_mime_type, mimeType);
} else if (e instanceof FileTooBigException) {
int mb = MAX_IMAGE_SIZE / 1024 / 1024;
errorMsg = ctx.getString(R.string.image_attach_error_too_big, mb);
} else {
errorMsg = ctx.getString(R.string.image_attach_error);
} }
Toast.makeText(textInput.getContext(), errorMsg, LENGTH_LONG).show(); Toast.makeText(ctx, errorMsg, LENGTH_LONG).show();
onCancel(); onCancel();
} }
......
...@@ -2,5 +2,8 @@ package org.briarproject.briar.api.messaging; ...@@ -2,5 +2,8 @@ package org.briarproject.briar.api.messaging;
import java.io.IOException; import java.io.IOException;
/**
* An exception that is thrown when a file is too big to attach to a message.
*/
public class FileTooBigException extends IOException { public class FileTooBigException extends IOException {
} }