Update attachments for public group chats.

This commit is contained in:
Mikunj 2019-10-21 10:52:53 +11:00
parent b12e6b838c
commit e438d09a62
7 changed files with 73 additions and 32 deletions

View File

@ -127,8 +127,7 @@
android:paddingStart="6dp" android:paddingStart="6dp"
android:paddingEnd="6dp" android:paddingEnd="6dp"
android:background="?selectableItemBackgroundBorderless" android:background="?selectableItemBackgroundBorderless"
android:contentDescription="@string/conversation_activity__quick_attachment_drawer_toggle_camera_description" android:contentDescription="@string/conversation_activity__quick_attachment_drawer_toggle_camera_description" />
android:visibility="gone"/>
<org.thoughtcrime.securesms.components.MicrophoneRecorderView <org.thoughtcrime.securesms.components.MicrophoneRecorderView
android:id="@+id/recorder_view" android:id="@+id/recorder_view"
@ -136,7 +135,8 @@
android:layout_width="36dp" android:layout_width="36dp"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:clipChildren="false" android:clipChildren="false"
android:clipToPadding="false"> android:clipToPadding="false"
android:visibility="gone">
<include layout="@layout/microphone_recorder_view" /> <include layout="@layout/microphone_recorder_view" />
@ -148,8 +148,7 @@
android:id="@+id/inline_attachment_container" android:id="@+id/inline_attachment_container"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="right|end" android:layout_gravity="right|end">
android:visibility="gone">
<ImageButton <ImageButton
android:id="@+id/inline_attachment_button" android:id="@+id/inline_attachment_button"

View File

