From d0742cf09f22b0758fa3510f18380fc5d7df43de Mon Sep 17 00:00:00 2001
From: nielsandriesse <andriesseniels@gmail.com>
Date: Wed, 13 May 2020 11:15:17 +1000
Subject: [PATCH] Refactor multi device message sending

---
 .../securesms/ConversationListActivity.java   |   4 +-
 .../loki/RecipientAvatarModifiedEvent.kt      |   5 -
 .../loki/protocol/EphemeralMessage.kt         |   2 +-
 .../protocol/MultiDeviceOpenGroupUpdateJob.kt |   1 +
 .../loki/protocol/MultiDeviceProtocol.kt      | 130 ++++++++++--------
 .../protocol/PushEphemeralMessageSendJob.kt   |   8 +-
 .../loki/protocol/SyncMessagesProtocol.kt     |   1 +
 .../utilities/ProfilePictureModifiedEvent.kt  |   5 +
 .../securesms/recipients/Recipient.java       |   4 +-
 9 files changed, 83 insertions(+), 77 deletions(-)
 delete mode 100644 src/org/thoughtcrime/securesms/loki/RecipientAvatarModifiedEvent.kt
 create mode 100644 src/org/thoughtcrime/securesms/loki/utilities/ProfilePictureModifiedEvent.kt

diff --git a/src/org/thoughtcrime/securesms/ConversationListActivity.java b/src/org/thoughtcrime/securesms/ConversationListActivity.java
index 0f717f4787..1c3180ee35 100644
--- a/src/org/thoughtcrime/securesms/ConversationListActivity.java
+++ b/src/org/thoughtcrime/securesms/ConversationListActivity.java
@@ -49,7 +49,7 @@ import org.thoughtcrime.securesms.database.Address;
 import org.thoughtcrime.securesms.database.DatabaseFactory;
 import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo;
 import org.thoughtcrime.securesms.lock.RegistrationLockDialog;
-import org.thoughtcrime.securesms.loki.RecipientAvatarModifiedEvent;
+import org.thoughtcrime.securesms.loki.utilities.ProfilePictureModifiedEvent;
 import org.thoughtcrime.securesms.loki.activities.JoinPublicChatActivity;
 import org.thoughtcrime.securesms.mms.GlideApp;
 import org.thoughtcrime.securesms.notifications.MarkReadReceiver;
@@ -330,7 +330,7 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
   }
 
   @Subscribe(threadMode = ThreadMode.MAIN)
