Upgrade messaging client to support attachments.

This commit is contained in:
akwizgran
2019-06-10 15:30:42 +01:00
parent f73d298752
commit 2bae639105
45 changed files with 1318 additions and 245 deletions

View File

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

View File

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

View File

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

View File

@@ -84,6 +84,7 @@ public class TextSendController implements TextInputListener {
return state;
}
@UiThread
public interface SendListener {
void onSendClick(@Nullable String text, List<AttachmentHeader> headers);
}