@ -183,16 +183,29 @@ public class AttachmentDownloadJob extends BaseJob implements InjectableType {
SignalServiceAttachmentPointer createAttachmentPointer(Attachment attachment) SignalServiceAttachmentPointer createAttachmentPointer(Attachment attachment)
throws InvalidPartException throws InvalidPartException
{ {
boolean isPublicAttachment = TextUtils.isEmpty(attachment.getKey()) && attachment.getDigest() == null;
if (TextUtils.isEmpty(attachment.getLocation())) { if (TextUtils.isEmpty(attachment.getLocation())) {
throw new InvalidPartException("empty content id"); throw new InvalidPartException("empty content id");
} }
if (TextUtils.isEmpty(attachment.getKey())) { if (TextUtils.isEmpty(attachment.getKey()) && !isPublicAttachment) {
throw new InvalidPartException("empty encrypted key"); throw new InvalidPartException("empty encrypted key");
} }
try { try {
long id = Long.parseLong(attachment.getLocation()); long id = Long.parseLong(attachment.getLocation());
if (isPublicAttachment) {
return new SignalServiceAttachmentPointer(id, null, new byte[0],
Optional.of(Util.toIntExact(attachment.getSize())),
Optional.absent(),
0, 0,
Optional.fromNullable(attachment.getDigest()),
Optional.fromNullable(attachment.getFileName()),
attachment.isVoiceNote(),
Optional.absent(), attachment.getUrl());
}
byte[] key = Base64.decode(attachment.getKey()); byte[] key = Base64.decode(attachment.getKey());
String relay = null; String relay = null;

View File

@ -7,6 +7,7 @@ import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.attachments.AttachmentId; import org.thoughtcrime.securesms.attachments.AttachmentId;
import org.thoughtcrime.securesms.attachments.DatabaseAttachment; import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
import org.thoughtcrime.securesms.attachments.PointerAttachment; import org.thoughtcrime.securesms.attachments.PointerAttachment;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.AttachmentDatabase; import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.dependencies.InjectableType;
@ -25,6 +26,7 @@ import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -40,28 +42,32 @@ public class AttachmentUploadJob extends BaseJob implements InjectableType {
private static final String KEY_ROW_ID = "row_id"; private static final String KEY_ROW_ID = "row_id";
private static final String KEY_UNIQUE_ID = "unique_id"; private static final String KEY_UNIQUE_ID = "unique_id";
private static final String KEY_DESTINATION = "destination";
private AttachmentId attachmentId; private AttachmentId attachmentId;
private Address destination;
@Inject SignalServiceMessageSender messageSender; @Inject SignalServiceMessageSender messageSender;
public AttachmentUploadJob(AttachmentId attachmentId) { public AttachmentUploadJob(AttachmentId attachmentId, Address destination) {
this(new Job.Parameters.Builder() this(new Job.Parameters.Builder()
.addConstraint(NetworkConstraint.KEY) .addConstraint(NetworkConstraint.KEY)
.setLifespan(TimeUnit.DAYS.toMillis(1)) .setLifespan(TimeUnit.DAYS.toMillis(1))
.setMaxAttempts(Parameters.UNLIMITED) .setMaxAttempts(Parameters.UNLIMITED)
.build(), .build(),
attachmentId); attachmentId, destination);
} }
private AttachmentUploadJob(@NonNull Job.Parameters parameters, @NonNull AttachmentId attachmentId) { private AttachmentUploadJob(@NonNull Job.Parameters parameters, @NonNull AttachmentId attachmentId, Address destination) {
super(parameters); super(parameters);
this.attachmentId = attachmentId; this.attachmentId = attachmentId;
this.destination = destination;
} }
@Override @Override
public @NonNull Data serialize() { public @NonNull Data serialize() {
return new Data.Builder().putLong(KEY_ROW_ID, attachmentId.getRowId()) return new Data.Builder().putLong(KEY_ROW_ID, attachmentId.getRowId())
.putLong(KEY_UNIQUE_ID, attachmentId.getUniqueId()) .putLong(KEY_UNIQUE_ID, attachmentId.getUniqueId())
.putString(KEY_DESTINATION, destination.serialize())
.build(); .build();
} }
@ -82,7 +88,7 @@ public class AttachmentUploadJob extends BaseJob implements InjectableType {
MediaConstraints mediaConstraints = MediaConstraints.getPushMediaConstraints(); MediaConstraints mediaConstraints = MediaConstraints.getPushMediaConstraints();
Attachment scaledAttachment = scaleAndStripExif(database, mediaConstraints, databaseAttachment); Attachment scaledAttachment = scaleAndStripExif(database, mediaConstraints, databaseAttachment);
SignalServiceAttachment localAttachment = getAttachmentFor(scaledAttachment); SignalServiceAttachment localAttachment = getAttachmentFor(scaledAttachment);
SignalServiceAttachmentPointer remoteAttachment = messageSender.uploadAttachment(localAttachment.asStream(), databaseAttachment.isSticker()); SignalServiceAttachmentPointer remoteAttachment = messageSender.uploadAttachment(localAttachment.asStream(), databaseAttachment.isSticker(), new SignalServiceAddress(destination.serialize()));
Attachment attachment = PointerAttachment.forPointer(Optional.of(remoteAttachment), null, databaseAttachment.getFastPreflightId()).get(); Attachment attachment = PointerAttachment.forPointer(Optional.of(remoteAttachment), null, databaseAttachment.getFastPreflightId()).get();
database.updateAttachmentAfterUpload(databaseAttachment.getAttachmentId(), attachment); database.updateAttachmentAfterUpload(databaseAttachment.getAttachmentId(), attachment);
@ -144,7 +150,7 @@ public class AttachmentUploadJob extends BaseJob implements InjectableType {
public static final class Factory implements Job.Factory<AttachmentUploadJob> { public static final class Factory implements Job.Factory<AttachmentUploadJob> {
@Override @Override
public @NonNull AttachmentUploadJob create(@NonNull Parameters parameters, @NonNull org.thoughtcrime.securesms.jobmanager.Data data) { public @NonNull AttachmentUploadJob create(@NonNull Parameters parameters, @NonNull org.thoughtcrime.securesms.jobmanager.Data data) {
return new AttachmentUploadJob(parameters, new AttachmentId(data.getLong(KEY_ROW_ID), data.getLong(KEY_UNIQUE_ID))); return new AttachmentUploadJob(parameters, new AttachmentId(data.getLong(KEY_ROW_ID), data.getLong(KEY_UNIQUE_ID)), Address.fromSerialized(data.getString(KEY_DESTINATION)));
} }
} }
} }

View File

@ -96,12 +96,11 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
OutgoingMediaMessage message = database.getOutgoingMessage(messageId); OutgoingMediaMessage message = database.getOutgoingMessage(messageId);
List<Attachment> attachments = new LinkedList<>(); List<Attachment> attachments = new LinkedList<>();
// Loki - For now all attachments are re-fetched by the receiver attachments.addAll(message.getAttachments());
// attachments.addAll(message.getAttachments()); attachments.addAll(Stream.of(message.getLinkPreviews()).filter(p -> p.getThumbnail().isPresent()).map(p -> p.getThumbnail().get()).toList());
// attachments.addAll(Stream.of(message.getLinkPreviews()).filter(p -> p.getThumbnail().isPresent()).map(p -> p.getThumbnail().get()).toList());
attachments.addAll(Stream.of(message.getSharedContacts()).filter(c -> c.getAvatar() != null).map(c -> c.getAvatar().getAttachment()).withoutNulls().toList()); attachments.addAll(Stream.of(message.getSharedContacts()).filter(c -> c.getAvatar() != null).map(c -> c.getAvatar().getAttachment()).withoutNulls().toList());
List<AttachmentUploadJob> attachmentJobs = Stream.of(attachments).map(a -> new AttachmentUploadJob(((DatabaseAttachment) a).getAttachmentId())).toList(); List<AttachmentUploadJob> attachmentJobs = Stream.of(attachments).map(a -> new AttachmentUploadJob(((DatabaseAttachment) a).getAttachmentId(), destination)).toList();
if (attachmentJobs.isEmpty()) { if (attachmentJobs.isEmpty()) {
jobManager.add(new PushGroupSendJob(messageId, destination, filterAddress)); jobManager.add(new PushGroupSendJob(messageId, destination, filterAddress));

View File

@ -109,7 +109,7 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
attachments.addAll(Stream.of(message.getLinkPreviews()).filter(p -> p.getThumbnail().isPresent()).map(p -> p.getThumbnail().get()).toList()); attachments.addAll(Stream.of(message.getLinkPreviews()).filter(p -> p.getThumbnail().isPresent()).map(p -> p.getThumbnail().get()).toList());
attachments.addAll(Stream.of(message.getSharedContacts()).filter(c -> c.getAvatar() != null).map(c -> c.getAvatar().getAttachment()).withoutNulls().toList()); attachments.addAll(Stream.of(message.getSharedContacts()).filter(c -> c.getAvatar() != null).map(c -> c.getAvatar().getAttachment()).withoutNulls().toList());
List<AttachmentUploadJob> attachmentJobs = Stream.of(attachments).map(a -> new AttachmentUploadJob(((DatabaseAttachment) a).getAttachmentId())).toList(); List<AttachmentUploadJob> attachmentJobs = Stream.of(attachments).map(a -> new AttachmentUploadJob(((DatabaseAttachment) a).getAttachmentId(), destination)).toList();
if (attachmentJobs.isEmpty()) { if (attachmentJobs.isEmpty()) {
jobManager.add(new PushMediaSendJob(templateMessageId, messageId, destination, isFriendRequest, customFriendRequestMessage)); jobManager.add(new PushMediaSendJob(templateMessageId, messageId, destination, isFriendRequest, customFriendRequestMessage));

View File

@ -17,10 +17,9 @@ import org.thoughtcrime.securesms.util.GroupUtil
import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.thoughtcrime.securesms.util.Util import org.thoughtcrime.securesms.util.Util
import org.whispersystems.libsignal.util.guava.Optional import org.whispersystems.libsignal.util.guava.Optional
import org.whispersystems.signalservice.api.messages.SignalServiceContent import org.whispersystems.signalservice.api.messages.*
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage
import org.whispersystems.signalservice.api.messages.SignalServiceGroup
import org.whispersystems.signalservice.api.push.SignalServiceAddress import org.whispersystems.signalservice.api.push.SignalServiceAddress
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
import org.whispersystems.signalservice.loki.api.LokiPublicChat import org.whispersystems.signalservice.loki.api.LokiPublicChat
import org.whispersystems.signalservice.loki.api.LokiPublicChatAPI import org.whispersystems.signalservice.loki.api.LokiPublicChatAPI
import org.whispersystems.signalservice.loki.api.LokiPublicChatMessage import org.whispersystems.signalservice.loki.api.LokiPublicChatMessage
@ -96,21 +95,35 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki
private fun pollForNewMessages() { private fun pollForNewMessages() {
fun processIncomingMessage(message: LokiPublicChatMessage) { fun processIncomingMessage(message: LokiPublicChatMessage) {
val id = group.id.toByteArray() val id = group.id.toByteArray()
val x1 = SignalServiceGroup(SignalServiceGroup.Type.UPDATE, id, null, null, null) val serviceGroup = SignalServiceGroup(SignalServiceGroup.Type.UPDATE, id, null, null, null)
val quote: SignalServiceDataMessage.Quote? val quote = if (message.quote != null) {
if (message.quote != null) { SignalServiceDataMessage.Quote(message.quote!!.quotedMessageTimestamp, SignalServiceAddress(message.quote!!.quoteeHexEncodedPublicKey), message.quote!!.quotedMessageBody, listOf())
quote = SignalServiceDataMessage.Quote(message.quote!!.quotedMessageTimestamp, SignalServiceAddress(message.quote!!.quoteeHexEncodedPublicKey), message.quote!!.quotedMessageBody, listOf())
} else { } else {
quote = null null
} }
val x2 = SignalServiceDataMessage(message.timestamp, x1, listOf(), message.body, false, 0, false, null, false, quote, null, null, null) val attachments = message.attachments.map { attachment ->
val x3 = SignalServiceContent(x2, message.hexEncodedPublicKey, SignalServiceAddress.DEFAULT_DEVICE_ID, message.timestamp, false) SignalServiceAttachmentPointer(
attachment.serverID,
attachment.contentType,
ByteArray(0),
Optional.of(attachment.size),
Optional.absent(),
attachment.width, attachment.height,
Optional.absent(),
Optional.of(attachment.fileName),
false,
Optional.fromNullable(attachment.caption),
attachment.url)
}
val body = if (message.body == message.timestamp.toString()) "" else message.body // Workaround for the fact that the back-end doesn't accept messages without a body
val serviceDataMessage = SignalServiceDataMessage(message.timestamp, serviceGroup, attachments, body, false, 0, false, null, false, quote, null, null, null)
val serviceContent = SignalServiceContent(serviceDataMessage, message.hexEncodedPublicKey, SignalServiceAddress.DEFAULT_DEVICE_ID, message.timestamp, false)
val senderDisplayName = "${message.displayName} (...${message.hexEncodedPublicKey.takeLast(8)})" val senderDisplayName = "${message.displayName} (...${message.hexEncodedPublicKey.takeLast(8)})"
DatabaseFactory.getLokiUserDatabase(context).setServerDisplayName(group.id, message.hexEncodedPublicKey, senderDisplayName) DatabaseFactory.getLokiUserDatabase(context).setServerDisplayName(group.id, message.hexEncodedPublicKey, senderDisplayName)
if (quote != null) { if (quote != null || attachments.count() > 0) {
PushDecryptJob(context).handleMediaMessage(x3, x2, Optional.absent(), Optional.of(message.serverID)) PushDecryptJob(context).handleMediaMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID))
} else { } else {
PushDecryptJob(context).handleTextMessage(x3, x2, Optional.absent(), Optional.of(message.serverID)) PushDecryptJob(context).handleTextMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID))
} }
} }
fun processOutgoingMessage(message: LokiPublicChatMessage) { fun processOutgoingMessage(message: LokiPublicChatMessage) {
@ -118,6 +131,10 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki
val lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context) val lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context)
val isDuplicate = lokiMessageDatabase.getMessageID(messageServerID) != null val isDuplicate = lokiMessageDatabase.getMessageID(messageServerID) != null
if (isDuplicate) { return } if (isDuplicate) { return }
// Ignore empty messages with no attachments or quotes
if (message.body.isEmpty() && message.attachments.isEmpty() && message.quote == null) { return }
val id = group.id.toByteArray() val id = group.id.toByteArray()
val mmsDatabase = DatabaseFactory.getMmsDatabase(context) val mmsDatabase = DatabaseFactory.getMmsDatabase(context)
val recipient = Recipient.from(context, Address.fromSerialized(GroupUtil.getEncodedId(id, false)), false) val recipient = Recipient.from(context, Address.fromSerialized(GroupUtil.getEncodedId(id, false)), false)
@ -127,7 +144,9 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki
} else { } else {
quote = null quote = null
} }
val signalMessage = OutgoingMediaMessage(recipient, message.body, listOf(), message.timestamp, 0, 0, // TODO: Handle attachments correctly for our previous messages
val body = if (message.body == message.timestamp.toString()) "" else message.body // Workaround for the fact that the back-end doesn't accept messages without a body
val signalMessage = OutgoingMediaMessage(recipient, body, listOf(), message.timestamp, 0, 0,
ThreadDatabase.DistributionTypes.DEFAULT, quote, listOf(), listOf(), listOf(), listOf()) ThreadDatabase.DistributionTypes.DEFAULT, quote, listOf(), listOf(), listOf(), listOf())
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient) val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient)
fun finalize() { fun finalize() {

View File

@ -114,9 +114,14 @@ public class AttachmentUtil {
if (message == null) { return true; } if (message == null) { return true; }
// TODO: Fix this so we can detect whether attachment is from a public group or not
return false;
/*
// check to see if we're friends with the person // check to see if we're friends with the person
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(message.getRecipient()); long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(message.getRecipient());
boolean isFriend = threadId >= 0 && DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadId) == LokiThreadFriendRequestStatus.FRIENDS; boolean isFriend = threadId >= 0 && DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadId) == LokiThreadFriendRequestStatus.FRIENDS;
return (!isFriend && !message.isOutgoing() && !Util.isOwnNumber(context, message.getRecipient().getAddress())); return (!isFriend && !message.isOutgoing() && !Util.isOwnNumber(context, message.getRecipient().getAddress()));
*/
} }
} }