-  public void onAvatarModified(RecipientAvatarModifiedEvent event) {
+  public void onAvatarModified(ProfilePictureModifiedEvent event) {
     Recipient recipient = event.getRecipient();
     if (recipient.isLocalNumber() || recipient.isUserMasterDevice()) {
       initializeProfileIcon(recipient);
diff --git a/src/org/thoughtcrime/securesms/loki/RecipientAvatarModifiedEvent.kt b/src/org/thoughtcrime/securesms/loki/RecipientAvatarModifiedEvent.kt
deleted file mode 100644
index 4faf84714b..0000000000
--- a/src/org/thoughtcrime/securesms/loki/RecipientAvatarModifiedEvent.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package org.thoughtcrime.securesms.loki
-
-import org.thoughtcrime.securesms.recipients.Recipient
-
-data class RecipientAvatarModifiedEvent(val recipient: Recipient)
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/loki/protocol/EphemeralMessage.kt b/src/org/thoughtcrime/securesms/loki/protocol/EphemeralMessage.kt
index e9c8377101..c813dc813a 100644
--- a/src/org/thoughtcrime/securesms/loki/protocol/EphemeralMessage.kt
+++ b/src/org/thoughtcrime/securesms/loki/protocol/EphemeralMessage.kt
@@ -2,7 +2,7 @@ package org.thoughtcrime.securesms.loki.protocol
 
 import org.whispersystems.signalservice.internal.util.JsonUtil
 
-data class EphemeralMessage private constructor(val data: Map<*, *>) {
+class EphemeralMessage private constructor(val data: Map<*, *>) {
 
     companion object {
 
diff --git a/src/org/thoughtcrime/securesms/loki/protocol/MultiDeviceOpenGroupUpdateJob.kt b/src/org/thoughtcrime/securesms/loki/protocol/MultiDeviceOpenGroupUpdateJob.kt
index 2c6ed90ff0..1b60841643 100644
--- a/src/org/thoughtcrime/securesms/loki/protocol/MultiDeviceOpenGroupUpdateJob.kt
+++ b/src/org/thoughtcrime/securesms/loki/protocol/MultiDeviceOpenGroupUpdateJob.kt
@@ -19,6 +19,7 @@ import javax.inject.Inject
 class MultiDeviceOpenGroupUpdateJob private constructor(parameters: Parameters) : BaseJob(parameters), InjectableType {
 
     companion object {
+
         const val KEY = "MultiDeviceOpenGroupUpdateJob"
     }
 
diff --git a/src/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt b/src/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt
index aa0c7d823f..e77226f99c 100644
--- a/src/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt
+++ b/src/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt
@@ -2,7 +2,17 @@ package org.thoughtcrime.securesms.loki.protocol
 
 import android.content.Context
 import org.thoughtcrime.securesms.ApplicationContext
+import org.thoughtcrime.securesms.database.Address
+import org.thoughtcrime.securesms.database.DatabaseFactory
+import org.thoughtcrime.securesms.jobs.PushMediaSendJob
+import org.thoughtcrime.securesms.jobs.PushSendJob
+import org.thoughtcrime.securesms.jobs.PushTextSendJob
 import org.thoughtcrime.securesms.recipients.Recipient
+import org.whispersystems.signalservice.loki.api.fileserver.LokiFileServerAPI
+import org.whispersystems.signalservice.loki.protocol.meta.SessionMetaProtocol
+import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol
+import org.whispersystems.signalservice.loki.protocol.todo.LokiMessageFriendRequestStatus
+import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus
 
 object MultiDeviceProtocol {
 
@@ -12,6 +22,8 @@ object MultiDeviceProtocol {
         ApplicationContext.getInstance(context).jobManager.add(PushEphemeralMessageSendJob(unlinkingRequest))
     }
 
+    enum class MessageType { Text, Media }
+
     @JvmStatic
     fun sendTextPush(context: Context, recipient: Recipient, messageID: Long) {
 
@@ -22,65 +34,61 @@ object MultiDeviceProtocol {
 
     }
 
-//    private static void sendMessagePush(Context context, MessageType type, Recipient recipient, long messageId) {
-//        JobManager jobManager = ApplicationContext.getInstance(context).getJobManager();
-//
-//        // Just send the message normally if it's a group message or we're sending to one of our devices
-//        String recipientHexEncodedPublicKey = recipient.getAddress().serialize();
-//        if (GeneralUtilitiesKt.isPublicChat(context, recipientHexEncodedPublicKey) || PromiseUtil.get(MultiDeviceUtilities.isOneOfOurDevices(context, recipient.getAddress()), false)) {
-//            if (type == MessageType.MEDIA) {
-//                PushMediaSendJob.enqueue(context, jobManager, messageId, recipient.getAddress(), false);
-//            } else {
-//                jobManager.add(new PushTextSendJob(messageId, recipient.getAddress()));
-//            }
-//            return;
-//        }
-//
-//        // If we get here then we are sending a message to a device that is not ours
-//        boolean[] hasSentSyncMessage = { false };
-//        MultiDeviceUtilities.getAllDevicePublicKeysWithFriendStatus(context, recipientHexEncodedPublicKey).success(devices -> {
-//            int friendCount = MultiDeviceUtilities.getFriendCount(context, devices.keySet());
-//            Util.runOnMain(() -> {
-//            ArrayList<Job> jobs = new ArrayList<>();
-//            for (Map.Entry<String, Boolean> entry : devices.entrySet()) {
-//            String deviceHexEncodedPublicKey = entry.getKey();
-//            boolean isFriend = entry.getValue();
-//
-//            Address address = Address.fromSerialized(deviceHexEncodedPublicKey);
-//            long messageIDToUse = recipientHexEncodedPublicKey.equals(deviceHexEncodedPublicKey) ? messageId : -1L;
-//
-//            if (isFriend) {
-//                // Send a normal message if the user is friends with the recipient
-//                // We should also send a sync message if we haven't already sent one
-//                boolean shouldSendSyncMessage = !hasSentSyncMessage[0] && address.isPhone();
-//                if (type == MessageType.MEDIA) {
-//                    jobs.add(new PushMediaSendJob(messageId, messageIDToUse, address, false, null, shouldSendSyncMessage));
-//                } else {
-//                    jobs.add(new PushTextSendJob(messageId, messageIDToUse, address, shouldSendSyncMessage));
-//                }
-//                if (shouldSendSyncMessage) { hasSentSyncMessage[0] = true; }
-//            } else {
-//                // Send friend requests to non-friends. If the user is friends with any
-//                // of the devices then send out a default friend request message.
-//                boolean isFriendsWithAny = (friendCount > 0);
-//                String defaultFriendRequestMessage = isFriendsWithAny ? "Please accept to enable messages to be synced across devices" : null;
-//                if (type == MessageType.MEDIA) {
-//                    jobs.add(new PushMediaSendJob(messageId, messageIDToUse, address, true, defaultFriendRequestMessage, false));
-//                } else {
-//                    jobs.add(new PushTextSendJob(messageId, messageIDToUse, address, true, defaultFriendRequestMessage, false));
-//                }
-//            }
-//        }
-//
-//            // Start the send
-//            if (type == MessageType.MEDIA) {
-//                PushMediaSendJob.enqueue(context, jobManager, (List<PushMediaSendJob>)(List)jobs);
-//            } else {
-//                // Schedule text send jobs
-//                jobManager.startChain(jobs).enqueue();
-//            }
-//        });
-//            return Unit.INSTANCE;
-//        });
-//    }
+    private fun sendMessagePushToDevice(context: Context, recipient: Recipient, messageID: Long, messageType: MessageType): PushSendJob {
+        val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient)
+        val threadFRStatus = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID)
+        val isContactFriend = (threadFRStatus == LokiThreadFriendRequestStatus.FRIENDS)
+        val messageFRStatus = DatabaseFactory.getLokiMessageDatabase(context).getFriendRequestStatus(messageID)
+        val isFRMessage = (messageFRStatus != LokiMessageFriendRequestStatus.NONE)
+        val hasVisibleContent = when (messageType) {
+            MessageType.Text -> DatabaseFactory.getSmsDatabase(context).getMessage(messageID).body.isNotBlank()
+            MessageType.Media -> {
+                val outgoingMediaMessage = DatabaseFactory.getMmsDatabase(context).getOutgoingMessage(messageID)
+                outgoingMediaMessage.body.isNotBlank() || outgoingMediaMessage.attachments.isNotEmpty()
+            }
+        }
+        val shouldSendAutoGeneratedFR = !isContactFriend && !isFRMessage
+            && !SessionMetaProtocol.shared.isNoteToSelf(recipient.address.serialize()) && !recipient.address.isGroup // Group threads work through session requests
+            && hasVisibleContent
+        if (!shouldSendAutoGeneratedFR) {
+            when (messageType) {
+                MessageType.Text -> return PushTextSendJob(messageID, recipient.address)
+                MessageType.Media -> return PushMediaSendJob(messageID, recipient.address)
+            }
+        } else {
+            val autoGeneratedFRMessage = "Please accept to enable messages to be synced across devices"
+            when (messageType) {
+                MessageType.Text -> return PushTextSendJob(messageID, messageID, recipient.address, true, autoGeneratedFRMessage)
+                MessageType.Media -> return PushMediaSendJob(messageID, messageID, recipient.address, true, autoGeneratedFRMessage)
+            }
+        }
+    }
+
+    private fun sendMessagePush(context: Context, recipient: Recipient, messageID: Long, messageType: MessageType) {
+        val jobManager = ApplicationContext.getInstance(context).jobManager
+        val isMultiDeviceRequired = !recipient.address.isOpenGroup
+        if (!isMultiDeviceRequired) {
+            when (messageType) {
+                MessageType.Text -> jobManager.add(PushTextSendJob(messageID, recipient.address))
+                MessageType.Media -> PushMediaSendJob.enqueue(context, jobManager, messageID, recipient.address)
+            }
+        }
+        val publicKey = recipient.address.serialize()
+        LokiFileServerAPI.shared.getDeviceLinks(publicKey).success {
+            val devices = MultiDeviceProtocol.shared.getAllLinkedDevices(publicKey)
+            val jobs = devices.map { sendMessagePushToDevice(context, Recipient.from(context, Address.fromSerialized(it), false), messageID, messageType) }
+            @Suppress("UNCHECKED_CAST")
+            when (messageType) {
+                MessageType.Text -> jobManager.startChain(jobs).enqueue()
+                MessageType.Media -> PushMediaSendJob.enqueue(context, jobManager, jobs as List<PushMediaSendJob>)
+            }
+        }.fail { exception ->
+            // Proceed even if updating the recipient's device links failed, so that message sending
+            // is independent of whether the file server is online
+            when (messageType) {
+                MessageType.Text -> jobManager.add(PushTextSendJob(messageID, recipient.address))
+                MessageType.Media -> PushMediaSendJob.enqueue(context, jobManager, messageID, recipient.address)
+            }
+        }
+    }
 }
diff --git a/src/org/thoughtcrime/securesms/loki/protocol/PushEphemeralMessageSendJob.kt b/src/org/thoughtcrime/securesms/loki/protocol/PushEphemeralMessageSendJob.kt
index 048aa97e8a..74cc3fcf3f 100644
--- a/src/org/thoughtcrime/securesms/loki/protocol/PushEphemeralMessageSendJob.kt
+++ b/src/org/thoughtcrime/securesms/loki/protocol/PushEphemeralMessageSendJob.kt
@@ -15,16 +15,12 @@ import org.whispersystems.signalservice.api.push.SignalServiceAddress
 import java.io.IOException
 import java.util.concurrent.TimeUnit
 
-class PushEphemeralMessageSendJob private constructor(
-    parameters: Parameters,
-    private val message: EphemeralMessage
-) : BaseJob(parameters) {
+class PushEphemeralMessageSendJob private constructor(parameters: Parameters, private val message: EphemeralMessage) : BaseJob(parameters) {
 
     companion object {
+        private val KEY_MESSAGE = "message"
 
         const val KEY = "PushBackgroundMessageSendJob"
-
-        private val KEY_MESSAGE = "message"
     }
 
     constructor(message: EphemeralMessage) : this(Parameters.Builder()
diff --git a/src/org/thoughtcrime/securesms/loki/protocol/SyncMessagesProtocol.kt b/src/org/thoughtcrime/securesms/loki/protocol/SyncMessagesProtocol.kt
index 5f4a1a88d6..96b09268af 100644
--- a/src/org/thoughtcrime/securesms/loki/protocol/SyncMessagesProtocol.kt
+++ b/src/org/thoughtcrime/securesms/loki/protocol/SyncMessagesProtocol.kt
@@ -40,6 +40,7 @@ object SyncMessagesProtocol {
     fun shouldSyncContact(context: Context, address: Address): Boolean {
         if (!PublicKeyValidation.isValid(address.serialize())) { return false }
         if (address.serialize() == TextSecurePreferences.getMasterHexEncodedPublicKey(context)) { return false }
+        if (address.serialize() == TextSecurePreferences.getLocalNumber(context)) { return false }
         val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, address, false))
         val isFriend = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID) == LokiThreadFriendRequestStatus.FRIENDS
         return isFriend
diff --git a/src/org/thoughtcrime/securesms/loki/utilities/ProfilePictureModifiedEvent.kt b/src/org/thoughtcrime/securesms/loki/utilities/ProfilePictureModifiedEvent.kt
new file mode 100644
index 0000000000..a05676ef62
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/loki/utilities/ProfilePictureModifiedEvent.kt
@@ -0,0 +1,5 @@
+package org.thoughtcrime.securesms.loki.utilities
+
+import org.thoughtcrime.securesms.recipients.Recipient
+
+data class ProfilePictureModifiedEvent(val recipient: Recipient)
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/recipients/Recipient.java b/src/org/thoughtcrime/securesms/recipients/Recipient.java
index 4cd243114d..297a6cf6b7 100644
--- a/src/org/thoughtcrime/securesms/recipients/Recipient.java
+++ b/src/org/thoughtcrime/securesms/recipients/Recipient.java
@@ -44,7 +44,7 @@ import org.thoughtcrime.securesms.database.RecipientDatabase.UnidentifiedAccessM
 import org.thoughtcrime.securesms.database.RecipientDatabase.VibrateState;
 import org.thoughtcrime.securesms.logging.Log;
 import org.thoughtcrime.securesms.loki.todo.JazzIdenticonContactPhoto;
-import org.thoughtcrime.securesms.loki.RecipientAvatarModifiedEvent;
+import org.thoughtcrime.securesms.loki.utilities.ProfilePictureModifiedEvent;
 import org.thoughtcrime.securesms.notifications.NotificationChannels;
 import org.thoughtcrime.securesms.recipients.RecipientProvider.RecipientDetails;
 import org.thoughtcrime.securesms.util.FutureTaskListener;
@@ -399,7 +399,7 @@ public class Recipient implements RecipientModifiedListener {
     }
 
     notifyListeners();
-    EventBus.getDefault().post(new RecipientAvatarModifiedEvent(this));
+    EventBus.getDefault().post(new ProfilePictureModifiedEvent(this));
   }
 
   public synchronized boolean isProfileSharing() {