Refactor attachment creation

This commit is contained in:
Torsten Grote
2019-04-05 15:48:31 -03:00
parent bb5a6c0241
commit 11eefaedcf
34 changed files with 557 additions and 288 deletions

View File

@@ -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());

View File

@@ -0,0 +1,114 @@
package org.briarproject.briar.android.attachment;
import android.content.ContentResolver;
import android.net.Uri;
import android.support.annotation.Nullable;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import org.briarproject.briar.api.messaging.MessagingManager;
import org.jsoup.UnsupportedMimeTypeException;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.logging.Logger;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.IoUtils.tryToClose;
import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now;
import static org.briarproject.briar.api.messaging.MessagingConstants.IMAGE_MIME_TYPES;
@NotNullByDefault
class AttachmentCreationTask {
private static Logger LOG =
getLogger(AttachmentCreationTask.class.getName());
private final MessagingManager messagingManager;
private final ContentResolver contentResolver;
private final GroupId groupId;
private final List<Uri> uris;
private final boolean needsSize;
@Nullable
private AttachmentCreator attachmentCreator;
private volatile boolean canceled = false;
AttachmentCreationTask(MessagingManager messagingManager,
ContentResolver contentResolver,
AttachmentCreator attachmentCreator, GroupId groupId,
List<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);
if (!canceled && attachmentCreator != null)
attachmentCreator.onAttachmentCreationFinished();
attachmentCreator = null;
}
@IoExecutor
private void processUri(Uri uri) {
if (canceled) return;
try {
AttachmentHeader h = storeAttachment(uri);
if (attachmentCreator != null) {
attachmentCreator
.onAttachmentHeaderReceived(uri, h, needsSize);
}
} catch (DbException | IOException e) {
logException(LOG, WARNING, e);
if (attachmentCreator != null) {
attachmentCreator.onAttachmentError(uri, e);
canceled = true;
}
}
}
@IoExecutor
private AttachmentHeader storeAttachment(Uri uri)
throws IOException, DbException {
long start = now();
String contentType = contentResolver.getType(uri);
if (contentType == null) throw new IOException("null content type");
if (!isValidMimeType(contentType))
throw new UnsupportedMimeTypeException("", contentType,
uri.toString());
InputStream is = contentResolver.openInputStream(uri);
if (is == null) throw new IOException();
long timestamp = System.currentTimeMillis();
AttachmentHeader h = messagingManager
.addLocalAttachment(groupId, timestamp, contentType, is);
tryToClose(is, LOG, WARNING);
logDuration(LOG, "Storing attachment", start);
return h;
}
private boolean isValidMimeType(@Nullable String mimeType) {
if (mimeType == null) return false;
for (String supportedType : IMAGE_MIME_TYPES) {
if (supportedType.equals(mimeType)) return true;
}
return false;
}
}

View File

