Commit 2bae6391 authored by akwizgran's avatar akwizgran

Upgrade messaging client to support attachments.

parent f73d2987
......@@ -25,7 +25,10 @@ public interface ClientHelper {
throws DbException, FormatException;
void addLocalMessage(Transaction txn, Message m, BdfDictionary metadata,
boolean shared) throws DbException, FormatException;
boolean shared, boolean temporary)
throws DbException, FormatException;
Message createMessage(GroupId g, long timestamp, byte[] body);
Message createMessage(GroupId g, long timestamp, BdfList body)
throws FormatException;
......@@ -108,7 +111,7 @@ public interface ClientHelper {
Author parseAndValidateAuthor(BdfList author) throws FormatException;
PublicKey parseAndValidateAgreementPublicKey(byte[] publicKeyBytes)
throws FormatException;
throws FormatException;
TransportProperties parseAndValidateTransportProperties(
BdfDictionary properties) throws FormatException;
......
......@@ -85,15 +85,21 @@ class ClientHelperImpl implements ClientHelper {
@Override
public void addLocalMessage(Message m, BdfDictionary metadata,
boolean shared) throws DbException, FormatException {
db.transaction(false, txn -> addLocalMessage(txn, m, metadata, shared));
db.transaction(false, txn -> addLocalMessage(txn, m, metadata, shared,
false));
}
@Override
public void addLocalMessage(Transaction txn, Message m,
BdfDictionary metadata, boolean shared)
BdfDictionary metadata, boolean shared, boolean temporary)
throws DbException, FormatException {
db.addLocalMessage(txn, m, metadataEncoder.encode(metadata), shared,
false);
temporary);
}
@Override
public Message createMessage(GroupId g, long timestamp, byte[] body) {
return messageFactory.createMessage(g, timestamp, body);
}
@Override
......
......@@ -284,7 +284,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
meta.put("transportId", t.getString());
meta.put("version", version);
meta.put("local", local);
clientHelper.addLocalMessage(txn, m, meta, shared);
clientHelper.addLocalMessage(txn, m, meta, shared, false);
} catch (FormatException e) {
throw new RuntimeException(e);
}
......
......@@ -314,6 +314,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
try {
shareMsg = hook.incomingMessage(txn, m, meta);
} catch (InvalidMessageException e) {
logException(LOG, INFO, e);
invalidateMessage(txn, m.getId());
return new DeliveryResult(false, false);
}
......
......@@ -438,7 +438,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
BdfDictionary meta = new BdfDictionary();
meta.put(MSG_KEY_UPDATE_VERSION, updateVersion);
meta.put(MSG_KEY_LOCAL, true);
clientHelper.addLocalMessage(txn, m, meta, true);
clientHelper.addLocalMessage(txn, m, meta, true, false);
} catch (FormatException e) {
throw new RuntimeException(e);
}
......
......@@ -637,7 +637,8 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
will(returnValue(timestamp));
oneOf(clientHelper).createMessage(g, timestamp, body);
will(returnValue(message));
oneOf(clientHelper).addLocalMessage(txn, message, meta, shared);
oneOf(clientHelper).addLocalMessage(txn, message, meta, shared,
false);
}});
}
}
......@@ -131,7 +131,7 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
localUpdateBody);
will(returnValue(localUpdate));
oneOf(clientHelper).addLocalMessage(txn, localUpdate,
localUpdateMeta, true);
localUpdateMeta, true, false);
}});
}
......@@ -284,7 +284,7 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
newLocalUpdateBody);
will(returnValue(newLocalUpdate));
oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate,
newLocalUpdateMeta, true);
newLocalUpdateMeta, true, false);
// No visibilities have changed
}});
......@@ -382,7 +382,7 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
newLocalUpdateBody);
will(returnValue(newLocalUpdate));
oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate,
newLocalUpdateMeta, true);
newLocalUpdateMeta, true, false);
// The client's visibility has changed
oneOf(hook).onClientVisibilityChanging(txn, contact, visibility);
}});
......@@ -567,7 +567,7 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
newLocalUpdateBody);
will(returnValue(newLocalUpdate));
oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate,
newLocalUpdateMeta, true);
newLocalUpdateMeta, true, false);
// The client's visibility has changed
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
contactGroup.getId());
......@@ -640,7 +640,7 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
newLocalUpdateBody);
will(returnValue(newLocalUpdate));
oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate,
newLocalUpdateMeta, true);
newLocalUpdateMeta, true, false);
// The client's visibility has changed
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
contactGroup.getId());
......
......@@ -6,6 +6,7 @@ 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;
......@@ -34,7 +35,6 @@ import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
import org.briarproject.bramble.api.crypto.CryptoExecutor;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.NoSuchContactException;
......@@ -46,7 +46,6 @@ import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionRegistry;
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
import org.briarproject.bramble.api.settings.SettingsManager;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.event.MessagesAckedEvent;
import org.briarproject.bramble.api.sync.event.MessagesSentEvent;
......@@ -65,10 +64,9 @@ import org.briarproject.briar.android.util.BriarSnackbarBuilder;
import org.briarproject.briar.android.view.BriarRecyclerView;
import org.briarproject.briar.android.view.ImagePreview;
import org.briarproject.briar.android.view.TextAttachmentController;
import org.briarproject.briar.android.view.TextAttachmentController.AttachImageListener;
import org.briarproject.briar.android.view.TextAttachmentController.AttachmentListener;
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.android.AndroidNotificationManager;
import org.briarproject.briar.api.blog.BlogSharingManager;
import org.briarproject.briar.api.client.ProtocolStateException;
......@@ -83,7 +81,6 @@ import org.briarproject.briar.api.introduction.IntroductionManager;
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.PrivateMessageFactory;
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager;
......@@ -94,7 +91,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.inject.Inject;
......@@ -129,6 +125,7 @@ import static org.briarproject.briar.android.conversation.ImageActivity.NAME;
import static org.briarproject.briar.android.util.UiUtils.getAvatarTransitionName;
import static org.briarproject.briar.android.util.UiUtils.getBulbTransitionName;
import static org.briarproject.briar.android.util.UiUtils.observeOnce;
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_ATTACHMENTS_PER_MESSAGE;
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_TEXT_LENGTH;
import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.STATE_DISMISSED;
import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.STATE_FINISHED;
......@@ -136,8 +133,8 @@ import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.S
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class ConversationActivity extends BriarActivity
implements EventListener, ConversationListener, SendListener,
TextCache, AttachmentCache, AttachImageListener {
implements EventListener, ConversationListener, TextCache,
AttachmentCache, AttachmentListener {
public static final String CONTACT_ID = "briar.CONTACT_ID";
......@@ -152,9 +149,6 @@ public class ConversationActivity extends BriarActivity
@Inject
ConnectionRegistry connectionRegistry;
@Inject
@CryptoExecutor
Executor cryptoExecutor;
@Inject
ViewModelProvider.Factory viewModelFactory;
@Inject
FeatureFlags featureFlags;
......@@ -169,10 +163,6 @@ public class ConversationActivity extends BriarActivity
@Inject
volatile EventBus eventBus;
@Inject
volatile SettingsManager settingsManager;
@Inject
volatile PrivateMessageFactory privateMessageFactory;
@Inject
volatile IntroductionManager introductionManager;
@Inject
volatile ForumSharingManager forumSharingManager;
......@@ -267,10 +257,10 @@ public class ConversationActivity extends BriarActivity
if (featureFlags.shouldEnableImageAttachments()) {
ImagePreview imagePreview = findViewById(R.id.imagePreview);
sendController = new TextAttachmentController(textInputView,
imagePreview, this, this, viewModel);
imagePreview, this, viewModel);
observeOnce(viewModel.hasImageSupport(), this, hasSupport -> {
if (hasSupport != null && hasSupport) {
// remove cast when removing FEATURE_FLAG_IMAGE_ATTACHMENTS
// TODO: remove cast when removing feature flag
((TextAttachmentController) sendController)
.setImagesSupported();
}
......@@ -305,7 +295,7 @@ public class ConversationActivity extends BriarActivity
Snackbar.LENGTH_SHORT)
.show();
} else if (request == REQUEST_ATTACH_IMAGE && result == RESULT_OK) {
// remove cast when removing FEATURE_FLAG_IMAGE_ATTACHMENTS
// TODO: remove cast when removing feature flag
((TextAttachmentController) sendController).onImageReceived(data);
}
}
......@@ -454,7 +444,7 @@ public class ConversationActivity extends BriarActivity
if (text == null) {
LOG.info("Eagerly loading text for latest message");
text = messagingManager.getMessageText(id);
textCache.put(id, text);
textCache.put(id, requireNonNull(text));
}
}
// If the message has a single image, load its size - for multiple
......@@ -478,8 +468,10 @@ public class ConversationActivity extends BriarActivity
adapter.incrementRevision();
textInputView.setReady(true);
// start observing onboarding after enabling
viewModel.showImageOnboarding().observeEvent(this,
this::showImageOnboarding);
if (featureFlags.shouldEnableImageAttachments()) {
viewModel.showImageOnboarding().observeEvent(this,
this::showImageOnboarding);
}
List<ConversationItem> items = createItems(headers);
adapter.addAll(items);
list.showData();
......@@ -515,7 +507,7 @@ public class ConversationActivity extends BriarActivity
long start = now();
String text = messagingManager.getMessageText(m);
logDuration(LOG, "Loading text", start);
displayMessageText(m, text);
displayMessageText(m, requireNonNull(text));
} catch (DbException e) {
logException(LOG, WARNING, e);
}
......@@ -660,6 +652,18 @@ public class ConversationActivity extends BriarActivity
startActivityForResult(intent, REQUEST_ATTACH_IMAGE);
}
@Override
public List<Uri> filterAttachmentUris(List<Uri> uris) {
if (uris.size() > MAX_ATTACHMENTS_PER_MESSAGE) {
String format = getResources().getString(
R.string.messaging_too_many_attachments_toast);
String warning = String.format(format, MAX_ATTACHMENTS_PER_MESSAGE);
Toast.makeText(this, warning, LENGTH_SHORT).show();
uris = uris.subList(0, MAX_ATTACHMENTS_PER_MESSAGE);
}
return new ArrayList<>(uris);
}
@Override
public void onSendClick(@Nullable String text,
List<AttachmentHeader> attachmentHeaders) {
......@@ -729,7 +733,7 @@ public class ConversationActivity extends BriarActivity
}
private void showImageOnboarding() {
// remove cast when removing FEATURE_FLAG_IMAGE_ATTACHMENTS
// TODO: remove cast when removing feature flag
((TextAttachmentController) sendController)
.showImageOnboarding(this, () ->
viewModel.onImageOnboardingSeen());
......
......@@ -61,6 +61,7 @@ public class ConversationViewModel extends AndroidViewModel
private static Logger LOG =
getLogger(ConversationViewModel.class.getName());
private static final String SHOW_ONBOARDING_IMAGE =
"showOnboardingImage";
private static final String SHOW_ONBOARDING_INTRODUCTION =
......@@ -181,12 +182,17 @@ public class ConversationViewModel extends AndroidViewModel
});
}
@UiThread
void sendMessage(@Nullable String text,
List<AttachmentHeader> attachmentHeaders, long timestamp) {
List<AttachmentHeader> headers, long timestamp) {
// messagingGroupId is loaded with the contact
observeForeverOnce(messagingGroupId, groupId -> {
if (groupId == null) throw new IllegalStateException();
createMessage(groupId, text, attachmentHeaders, timestamp);
requireNonNull(groupId);
observeForeverOnce(imageSupport, hasImageSupport -> {
requireNonNull(hasImageSupport);
createMessage(groupId, text, headers, timestamp,
hasImageSupport);
});
});
}
......@@ -270,21 +276,24 @@ public class ConversationViewModel extends AndroidViewModel
}
private void createMessage(GroupId groupId, @Nullable String text,
List<AttachmentHeader> attachments, long timestamp) {
List<AttachmentHeader> headers, long timestamp,
boolean hasImageSupport) {
try {
// TODO remove when text can be null in the backend
String msgText = text == null ? "null" : text;
PrivateMessage pm = privateMessageFactory
.createPrivateMessage(groupId, timestamp, msgText,
attachments);
storeMessage(pm, msgText, attachments);
PrivateMessage pm;
if (hasImageSupport) {
pm = privateMessageFactory.createPrivateMessage(groupId,
timestamp, text, headers);
} else {
pm = privateMessageFactory.createLegacyPrivateMessage(
groupId, timestamp, requireNonNull(text));
}
storeMessage(pm);
} catch (FormatException e) {
throw new RuntimeException(e);
throw new AssertionError(e);
}
}
private void storeMessage(PrivateMessage m, @Nullable String text,
List<AttachmentHeader> attachments) {
private void storeMessage(PrivateMessage m) {
attachmentCreator.onAttachmentsSent(m.getMessage().getId());
dbExecutor.execute(() -> {
try {
......@@ -295,7 +304,7 @@ public class ConversationViewModel extends AndroidViewModel
PrivateMessageHeader h = new PrivateMessageHeader(
message.getId(), message.getGroupId(),
message.getTimestamp(), true, true, false, false,
text != null, attachments);
m.hasText(), m.getAttachmentHeaders());
// TODO add text to cache when available here
addedHeader.postEvent(h);
} catch (DbException e) {
......
......@@ -41,7 +41,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.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;
......@@ -53,7 +52,7 @@ public class TextAttachmentController extends TextSendController
implements ImagePreviewListener {
private final ImagePreview imagePreview;
private final AttachImageListener imageListener;
private final AttachmentListener attachmentListener;
private final CompositeSendButton sendButton;
private final AttachmentManager attachmentManager;
......@@ -62,10 +61,10 @@ public class TextAttachmentController extends TextSendController
private boolean loadingUris = false;
public TextAttachmentController(TextInputView v, ImagePreview imagePreview,
SendListener listener, AttachImageListener imageListener,
AttachmentListener attachmentListener,
AttachmentManager attachmentManager) {
super(v, listener, false);
this.imageListener = imageListener;
super(v, attachmentListener, false);
this.attachmentListener = attachmentListener;
this.imagePreview = imagePreview;
this.attachmentManager = attachmentManager;
this.imagePreview.setImagePreviewListener(this);
......@@ -124,8 +123,8 @@ public class TextAttachmentController extends TextSendController
return;
}
Intent intent = getAttachFileIntent();
if (imageListener.getLifecycle().getCurrentState() != DESTROYED) {
requireNonNull(imageListener).onAttachImage(intent);
if (attachmentListener.getLifecycle().getCurrentState() != DESTROYED) {
attachmentListener.onAttachImage(intent);
}
}
......@@ -144,11 +143,13 @@ public class TextAttachmentController extends TextSendController
* returned by the Activity started with {@link #getAttachFileIntent()}.
* <p>
* This method must be called at most once per call to
* {@link AttachImageListener#onAttachImage(Intent)}.
* {@link AttachmentListener#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)}.
* at most once per call to
* {@link Activity#startActivityForResult(Intent, int)}.
*/
@SuppressWarnings("JavadocReference")
public void onImageReceived(@Nullable Intent resultData) {
if (resultData == null) return;
if (loadingUris || !imageUris.isEmpty()) throw new AssertionError();
......@@ -168,6 +169,9 @@ public class TextAttachmentController extends TextSendController
if (imageUris.isEmpty()) return;
if (loadingUris) throw new AssertionError();
loadingUris = true;
List<Uri> filtered = attachmentListener.filterAttachmentUris(imageUris);
imageUris.clear();
imageUris.addAll(filtered);
updateViewState();
textInput.setHint(R.string.image_caption_hint);
List<ImagePreviewItem> items = ImagePreviewItem.fromUris(imageUris);
......@@ -175,7 +179,7 @@ public class TextAttachmentController extends TextSendController
// store attachments and show preview when successful
LiveData<AttachmentResult> result =
attachmentManager.storeAttachments(imageUris, restart);
result.observe(imageListener, new Observer<AttachmentResult>() {
result.observe(attachmentListener, new Observer<AttachmentResult>() {
@Override
public void onChanged(@Nullable AttachmentResult attachmentResult) {
if (attachmentResult == null) {
......@@ -316,8 +320,12 @@ public class TextAttachmentController extends TextSendController
};
}
public interface AttachImageListener extends LifecycleOwner {
@UiThread
public interface AttachmentListener extends SendListener, LifecycleOwner {
void onAttachImage(Intent intent);
List<Uri> filterAttachmentUris(List<Uri> uris);
}
}
......@@ -84,6 +84,7 @@ public class TextSendController implements TextInputListener {
return state;
}
@UiThread
public interface SendListener {
void onSendClick(@Nullable String text, List<AttachmentHeader> headers);
}
......
......@@ -150,6 +150,7 @@
<string name="dialog_message_no_image_support">Your contact\'s Briar does not yet support image attachments. Once they upgrade you\'ll see a different icon.</string>
<string name="dialog_title_image_support">You can now send images to this contact</string>
<string name="dialog_message_image_support">Tap this icon to attach images.</string>
<string name="messaging_too_many_attachments_toast">Only the first %d images will be sent</string>
<!-- Adding Contacts -->
......
......@@ -4,26 +4,30 @@ import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.messaging.PrivateMessage;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
public abstract class ThreadedMessage extends PrivateMessage {
public abstract class ThreadedMessage {
private final Message message;
@Nullable
private final MessageId parent;
private final Author author;
public ThreadedMessage(Message message, @Nullable MessageId parent,
Author author) {
super(message);
this.message = message;
this.parent = parent;
this.author = author;
}
public Message getMessage() {
return message;
}
@Nullable
public MessageId getParent() {
return parent;
......
......@@ -18,7 +18,8 @@ public abstract class ConversationMessageReceivedEvent<H extends ConversationMes
private final H messageHeader;
private final ContactId contactId;
public ConversationMessageReceivedEvent(H messageHeader, ContactId contactId) {
public ConversationMessageReceivedEvent(H messageHeader,
ContactId contactId) {
this.messageHeader = messageHeader;
this.contactId = contactId;
}
......
......@@ -9,6 +9,16 @@ public interface MessagingConstants {
*/
int MAX_PRIVATE_MESSAGE_TEXT_LENGTH = MAX_MESSAGE_BODY_LENGTH - 1024;
/**
* The maximum number of attachments per private message.
*/
int MAX_ATTACHMENTS_PER_MESSAGE = 10;
/**
* The maximum length of an attachment's content type in UTF-8 bytes.
*/
int MAX_CONTENT_TYPE_BYTES = 50;
/**
* The supported mime types for image attachments.
*/
......@@ -22,6 +32,6 @@ public interface MessagingConstants {
* The maximum allowed size of image attachments.
* TODO: Different limit for GIFs?
*/
int MAX_IMAGE_SIZE = MAX_MESSAGE_BODY_LENGTH; // 6 * 1024 * 1024;
int MAX_IMAGE_SIZE = MAX_MESSAGE_BODY_LENGTH - 100; // 6 * 1024 * 1024;
}
......@@ -12,6 +12,8 @@ import org.briarproject.briar.api.conversation.ConversationManager.ConversationC
import java.io.IOException;
import java.io.InputStream;
import javax.annotation.Nullable;
@NotNullByDefault
public interface MessagingManager extends ConversationClient {
......@@ -28,7 +30,7 @@ public interface MessagingManager extends ConversationClient {
/**
* The current minor version of the messaging client.
*/
int MINOR_VERSION = 0;
int MINOR_VERSION = 1;
/**
* Stores a local private message.
......@@ -59,8 +61,10 @@ public interface MessagingManager extends ConversationClient {
GroupId getConversationId(ContactId c) throws DbException;
/**
* Returns the text of the private message with the given ID.
* Returns the text of the private message with the given ID, or null if
* the private message has no text.
*/
@Nullable
String getMessageText(MessageId m) throws DbException;
/**
......
......@@ -3,20 +3,56 @@ package org.briarproject.briar.api.messaging;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Message;
import java.util.List;
import javax.annotation.concurrent.Immutable;
import static java.util.Collections.emptyList;
@Immutable
@NotNullByDefault
public class PrivateMessage {
private final Message message;
private final boolean legacyFormat, hasText;
private final List<AttachmentHeader> attachmentHeaders;
/**
* Constructor for private messages in the legacy format, which does not
* support attachments.
*/
public PrivateMessage(Message message) {
this.message = message;
legacyFormat = true;
hasText = true;
attachmentHeaders = emptyList();
}
/**
* Constructor for private messages in the current format, which supports
* attachments.
*/
public PrivateMessage(Message message, boolean hasText,
List<AttachmentHeader> headers) {
this.message = message;
this.hasText = hasText;
this.attachmentHeaders = headers;
legacyFormat = false;
}
public Message getMessage() {
return message;
}
public boolean isLegacyFormat() {
return legacyFormat;
}
public boolean hasText() {
return hasText;
}
public List<AttachmentHeader> getAttachmentHeaders() {
return attachmentHeaders;
}
}
......@@ -6,11 +6,16 @@ import org.briarproject.bramble.api.sync.GroupId;
import java.util.List;
import javax.annotation.Nullable;
@NotNullByDefault
public interface PrivateMessageFactory {
PrivateMessage createLegacyPrivateMessage(GroupId groupId, long timestamp,
String text) throws FormatException;
PrivateMessage createPrivateMessage(GroupId groupId, long timestamp,
String text, List<AttachmentHeader> attachments)
@Nullable String text, List<AttachmentHeader> headers)
throws FormatException;
}
......@@ -19,10 +19,10 @@ public class PrivateMessageHeader extends ConversationMessageHeader {
public PrivateMessageHeader(MessageId id, GroupId groupId, long timestamp,
boolean local, boolean read, boolean sent, boolean seen,
boolean hasText, List<AttachmentHeader> attachmentHeaders) {
boolean hasText, List<AttachmentHeader> headers) {
super(id, groupId, timestamp, local, read, sent, seen);