diff --git a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java index 0556817c11..0cbf5ae98b 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.jobs; import android.content.Context; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.annotation.WorkerThread; import com.annimon.stream.Stream; @@ -56,23 +57,50 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType { private static final String TAG = PushMediaSendJob.class.getSimpleName(); + private static final String KEY_TEMPLATE_MESSAGE_ID = "template_message_id"; private static final String KEY_MESSAGE_ID = "message_id"; + private static final String KEY_DESTINATION = "destination"; + private static final String KEY_IS_FRIEND_REQUEST = "is_friend_request"; + private static final String KEY_CUSTOM_FR_MESSAGE = "custom_friend_request_message"; @Inject SignalServiceMessageSender messageSender; - private long messageId; + private long messageId; // The message id + private long templateMessageId; // The message id of the message to template this send job from - public PushMediaSendJob(long messageId, Address destination) { - this(constructParameters(destination), messageId); + // Loki - Multi-device + + private Address destination; // Destination to check whether this is another device we're sending to + private boolean isFriendRequest; // Whether this is a friend request message + private String customFriendRequestMessage; // If this isn't set then we use the message body + + public PushMediaSendJob(long messageId, Address destination) { this(messageId, messageId, destination); } + public PushMediaSendJob(long templateMessageId, long messageId, Address destination) { this(templateMessageId, messageId, destination, false, null); } + public PushMediaSendJob(long templateMessageId, long messageId, Address destination, boolean isFriendRequest, String customFriendRequestMessage) { + this(constructParameters(destination), templateMessageId, messageId, destination, isFriendRequest, customFriendRequestMessage); } - private PushMediaSendJob(Job.Parameters parameters, long messageId) { + private PushMediaSendJob(@NonNull Job.Parameters parameters, long templateMessageId, long messageId, Address destination, boolean isFriendRequest, String customFriendRequestMessage) { super(parameters); + this.templateMessageId = templateMessageId; this.messageId = messageId; + this.destination = destination; + this.isFriendRequest = isFriendRequest; + this.customFriendRequestMessage = customFriendRequestMessage; } @WorkerThread public static void enqueue(@NonNull Context context, @NonNull JobManager jobManager, long messageId, @NonNull Address destination) { + enqueue(context, jobManager, messageId, messageId, destination); + } + + @WorkerThread + public static void enqueue(@NonNull Context context, @NonNull JobManager jobManager, long templateMessageId, long messageId, @NonNull Address destination) { + enqueue(context, jobManager, templateMessageId, messageId, destination, false, null); + } + + @WorkerThread + public static void enqueue(@NonNull Context context, @NonNull JobManager jobManager, long templateMessageId, long messageId, @NonNull Address destination, Boolean isFriendRequest, @Nullable String customFriendRequestMessage) { try { MmsDatabase database = DatabaseFactory.getMmsDatabase(context); OutgoingMediaMessage message = database.getOutgoingMessage(messageId); @@ -86,10 +114,10 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType { List attachmentJobs = Stream.of(attachments).map(a -> new AttachmentUploadJob(((DatabaseAttachment) a).getAttachmentId())).toList(); if (attachmentJobs.isEmpty()) { - jobManager.add(new PushMediaSendJob(messageId, destination)); + jobManager.add(new PushMediaSendJob(templateMessageId, messageId, destination, isFriendRequest, customFriendRequestMessage)); } else { jobManager.startChain(attachmentJobs) - .then(new PushMediaSendJob(messageId, destination)) + .then(new PushMediaSendJob(templateMessageId, messageId, destination, isFriendRequest, customFriendRequestMessage)) .enqueue(); } @@ -102,7 +130,14 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType { @Override public @NonNull Data serialize() { - return new Data.Builder().putLong(KEY_MESSAGE_ID, messageId).build(); + Data.Builder builder = new Data.Builder() + .putLong(KEY_TEMPLATE_MESSAGE_ID, templateMessageId) + .putLong(KEY_MESSAGE_ID, messageId) + .putString(KEY_DESTINATION, destination.serialize()) + .putBoolean(KEY_IS_FRIEND_REQUEST, isFriendRequest); + + if (customFriendRequestMessage != null) { builder.putString(KEY_CUSTOM_FR_MESSAGE, customFriendRequestMessage); } + return builder.build(); } @Override @@ -122,11 +157,9 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType { { ExpiringMessageManager expirationManager = ApplicationContext.getInstance(context).getExpiringMessageManager(); MmsDatabase database = DatabaseFactory.getMmsDatabase(context); - OutgoingMediaMessage message = database.getOutgoingMessage(messageId); + OutgoingMediaMessage message = database.getOutgoingMessage(templateMessageId); - message.isFriendRequest = (DatabaseFactory.getLokiMessageDatabase(context).getFriendRequestStatus(messageId) == LokiMessageFriendRequestStatus.REQUEST_SENDING); - - if (database.isSent(messageId)) { + if (messageId >= 0 && database.isSent(messageId)) { warn(TAG, "Message " + messageId + " was already sent. Ignoring."); return; } @@ -134,15 +167,17 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType { try { log(TAG, "Sending message: " + messageId); - Recipient recipient = message.getRecipient().resolve(); + Recipient recipient = Recipient.from(context, destination, false); byte[] profileKey = recipient.getProfileKey(); UnidentifiedAccessMode accessMode = recipient.getUnidentifiedAccessMode(); boolean unidentified = deliver(message); - database.markAsSent(messageId, true); - markAttachmentsUploaded(messageId, message.getAttachments()); - database.markUnidentified(messageId, unidentified); + if (messageId >= 0) { + database.markAsSent(messageId, true); + markAttachmentsUploaded(messageId, message.getAttachments()); + database.markUnidentified(messageId, unidentified); + } if (recipient.isLocalNumber()) { SyncMessageId id = new SyncMessageId(recipient.getAddress(), message.getSentTimeMillis()); @@ -163,7 +198,7 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType { } } - if (message.getExpiresIn() > 0 && !message.isExpirationUpdate()) { + if (messageId > 0 && message.getExpiresIn() > 0 && !message.isExpirationUpdate()) { database.markExpireStarted(messageId); expirationManager.scheduleDeletion(messageId, true, message.getExpiresIn()); } @@ -172,9 +207,11 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType { } catch (InsecureFallbackApprovalException ifae) { warn(TAG, "Failure", ifae); - database.markAsPendingInsecureSmsFallback(messageId); - notifyMediaMessageDeliveryFailed(context, messageId); - ApplicationContext.getInstance(context).getJobManager().add(new DirectoryRefreshJob(false)); + if (messageId >= 0) { + database.markAsPendingInsecureSmsFallback(messageId); + notifyMediaMessageDeliveryFailed(context, messageId); + ApplicationContext.getInstance(context).getJobManager().add(new DirectoryRefreshJob(false)); + } } catch (UntrustedIdentityException uie) { warn(TAG, "Failure", uie); database.addMismatchedIdentity(messageId, Address.fromSerialized(uie.getE164Number()), uie.getIdentityKey()); @@ -191,22 +228,19 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType { @Override public void onCanceled() { - DatabaseFactory.getMmsDatabase(context).markAsSentFailed(messageId); - notifyMediaMessageDeliveryFailed(context, messageId); + if (messageId >= 0) { + DatabaseFactory.getMmsDatabase(context).markAsSentFailed(messageId); + notifyMediaMessageDeliveryFailed(context, messageId); + } } private boolean deliver(OutgoingMediaMessage message) throws RetryLaterException, InsecureFallbackApprovalException, UntrustedIdentityException, UndeliverableMessageException { - if (message.getRecipient() == null) { - throw new UndeliverableMessageException("No destination address."); - } - try { -// rotateSenderCertificateIfNecessary(); - - SignalServiceAddress address = getPushAddress(message.getRecipient().getAddress()); + Recipient recipient = Recipient.from(context, destination, false); + SignalServiceAddress address = getPushAddress(recipient.getAddress()); List attachments = Stream.of(message.getAttachments()).filterNot(Attachment::isSticker).toList(); List serviceAttachments = getAttachmentPointersFor(attachments); Optional profileKey = getProfileKey(message.getRecipient()); @@ -216,15 +250,10 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType { List previews = getPreviewsFor(message); // Loki - Include a pre key bundle if the message is a friend request or an end session message - PreKeyBundle preKeyBundle; - if (message.isFriendRequest) { - preKeyBundle = DatabaseFactory.getLokiPreKeyBundleDatabase(context).generatePreKeyBundle(address.getNumber()); - } else { - preKeyBundle = null; - } - + PreKeyBundle preKeyBundle = isFriendRequest ? DatabaseFactory.getLokiPreKeyBundleDatabase(context).generatePreKeyBundle(address.getNumber()) : null; + String body = (isFriendRequest && customFriendRequestMessage != null) ? customFriendRequestMessage : message.getBody(); SignalServiceDataMessage mediaMessage = SignalServiceDataMessage.newBuilder() - .withBody(message.getBody()) + .withBody(body) .withAttachments(serviceAttachments) .withTimestamp(message.getSentTimeMillis()) .withExpiration((int)(message.getExpiresIn() / 1000)) @@ -235,7 +264,7 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType { .withPreviews(previews) .asExpirationUpdate(message.isExpirationUpdate()) .withPreKeyBundle(preKeyBundle) - .asFriendRequest(message.isFriendRequest) + .asFriendRequest(isFriendRequest) .build(); if (address.getNumber().equals(TextSecurePreferences.getLocalNumber(context))) { @@ -245,7 +274,7 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType { messageSender.sendMessage(messageId, syncMessage, syncAccess); return syncAccess.isPresent(); } else { - return messageSender.sendMessage(messageId, address, UnidentifiedAccessUtil.getAccessFor(context, message.getRecipient()), mediaMessage).getSuccess().isUnidentified(); + return messageSender.sendMessage(messageId, address, UnidentifiedAccessUtil.getAccessFor(context, recipient), mediaMessage).getSuccess().isUnidentified(); } } catch (UnregisteredUserException e) { warn(TAG, e); @@ -262,7 +291,12 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType { public static final class Factory implements Job.Factory { @Override public @NonNull PushMediaSendJob create(@NonNull Parameters parameters, @NonNull Data data) { - return new PushMediaSendJob(parameters, data.getLong(KEY_MESSAGE_ID)); + long templateMessageID = data.getLong(KEY_TEMPLATE_MESSAGE_ID); + long messageID = data.getLong(KEY_MESSAGE_ID); + Address destination = Address.fromSerialized(data.getString(KEY_DESTINATION)); + boolean isFriendRequest = data.getBoolean(KEY_IS_FRIEND_REQUEST); + String frMessage = data.hasString(KEY_CUSTOM_FR_MESSAGE) ? data.getString(KEY_CUSTOM_FR_MESSAGE) : null; + return new PushMediaSendJob(parameters, templateMessageID, messageID, destination, isFriendRequest, frMessage); } } } diff --git a/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java index 3b26f01955..4567c58672 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java @@ -53,9 +53,9 @@ public class PushTextSendJob extends PushSendJob implements InjectableType { // Loki - Multi-device - private Address destination = null; // Destination to check whether this is another device we're sending to - private boolean isFriendRequest = false; // Whether this is a friend request message - private String customFriendRequestMessage = null; // If this isn't set then we use the message body + private Address destination; // Destination to check whether this is another device we're sending to + private boolean isFriendRequest; // Whether this is a friend request message + private String customFriendRequestMessage; // If this isn't set then we use the message body public PushTextSendJob(long messageId, Address destination) { this(messageId, messageId, destination); } public PushTextSendJob(long templateMessageId, long messageId, Address destination) { this(templateMessageId, messageId, destination, false, null); } @@ -117,7 +117,7 @@ public class PushTextSendJob extends PushSendJob implements InjectableType { byte[] profileKey = recipient.getProfileKey(); UnidentifiedAccessMode accessMode = recipient.getUnidentifiedAccessMode(); - boolean unidentified = deliver(messageId, recipient, record); + boolean unidentified = deliver(record); if (messageId >= 0) { database.markAsSent(messageId, true); @@ -185,11 +185,12 @@ public class PushTextSendJob extends PushSendJob implements InjectableType { } } - private boolean deliver(long messageId, Recipient recipient, SmsMessageRecord message) + private boolean deliver(SmsMessageRecord message) throws UntrustedIdentityException, InsecureFallbackApprovalException, RetryLaterException { try { // rotateSenderCertificateIfNecessary(); + Recipient recipient = Recipient.from(context, destination, false); SignalServiceAddress address = getPushAddress(recipient.getAddress()); Optional profileKey = getProfileKey(recipient); Optional unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient); diff --git a/src/org/thoughtcrime/securesms/sms/MessageSender.java b/src/org/thoughtcrime/securesms/sms/MessageSender.java index 3992653c55..c3e8c56523 100644 --- a/src/org/thoughtcrime/securesms/sms/MessageSender.java +++ b/src/org/thoughtcrime/securesms/sms/MessageSender.java @@ -239,8 +239,34 @@ public class MessageSender { } private static void sendMediaPush(Context context, Recipient recipient, long messageId) { + LokiStorageAPI storageAPI = LokiStorageAPI.Companion.getShared(); JobManager jobManager = ApplicationContext.getInstance(context).getJobManager(); - PushMediaSendJob.enqueue(context, jobManager, messageId, recipient.getAddress()); + + // Just send the message normally if the storage api is not set or if it's a group message + String recipientPubKey = recipient.getAddress().serialize(); + if (storageAPI == null || UtilKt.isGroupChat(recipientPubKey)) { + if (storageAPI == null) { Log.w("Loki", "LokiStorageAPI is not initialized!"); } + PushMediaSendJob.enqueue(context, jobManager, messageId, recipient.getAddress()); + } + + MultiDeviceUtilKt.getAllDevices(context, recipientPubKey, storageAPI, (devicePubKey, isFriend, friendCount) -> { + Address deviceAddress = Address.fromSerialized(devicePubKey); + long messageIdToUse = recipientPubKey.equals(devicePubKey) ? messageId : -1L; + + // Send a normal message to our friends + if (isFriend) { + PushMediaSendJob.enqueue(context, jobManager, messageId, messageIdToUse, deviceAddress); + } else { + // Send friend requests to non friends + // If we're friends with one of the devices then send out a default friend request message + boolean isFriendsWithAny = friendCount > 0; + String defaultFriendRequestMessage = isFriendsWithAny ? "This is a friend request for devices linked to " + recipientPubKey : null; + PushMediaSendJob.enqueue(context, jobManager, messageId, messageIdToUse, deviceAddress, true, defaultFriendRequestMessage); + } + + return Unit.INSTANCE; + }); + } private static void sendGroupPush(Context context, Recipient recipient, long messageId, Address filterAddress) {