@@ -0,0 +1,206 @@
package org.briarproject.briar.android.attachment;
import android.app.Application;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MutableLiveData;
import android.net.Uri;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.R;
import org.briarproject.briar.api.messaging.Attachment;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import org.briarproject.briar.api.messaging.FileTooBigException;
import org.briarproject.briar.api.messaging.MessagingManager;
import org.jsoup.UnsupportedMimeTypeException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_IMAGE_SIZE;
@NotNullByDefault
public class AttachmentCreator {
private static Logger LOG = getLogger(AttachmentCreator.class.getName());
private final Application app;
@IoExecutor
private final Executor ioExecutor;
private final MessagingManager messagingManager;
private final AttachmentRetriever controller;
private final Map<Uri, AttachmentItem> unsentItems =
new ConcurrentHashMap<>();
private final Map<Uri, MutableLiveData<AttachmentItemResult>>
liveDataResult = new ConcurrentHashMap<>();
@Nullable
private MutableLiveData<Boolean> liveDataFinished = null;
@Nullable
private AttachmentCreationTask task;
public AttachmentCreator(Application app, @IoExecutor Executor ioExecutor,
MessagingManager messagingManager,
AttachmentRetriever controller) {
this.app = app;
this.ioExecutor = ioExecutor;
this.messagingManager = messagingManager;
this.controller = controller;
}
@UiThread
public AttachmentResult storeAttachments(GroupId groupId,
Collection<Uri> uris) {
if (task != null && !isStoring()) throw new AssertionError();
List<LiveData<AttachmentItemResult>> itemResults = new ArrayList<>();
List<Uri> urisToStore = new ArrayList<>();
for (Uri uri : uris) {
MutableLiveData<AttachmentItemResult> liveData =
new MutableLiveData<>();
itemResults.add(liveData);
liveDataResult.put(uri, liveData);
if (unsentItems.containsKey(uri)) {
// This can happen due to configuration changes.
// So don't create a new attachment, if we have one already.
AttachmentItem item = requireNonNull(unsentItems.get(uri));
AttachmentItemResult result =
new AttachmentItemResult(uri, item);
liveData.setValue(result);
} else {
urisToStore.add(uri);
}
}
boolean needsSize = uris.size() == 1;
task = new AttachmentCreationTask(messagingManager,
app.getContentResolver(), this, groupId, urisToStore,
needsSize);
ioExecutor.execute(() -> task.storeAttachments());
liveDataFinished = new MutableLiveData<>();
return new AttachmentResult(itemResults, liveDataFinished);
}
@IoExecutor
void onAttachmentHeaderReceived(Uri uri, AttachmentHeader h,
boolean needsSize) {
// get and cache AttachmentItem for ImagePreview
try {
Attachment a = controller.getMessageAttachment(h);
AttachmentItem item = controller.getAttachmentItem(h, a, needsSize);
if (item.hasError()) throw new IOException();
unsentItems.put(uri, item);
MutableLiveData<AttachmentItemResult> result =
liveDataResult.get(uri);
if (result != null) { // might have been cleared on UiThread
result.postValue(new AttachmentItemResult(uri, item));
}
} catch (IOException | DbException e) {
logException(LOG, WARNING, e);
onAttachmentError(uri, e);
}
}
@IoExecutor
void onAttachmentError(Uri uri, Throwable t) {
String errorMsg;
if (t instanceof UnsupportedMimeTypeException) {
String mimeType = ((UnsupportedMimeTypeException) t).getMimeType();
errorMsg = app.getString(
R.string.image_attach_error_invalid_mime_type, mimeType);
} else if (t instanceof FileTooBigException) {
int mb = MAX_IMAGE_SIZE / 1024 / 1024;
errorMsg = app.getString(R.string.image_attach_error_too_big, mb);
} else {
errorMsg = null; // generic error
}
MutableLiveData<AttachmentItemResult> result = liveDataResult.get(uri);
if (result != null)
result.postValue(new AttachmentItemResult(errorMsg));
// expect to receive a cancel from the UI
}
@IoExecutor
void onAttachmentCreationFinished() {
if (liveDataFinished != null) liveDataFinished.postValue(true);
}
@UiThread
public List<AttachmentHeader> getAttachmentHeadersForSending() {
List<AttachmentHeader> headers =
new ArrayList<>(unsentItems.values().size());
for (AttachmentItem item : unsentItems.values()) {
headers.add(item.getHeader());
}
return headers;
}
/**
* Marks the attachments as sent and adds the items to the cache for display
*
* @param id The MessageId of the sent message.
*/
public void onAttachmentsSent(MessageId id) {
controller.cachePut(id, new ArrayList<>(unsentItems.values()));
resetState();
}
@UiThread
public void cancel() {
if (task == null) throw new AssertionError();
task.cancel();
// let observers know that they can remove themselves
for (MutableLiveData<AttachmentItemResult> liveData : liveDataResult
.values()) {
if (liveData.getValue() == null) {
liveData.setValue(null);
}
}
if (liveDataFinished != null) liveDataFinished.setValue(false);
deleteUnsentAttachments();
resetState();
}
@UiThread
private void resetState() {
task = null;
liveDataResult.clear();
liveDataFinished = null;
unsentItems.clear();
}
@UiThread
public void deleteUnsentAttachments() {
List<AttachmentItem> itemsToDelete =
new ArrayList<>(unsentItems.values());
ioExecutor.execute(() -> {
for (AttachmentItem item : itemsToDelete) {
try {
messagingManager.removeAttachment(item.getHeader());
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}
});
}
private boolean isStoring() {
return liveDataFinished != null;
}
}

View File

@@ -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);
}
}

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.android.conversation;
package org.briarproject.briar.android.attachment;
import android.os.Parcel;
import android.os.Parcelable;
@@ -12,6 +12,8 @@ import java.util.concurrent.atomic.AtomicLong;
import javax.annotation.concurrent.Immutable;
import static java.util.Objects.requireNonNull;
@Immutable
@NotNullByDefault
public class AttachmentItem implements Parcelable {
@@ -57,8 +59,8 @@ public class AttachmentItem implements Parcelable {
MessageId messageId = new MessageId(messageIdByte);
width = in.readInt();
height = in.readInt();
String mimeType = in.readString();
extension = in.readString();
String mimeType = requireNonNull(in.readString());
extension = requireNonNull(in.readString());
thumbnailWidth = in.readInt();
thumbnailHeight = in.readInt();
hasError = in.readByte() != 0;
@@ -82,27 +84,27 @@ public class AttachmentItem implements Parcelable {
return height;
}
String getMimeType() {
public String getMimeType() {
return header.getContentType();
}
String getExtension() {
public String getExtension() {
return extension;
}
int getThumbnailWidth() {
public int getThumbnailWidth() {
return thumbnailWidth;
}
int getThumbnailHeight() {
public int getThumbnailHeight() {
return thumbnailHeight;
}
boolean hasError() {
public boolean hasError() {
return hasError;
}
String getTransitionName() {
public String getTransitionName() {
return String.valueOf(instanceId);
}

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.android.conversation;
package org.briarproject.briar.android.attachment;
import android.net.Uri;
@@ -9,7 +9,7 @@ import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
public class AttachmentResult {
public class AttachmentItemResult {
@Nullable
private final Uri uri;
@@ -18,13 +18,13 @@ public class AttachmentResult {
@Nullable
private final String errorMsg;
public AttachmentResult(Uri uri, AttachmentItem item) {
public AttachmentItemResult(Uri uri, AttachmentItem item) {
this.uri = uri;
this.item = item;
this.errorMsg = null;
}
public AttachmentResult(@Nullable String errorMsg) {
public AttachmentItemResult(@Nullable String errorMsg) {
this.uri = null;
this.item = null;
this.errorMsg = errorMsg;

View File

@@ -0,0 +1,20 @@
package org.briarproject.briar.android.attachment;
import android.net.Uri;
import android.support.annotation.UiThread;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import java.util.Collection;
import java.util.List;
@UiThread
public interface AttachmentManager{
AttachmentResult storeAttachments(Collection<Uri> uri);
List<AttachmentHeader> getAttachmentHeadersForSending();
void cancel();
}

View File

@@ -0,0 +1,33 @@
package org.briarproject.briar.android.attachment;
import android.arch.lifecycle.LiveData;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.Collection;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
public class AttachmentResult {
private final Collection<LiveData<AttachmentItemResult>> itemResults;
private final LiveData<Boolean> finished;
public AttachmentResult(
Collection<LiveData<AttachmentItemResult>> itemResults,
LiveData<Boolean> finished) {
this.itemResults = itemResults;
this.finished = finished;
}
public Collection<LiveData<AttachmentItemResult>> getItemResults() {
return itemResults;
}
public LiveData<Boolean> getFinished() {
return finished;
}
}

View File

@@ -1,9 +1,7 @@
package org.briarproject.briar.android.conversation;
package org.briarproject.briar.android.attachment;
import android.content.ContentResolver;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;
import android.net.Uri;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.support.media.ExifInterface;
@@ -15,9 +13,8 @@ import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.android.conversation.ImageHelper.DecodeResult;
import org.briarproject.briar.android.attachment.ImageHelper.DecodeResult;
import org.briarproject.briar.api.messaging.Attachment;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import org.briarproject.briar.api.messaging.MessagingManager;
@@ -38,20 +35,18 @@ import static android.support.media.ExifInterface.ORIENTATION_TRANSVERSE;
import static android.support.media.ExifInterface.TAG_IMAGE_LENGTH;
import static android.support.media.ExifInterface.TAG_IMAGE_WIDTH;
import static android.support.media.ExifInterface.TAG_ORIENTATION;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.IoUtils.tryToClose;
import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now;
import static org.briarproject.briar.api.messaging.MessagingConstants.IMAGE_MIME_TYPES;
@NotNullByDefault
class AttachmentController {
public class AttachmentRetriever {
private static final Logger LOG =
getLogger(AttachmentController.class.getName());
getLogger(AttachmentRetriever.class.getName());
private static final int READ_LIMIT = 1024 * 8192;
private final MessagingManager messagingManager;
@@ -60,12 +55,11 @@ class AttachmentController {
private final int minWidth, maxWidth;
private final int minHeight, maxHeight;
private final Map<Uri, AttachmentItem> unsentItems =
new ConcurrentHashMap<>();
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;
@@ -76,7 +70,7 @@ class AttachmentController {
maxHeight = dimensions.maxHeight;
}
AttachmentController(MessagingManager messagingManager,
public AttachmentRetriever(MessagingManager messagingManager,
AttachmentDimensions dimensions) {
this(messagingManager, dimensions, new ImageHelper() {
@Override
@@ -99,17 +93,17 @@ class AttachmentController {
});
}
void put(MessageId messageId, List<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 =
@@ -118,86 +112,13 @@ class AttachmentController {
Attachment a = messagingManager.getAttachment(h.getMessageId());
attachments.add(new Pair<>(h, a));
}
logDuration(LOG, "Loading attachment", start);
logDuration(LOG, "Loading attachments", start);
return attachments;
}
@DatabaseExecutor
AttachmentItem createAttachmentHeader(ContentResolver contentResolver,
GroupId groupId, Uri uri, boolean needsSize)
throws IOException, DbException {
if (unsentItems.containsKey(uri)) {
// This can happen due to configuration (screen orientation) change.
// So don't create a new attachment, if we have one already.
return requireNonNull(unsentItems.get(uri));
}
long start = now();
InputStream is = contentResolver.openInputStream(uri);
if (is == null) throw new IOException();
String contentType = contentResolver.getType(uri);
if (contentType == null) throw new IOException("null content type");
long timestamp = System.currentTimeMillis();
AttachmentHeader h = messagingManager
.addLocalAttachment(groupId, timestamp, contentType, is);
tryToClose(is, LOG, WARNING);
logDuration(LOG, "Storing attachment", start);
// get and store AttachmentItem for ImagePreview
AttachmentItem item =
getAttachmentItem(contentResolver, uri, h, needsSize);
if (item.hasError()) throw new IOException();
unsentItems.put(uri, item);
return item;
}
boolean isValidMimeType(@Nullable String mimeType) {
if (mimeType == null) return false;
for (String supportedType : IMAGE_MIME_TYPES) {
if (supportedType.equals(mimeType)) return true;
}
return false;
}
@DatabaseExecutor
void deleteUnsentAttachments() {
for (AttachmentItem item : unsentItems.values()) {
try {
messagingManager.removeAttachment(item.getHeader());
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}
unsentItems.clear();
}
List<AttachmentHeader> getUnsentAttachmentHeaders() {
List<AttachmentHeader> headers =
new ArrayList<>(unsentItems.values().size());
for (AttachmentItem item : unsentItems.values()) {
headers.add(item.getHeader());
}
return headers;
}
/**
* Marks the attachments as sent and adds the items to the cache for display
* @param id The MessageId of the sent message.
*/
void onAttachmentsSent(MessageId id) {
attachmentCache.put(id, new ArrayList<>(unsentItems.values()));
unsentItems.clear();
}
@DatabaseExecutor
private AttachmentItem getAttachmentItem(ContentResolver contentResolver,
Uri uri, AttachmentHeader h, boolean needsSize) throws IOException {
InputStream is = null;
try {
is = contentResolver.openInputStream(uri);
if (is == null) throw new IOException();
return getAttachmentItem(h, new Attachment(is), needsSize);
} finally {
if (is != null) tryToClose(is, LOG, WARNING);
}
Attachment getMessageAttachment(AttachmentHeader h) throws DbException {
return messagingManager.getAttachment(h.getMessageId());
}
/**
@@ -205,7 +126,7 @@ class AttachmentController {
* <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());
@@ -221,7 +142,6 @@ class AttachmentController {
* Creates an {@link AttachmentItem} from the {@link Attachment}'s
* {@link InputStream} which will be closed when this method returns.
*/
@VisibleForTesting
AttachmentItem getAttachmentItem(AttachmentHeader h, Attachment a,
boolean needsSize) {
if (!needsSize) {

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.android.conversation;
package org.briarproject.briar.android.attachment;
import android.support.annotation.Nullable;

View File

@@ -52,6 +52,8 @@ import org.briarproject.bramble.api.sync.event.MessagesSentEvent;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.attachment.AttachmentItem;
import org.briarproject.briar.android.attachment.AttachmentRetriever;
import org.briarproject.briar.android.blog.BlogActivity;
import org.briarproject.briar.android.conversation.ConversationVisitor.AttachmentCache;
import org.briarproject.briar.android.conversation.ConversationVisitor.TextCache;
@@ -183,7 +185,7 @@ public class ConversationActivity extends BriarActivity
loadMessages();
};
private AttachmentController attachmentController;
private AttachmentRetriever attachmentRetriever;
private ConversationViewModel viewModel;
private ConversationVisitor visitor;
private ConversationAdapter adapter;
@@ -218,7 +220,7 @@ public class ConversationActivity extends BriarActivity
viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(ConversationViewModel.class);
attachmentController = viewModel.getAttachmentController();
attachmentRetriever = viewModel.getAttachmentRetriever();
setContentView(R.layout.activity_conversation);
@@ -456,13 +458,13 @@ public class ConversationActivity extends BriarActivity
// If the message has a single image, load its size - for multiple
// images we use a grid so the size is fixed
if (h.getAttachmentHeaders().size() == 1) {
List<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);
}
}
}
@@ -544,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);
@@ -558,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) {
@@ -903,7 +905,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();

View File

@@ -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

View File

@@ -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;

View File

@@ -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;

View File

@@ -5,7 +5,6 @@ import android.arch.lifecycle.AndroidViewModel;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MutableLiveData;
import android.arch.lifecycle.Transformations;
import android.content.ContentResolver;
import android.net.Uri;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
@@ -20,26 +19,26 @@ import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.NoSuchContactException;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.api.settings.SettingsManager;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.briar.R;
import org.briarproject.briar.android.attachment.AttachmentCreator;
import org.briarproject.briar.android.attachment.AttachmentManager;
import org.briarproject.briar.android.attachment.AttachmentResult;
import org.briarproject.briar.android.attachment.AttachmentRetriever;
import org.briarproject.briar.android.util.UiUtils;
import org.briarproject.briar.android.view.TextAttachmentController.AttachmentManager;
import org.briarproject.briar.android.viewmodel.LiveEvent;
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import org.briarproject.briar.api.messaging.FileTooBigException;
import org.briarproject.briar.api.messaging.MessagingManager;
import org.briarproject.briar.api.messaging.PrivateMessage;
import org.briarproject.briar.api.messaging.PrivateMessageFactory;
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Executor;
@@ -53,14 +52,12 @@ import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now;
import static org.briarproject.briar.android.conversation.AttachmentDimensions.getAttachmentDimensions;
import static org.briarproject.briar.android.attachment.AttachmentDimensions.getAttachmentDimensions;
import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce;
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_IMAGE_SIZE;
@NotNullByDefault
public class ConversationViewModel extends AndroidViewModel implements
AttachmentManager {
public class ConversationViewModel extends AndroidViewModel
implements AttachmentManager {
private static Logger LOG =
getLogger(ConversationViewModel.class.getName());
@@ -78,10 +75,13 @@ public class ConversationViewModel extends AndroidViewModel implements
private final ContactManager contactManager;
private final SettingsManager settingsManager;
private final PrivateMessageFactory privateMessageFactory;
private final AttachmentController attachmentController;
private final AttachmentRetriever attachmentRetriever;
private final AttachmentCreator attachmentCreator;
@Nullable
private ContactId contactId = null;
@Nullable
private volatile GroupId messagingGroupId = null;
private final MutableLiveData<Contact> contact = new MutableLiveData<>();
private final LiveData<AuthorId> contactAuthorId =
Transformations.map(contact, c -> c.getAuthor().getId());
@@ -97,15 +97,14 @@ public class ConversationViewModel extends AndroidViewModel implements
new MutableLiveData<>();
private final MutableLiveData<Boolean> contactDeleted =
new MutableLiveData<>();
private final MutableLiveData<GroupId> messagingGroupId =
new MutableLiveData<>();
private final MutableLiveData<PrivateMessageHeader> addedHeader =
new MutableLiveData<>();
@Inject
ConversationViewModel(Application application,
@DatabaseExecutor Executor dbExecutor,
@CryptoExecutor Executor cryptoExecutor, TransactionManager db,
@CryptoExecutor Executor cryptoExecutor,
@IoExecutor Executor ioExecutor, TransactionManager db,
MessagingManager messagingManager, ContactManager contactManager,
SettingsManager settingsManager,
PrivateMessageFactory privateMessageFactory) {
@@ -117,15 +116,17 @@ public class ConversationViewModel extends AndroidViewModel implements
this.contactManager = contactManager;
this.settingsManager = settingsManager;
this.privateMessageFactory = privateMessageFactory;
this.attachmentController = new AttachmentController(messagingManager,
this.attachmentRetriever = new AttachmentRetriever(messagingManager,
getAttachmentDimensions(application.getResources()));
this.attachmentCreator = new AttachmentCreator(getApplication(),
ioExecutor, messagingManager, attachmentRetriever);
contactDeleted.setValue(false);
}
@Override
protected void onCleared() {
super.onCleared();
attachmentController.deleteUnsentAttachments();
attachmentCreator.deleteUnsentAttachments();
}
/**
@@ -149,6 +150,10 @@ public class ConversationViewModel extends AndroidViewModel implements
contact.postValue(c);
logDuration(LOG, "Loading contact", start);
start = now();
messagingGroupId =
messagingManager.getConversationId(contactId);
logDuration(LOG, "Load conversation GroupId", start);
start = now();
checkFeaturesAndOnboarding(contactId);
logDuration(LOG, "Checking for image support", start);
} catch (NoSuchContactException e) {
@@ -185,73 +190,29 @@ public class ConversationViewModel extends AndroidViewModel implements
void sendMessage(@Nullable String text,
List<AttachmentHeader> attachmentHeaders, long timestamp) {
if (messagingGroupId.getValue() == null) loadGroupId();
observeForeverOnce(messagingGroupId, groupId -> {
if (groupId == null) return;
createMessage(groupId, text, attachmentHeaders, timestamp);
});
GroupId groupId = messagingGroupId;
if (groupId == null) throw new IllegalStateException();
createMessage(groupId, text, attachmentHeaders, timestamp);
}
@Override
public LiveData<AttachmentResult> storeAttachment(Uri uri,
boolean needsSize) {
// use LiveData to not keep references to view scope
MutableLiveData<AttachmentResult> result = new MutableLiveData<>();
// check first if mime type is supported
ContentResolver contentResolver =
getApplication().getContentResolver();
String mimeType = contentResolver.getType(uri);
if (!attachmentController.isValidMimeType(mimeType)) {
String errorMsg = getApplication().getString(
R.string.image_attach_error_invalid_mime_type, mimeType);
result.setValue(new AttachmentResult(errorMsg));
return result;
}
if (messagingGroupId.getValue() == null) loadGroupId();
observeForeverOnce(messagingGroupId, groupId -> dbExecutor.execute(()
-> {
if (groupId == null) throw new IllegalStateException();
long start = now();
try {
AttachmentItem item = attachmentController
.createAttachmentHeader(contentResolver, groupId, uri,
needsSize);
result.postValue(new AttachmentResult(uri, item));
} catch(FileTooBigException e) {
logException(LOG, WARNING, e);
int mb = MAX_IMAGE_SIZE / 1024 / 1024;
String errorMsg = getApplication()
.getString(R.string.image_attach_error_too_big, mb);
result.postValue(new AttachmentResult(errorMsg));
} catch (DbException | IOException e) {
logException(LOG, WARNING, e);
result.postValue(new AttachmentResult(null));
}
logDuration(LOG, "Storing attachment", start);
}));
return result;
@UiThread
public AttachmentResult storeAttachments(Collection<Uri> uris) {
GroupId groupId = messagingGroupId;
if (groupId == null) throw new IllegalStateException();
return attachmentCreator.storeAttachments(groupId, uris);
}
@Override
public List<AttachmentHeader> getAttachmentHeaders() {
return attachmentController.getUnsentAttachmentHeaders();
@UiThread
public List<AttachmentHeader> getAttachmentHeadersForSending() {
return attachmentCreator.getAttachmentHeadersForSending();
}
@Override
public void removeAttachments() {
dbExecutor.execute(attachmentController::deleteUnsentAttachments);
}
private void loadGroupId() {
if (contactId == null) throw new IllegalStateException();
dbExecutor.execute(() -> {
try {
messagingGroupId.postValue(
messagingManager.getConversationId(contactId));
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
@UiThread
public void cancel() {
attachmentCreator.cancel();
}
@DatabaseExecutor
@@ -337,7 +298,7 @@ public class ConversationViewModel extends AndroidViewModel implements
message.getId(), message.getGroupId(),
message.getTimestamp(), true, true, false, false,
text != null, attachments);
attachmentController.onAttachmentsSent(m.getMessage().getId());
attachmentCreator.onAttachmentsSent(m.getMessage().getId());
// TODO add text to cache when available here
addedHeader.postValue(h);
} catch (DbException e) {
@@ -351,8 +312,8 @@ public class ConversationViewModel extends AndroidViewModel implements
addedHeader.setValue(null);
}
AttachmentController getAttachmentController() {
return attachmentController;
AttachmentRetriever getAttachmentRetriever() {
return attachmentRetriever;
}
LiveData<Contact> getContact() {

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -9,7 +9,7 @@ import android.view.LayoutInflater;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.conversation.AttachmentResult;
import org.briarproject.briar.android.attachment.AttachmentItemResult;
import java.util.Collection;
@@ -72,7 +72,7 @@ public class ImagePreview extends ConstraintLayout {
imageList.setAdapter(adapter);
}
void loadPreviewImage(AttachmentResult result) {
void loadPreviewImage(AttachmentItemResult result) {
ImagePreviewAdapter adapter =
((ImagePreviewAdapter) imageList.getAdapter());
requireNonNull(adapter).loadItemPreview(result);

View File

@@ -8,7 +8,7 @@ import android.view.ViewGroup;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.conversation.AttachmentResult;
import org.briarproject.briar.android.attachment.AttachmentItemResult;
import java.util.ArrayList;
import java.util.Collection;
@@ -50,7 +50,7 @@ class ImagePreviewAdapter extends Adapter<ImagePreviewViewHolder> {
return items.size();
}
void loadItemPreview(AttachmentResult result) {
void loadItemPreview(AttachmentItemResult result) {
ImagePreviewItem newItem =
new ImagePreviewItem(requireNonNull(result.getUri()));
int pos = items.indexOf(newItem);

View File

@@ -4,7 +4,7 @@ import android.net.Uri;
import android.support.annotation.Nullable;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.android.conversation.AttachmentItem;
import org.briarproject.briar.android.attachment.AttachmentItem;
import java.util.ArrayList;
import java.util.Collection;
@@ -30,10 +30,6 @@ class ImagePreviewItem {
return items;
}
Uri getUri() {
return uri;
}
public void setItem(AttachmentItem item) {
this.item = item;
}

View File

@@ -38,9 +38,9 @@ class ImagePreviewViewHolder extends ViewHolder {
}
void bind(ImagePreviewItem item) {
if (item.getItem() == null) return;
if (item.getItem() == null) return; // shows progress bar
GlideApp.with(imageView)
.load(item.getUri())
.load(item.getItem())
.diskCacheStrategy(NONE)
.error(ERROR_RES)
.downsample(FIT_CENTER)

View File

@@ -3,6 +3,7 @@ package org.briarproject.briar.android.view;
import android.app.Activity;
import android.arch.lifecycle.LifecycleOwner;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.Observer;
import android.content.ClipData;
import android.content.Context;
import android.content.Intent;
@@ -17,9 +18,10 @@ import android.widget.Toast;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.conversation.AttachmentResult;
import org.briarproject.briar.android.attachment.AttachmentItemResult;
import org.briarproject.briar.android.attachment.AttachmentManager;
import org.briarproject.briar.android.attachment.AttachmentResult;
import org.briarproject.briar.android.view.ImagePreview.ImagePreviewListener;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import java.util.ArrayList;
import java.util.List;
@@ -38,7 +40,6 @@ import static android.support.v4.content.ContextCompat.getColor;
import static android.support.v4.view.AbsSavedState.EMPTY_STATE;
import static android.view.View.GONE;
import static android.widget.Toast.LENGTH_LONG;
import static java.util.Collections.emptyList;
import static java.util.Objects.requireNonNull;
import static org.briarproject.briar.android.util.UiUtils.resolveColorAttribute;
import static org.briarproject.briar.api.messaging.MessagingConstants.IMAGE_MIME_TYPES;
@@ -55,9 +56,8 @@ public class TextAttachmentController extends TextSendController
private final CompositeSendButton sendButton;
private final AttachmentManager attachmentManager;
private CharSequence textHint;
private List<Uri> imageUris = emptyList();
private int urisLoaded = 0;
private final List<Uri> imageUris = new ArrayList<>();
private final CharSequence textHint;
private boolean loadingUris = false;
public TextAttachmentController(TextInputView v, ImagePreview imagePreview,
@@ -96,7 +96,7 @@ public class TextAttachmentController extends TextSendController
if (canSend()) {
if (loadingUris) throw new AssertionError();
listener.onSendClick(textInput.getText(),
attachmentManager.getAttachmentHeaders());
attachmentManager.getAttachmentHeadersForSending());
reset();
}
}
@@ -140,14 +140,12 @@ public class TextAttachmentController extends TextSendController
public void onImageReceived(@Nullable Intent resultData) {
if (resultData == null) return;
if (loadingUris) throw new AssertionError();
if (loadingUris || !imageUris.isEmpty()) throw new AssertionError();
if (resultData.getData() != null) {
imageUris = new ArrayList<>(1);
imageUris.add(resultData.getData());
onNewUris();
} else if (SDK_INT >= 18 && resultData.getClipData() != null) {
ClipData clipData = resultData.getClipData();
imageUris = new ArrayList<>(clipData.getItemCount());
for (int i = 0; i < clipData.getItemCount(); i++) {
imageUris.add(clipData.getItemAt(i).getUri());
}
@@ -157,40 +155,62 @@ public class TextAttachmentController extends TextSendController
private void onNewUris() {
if (imageUris.isEmpty()) return;
if (loadingUris || urisLoaded != 0) throw new AssertionError();
if (loadingUris) throw new AssertionError();
loadingUris = true;
updateViewState();
textInput.setHint(R.string.image_caption_hint);
List<ImagePreviewItem> items = ImagePreviewItem.fromUris(imageUris);
imagePreview.showPreview(items);
// store attachments and show preview when successful
boolean needsSize = items.size() == 1;
for (ImagePreviewItem item : items) {
attachmentManager.storeAttachment(item.getUri(), needsSize)
.observe(imageListener, this::onAttachmentResultReceived);
AttachmentResult result = attachmentManager.storeAttachments(imageUris);
for (LiveData<AttachmentItemResult> liveData : result
.getItemResults()) {
onLiveDataReturned(liveData);
}
result.getFinished().observe(imageListener, new Observer<Boolean>() {
@Override
public void onChanged(@Nullable Boolean finished) {
if (finished != null && finished) onAllAttachmentsCreated();
result.getFinished().removeObserver(this);
}
});
}
private void onAttachmentResultReceived(AttachmentResult result) {
if (!loadingUris) return; // if this is false, the user cancelled
private void onLiveDataReturned(LiveData<AttachmentItemResult> liveData) {
liveData.observe(imageListener, new Observer<AttachmentItemResult>() {
@Override
public void onChanged(@Nullable AttachmentItemResult result) {
if (result != null) {
onAttachmentResultReceived(result);
}
liveData.removeObserver(this);
}
});
}
private void onAttachmentResultReceived(AttachmentItemResult result) {
if (!loadingUris) throw new AssertionError();
if (result.isError() || result.getUri() == null) {
onError(result.getErrorMsg());
} else {
imagePreview.loadPreviewImage(result);
urisLoaded++;
checkAllUrisLoaded();
}
}
private void onAllAttachmentsCreated() {
if (!loadingUris) throw new AssertionError();
loadingUris = false;
updateViewState();
}
private void reset() {
// restore hint
textInput.setHint(textHint);
// hide image layout
imagePreview.setVisibility(GONE);
// reset image URIs
imageUris = emptyList();
// no URIs has been loaded
urisLoaded = 0;
imageUris.clear();
// definitely not loading anymore
loadingUris = false;
// show the image button again, so images can get attached
updateViewState();
@@ -208,7 +228,8 @@ public class TextAttachmentController extends TextSendController
@Nullable
public Parcelable onRestoreInstanceState(Parcelable inState) {
SavedState state = (SavedState) inState;
imageUris = requireNonNull(state.imageUris);
if (!imageUris.isEmpty()) throw new AssertionError();
if (state.imageUris != null) imageUris.addAll(state.imageUris);
onNewUris();
return state.getSuperState();
}
@@ -226,19 +247,10 @@ public class TextAttachmentController extends TextSendController
@Override
public void onCancel() {
textInput.clearText();
attachmentManager.removeAttachments();
attachmentManager.cancel();
reset();
}
private void checkAllUrisLoaded() {
if (!loadingUris) throw new AssertionError();
if (urisLoaded == imageUris.size()) {
loadingUris = false;
// all images were turned into attachments
updateViewState();
}
}
public void showImageOnboarding(Activity activity,
Runnable onOnboardingSeen) {
PromptStateChangeListener listener = (prompt, state) -> {
@@ -297,19 +309,4 @@ public class TextAttachmentController extends TextSendController
void onAttachImage(Intent intent);
}
public interface AttachmentManager {
/**
* Stores a new attachment in the database.
*
* @param uri The Uri of the attachment to store.
* @param needsSize true if this is the only image in the message
* and therefore needs to know its size.
*/
LiveData<AttachmentResult> storeAttachment(Uri uri, boolean needsSize);
List<AttachmentHeader> getAttachmentHeaders();
void removeAttachments();
}
}

View File

@@ -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

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.android.conversation;
package org.briarproject.briar.android.attachment;
import com.bumptech.glide.util.MarkEnforcingInputStream;

View File

@@ -174,6 +174,10 @@ class MessagingManagerImpl extends ConversationClientImpl
}
if (is.available() > 0) throw new FileTooBigException();
is.close();
try {
Thread.sleep(1000);
} catch (InterruptedException ignored) {
}
Message m = messageFactory.createMessage(groupId, timestamp, body);
clientHelper.addLocalMessage(m, new BdfDictionary(), false);
return new AttachmentHeader(m.getId(), contentType);