From 789aa244b5594407d904178f7eb1f30cf1c9c12c Mon Sep 17 00:00:00 2001 From: Mikunj Date: Fri, 1 Nov 2019 12:15:40 +1100 Subject: [PATCH] Use promises instead of blocking the thread. --- .../components/TypingStatusSender.java | 2 +- .../conversation/ConversationActivity.java | 28 ++--- .../securesms/jobs/PushDecryptJob.java | 32 +++-- .../securesms/jobs/PushMediaSendJob.java | 3 +- .../securesms/jobs/PushTextSendJob.java | 3 +- .../securesms/jobs/TypingSendJob.java | 6 +- .../securesms/loki/FriendRequestHandler.kt | 37 +++--- .../securesms/loki/MultiDeviceUtilities.kt | 119 +++++++++++------- .../securesms/sms/MessageSender.java | 79 +++++++----- 9 files changed, 182 insertions(+), 127 deletions(-) diff --git a/src/org/thoughtcrime/securesms/components/TypingStatusSender.java b/src/org/thoughtcrime/securesms/components/TypingStatusSender.java index dd28616d8e..927b158e42 100644 --- a/src/org/thoughtcrime/securesms/components/TypingStatusSender.java +++ b/src/org/thoughtcrime/securesms/components/TypingStatusSender.java @@ -91,7 +91,7 @@ public class TypingStatusSender { ApplicationContext.getInstance(context).getJobManager().add(new TypingSendJob(threadId, typingStarted)); return; } - LokiStorageAPI.shared.getAllDevicePublicKeysAsync(recipient.getAddress().serialize()).success(devices -> { + LokiStorageAPI.shared.getAllDevicePublicKeys(recipient.getAddress().serialize()).success(devices -> { for (String device : devices) { Recipient deviceRecipient = Recipient.from(context, Address.fromSerialized(device), false); long deviceThreadID = threadDatabase.getThreadIdIfExistsFor(deviceRecipient); diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java index 2c80d636f7..d9ec0a0154 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -127,6 +127,7 @@ import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher; import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable; import org.thoughtcrime.securesms.crypto.SecurityEvent; import org.thoughtcrime.securesms.database.Address; +import org.thoughtcrime.securesms.database.Database; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DraftDatabase; import org.thoughtcrime.securesms.database.DraftDatabase.Draft; @@ -2192,7 +2193,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity if (threadID != this.threadId) { Recipient threadRecipient = DatabaseFactory.getThreadDatabase(this).getRecipientForThreadId(threadID); if (threadRecipient != null && !threadRecipient.isGroupRecipient()) { - LokiStorageAPI.shared.getAllDevicePublicKeysAsync(threadRecipient.getAddress().serialize()).success(devices -> { + LokiStorageAPI.shared.getAllDevicePublicKeys(threadRecipient.getAddress().serialize()).success(devices -> { // We should update our input if this thread is a part of the other threads device if (devices.contains(recipient.getAddress().serialize())) { this.updateInputPanel(); @@ -2212,10 +2213,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity return; } - task(() -> { - // Run the functions below in a background thread since they are blocking - return MultiDeviceUtilities.isFriendsWithAnyLinkedDevice(this, recipient) || !hasPendingFriendRequestWithAnyLinkedDevice(); - }).success(shouldEnableInput -> { + // It could take a while before our promise resolves, so we assume the best case + LokiThreadFriendRequestStatus friendRequestStatus = DatabaseFactory.getLokiThreadDatabase(this).getFriendRequestStatus(threadId); + boolean isPending = friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_SENDING || friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_SENT || friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_RECEIVED; + setInputPanelEnabled(!isPending); + + // This promise correctly updates the UI for multidevice + MultiDeviceUtilities.shouldEnableUserInput(this, recipient).success(shouldEnableInput -> { setInputPanelEnabled(shouldEnableInput); return Unit.INSTANCE; }); @@ -3054,20 +3058,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity updateInputPanel(); } - public boolean hasPendingFriendRequestWithAnyLinkedDevice() { - if (recipient.isGroupRecipient()) return false; - - // This call will block the thread that is being run on! be careful - Map map = MultiDeviceUtilities.getAllDeviceFriendRequestStatuses(this, recipient.getAddress().serialize()); - for (LokiThreadFriendRequestStatus status : map.values()) { - if (status == LokiThreadFriendRequestStatus.REQUEST_SENDING || status == LokiThreadFriendRequestStatus.REQUEST_SENT || status == LokiThreadFriendRequestStatus.REQUEST_RECEIVED) { - return true; - } - } - - return false; - } - public boolean isNoteToSelf() { return TextSecurePreferences.getLocalNumber(this).equalsIgnoreCase(recipient.getAddress().serialize()); } diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index e7e893ba11..332375246a 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -134,6 +134,7 @@ import org.whispersystems.signalservice.loki.messaging.LokiMessageFriendRequestS import org.whispersystems.signalservice.loki.messaging.LokiServiceMessage; import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus; import org.whispersystems.signalservice.loki.messaging.LokiThreadSessionResetStatus; +import org.whispersystems.signalservice.loki.utilities.PromiseUtil; import java.security.MessageDigest; import java.security.SecureRandom; @@ -1094,15 +1095,19 @@ public class PushDecryptJob extends BaseJob implements InjectableType { // it must be a friend request accepted message. Declining a friend request doesn't send a message. lokiThreadDatabase.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.FRIENDS); // Update the last message if needed - String primaryDevice = LokiStorageAPI.shared.getPrimaryDevicePublicKey(pubKey); - long primaryDeviceThreadID = primaryDevice == null ? threadID : DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, Address.fromSerialized(primaryDevice), false)); - FriendRequestHandler.updateLastFriendRequestMessage(context, primaryDeviceThreadID, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED); + LokiStorageAPI.shared.getPrimaryDevicePublicKey(pubKey).success(primaryDevice -> { + Util.runOnMain(() -> { + long primaryDeviceThreadID = primaryDevice == null ? threadID : DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, Address.fromSerialized(primaryDevice), false)); + FriendRequestHandler.updateLastFriendRequestMessage(context, primaryDeviceThreadID, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED); + }); + return Unit.INSTANCE; + }); } private void updateFriendRequestStatusIfNeeded(@NonNull SignalServiceEnvelope envelope, @NonNull SignalServiceContent content, @NonNull SignalServiceDataMessage message) { if (!envelope.isFriendRequest() || isGroupChatMessage(message)) { return; } // This handles the case where another user sends us a regular message without authorisation - boolean shouldBecomeFriends = MultiDeviceUtilities.shouldAutomaticallyBecomeFriendsWithDevice(content.getSender(), context); + boolean shouldBecomeFriends = PromiseUtil.get(MultiDeviceUtilities.shouldAutomaticallyBecomeFriendsWithDevice(content.getSender(), context), false); if (shouldBecomeFriends) { // Become friends AND update the message they sent becomeFriendsWithContact(content.getSender()); @@ -1113,9 +1118,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType { Recipient originalRecipient = getMessageDestination(content, message); Recipient primaryDeviceRecipient = getMessagePrimaryDestination(content, message); LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context); - SmsDatabase smsMessageDatabase = DatabaseFactory.getSmsDatabase(context); - MmsDatabase mmsMessageDatabase = DatabaseFactory.getMmsDatabase(context); - LokiMessageDatabase lokiMessageDatabase= DatabaseFactory.getLokiMessageDatabase(context); long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(originalRecipient); long primaryDeviceThreadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(primaryDeviceRecipient); @@ -1532,7 +1534,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType { private Recipient getPrimaryDeviceRecipient(String recipient) { try { - String primaryDevice = LokiStorageAPI.shared.getPrimaryDevicePublicKey(recipient); + String primaryDevice = LokiStorageAPI.shared.getPrimaryDevicePublicKey(recipient).get(); String publicKey = (primaryDevice != null) ? primaryDevice : recipient; // If the public key matches our primary device then we need to forward the message to ourselves (Note to self) String ourPrimaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context); @@ -1593,10 +1595,16 @@ public class PushDecryptJob extends BaseJob implements InjectableType { } else if (content.getCallMessage().isPresent() || content.getTypingMessage().isPresent()) { return sender.isBlocked(); } else if (content.getSyncMessage().isPresent()) { - // We should ignore a sync message if the sender is not one of our devices - boolean isOurDevice = MultiDeviceUtilities.isOneOfOurDevices(context, sender.getAddress()); - if (!isOurDevice) { Log.w(TAG, "Got a sync message from a device that is not ours!."); } - return !isOurDevice; + try { + // We should ignore a sync message if the sender is not one of our devices + boolean isOurDevice = MultiDeviceUtilities.isOneOfOurDevices(context, sender.getAddress()).get(); + if (!isOurDevice) { + Log.w(TAG, "Got a sync message from a device that is not ours!."); + } + return !isOurDevice; + } catch (Exception e) { + return true; + } } return false; diff --git a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java index 9a10d5f797..408e353d9c 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java @@ -45,6 +45,7 @@ import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException; import org.whispersystems.signalservice.loki.api.LokiStorageAPI; import org.whispersystems.signalservice.loki.messaging.LokiSyncMessage; +import org.whispersystems.signalservice.loki.utilities.PromiseUtil; import java.io.FileNotFoundException; import java.io.IOException; @@ -281,7 +282,7 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType { LokiSyncMessage syncMessage = null; if (shouldSendSyncMessage) { // Set the sync message destination the primary device, this way it will show that we sent a message to the primary device and not a secondary device - String primaryDevice = LokiStorageAPI.shared.getPrimaryDevicePublicKey(address.getNumber()); + String primaryDevice = PromiseUtil.get(LokiStorageAPI.shared.getPrimaryDevicePublicKey(address.getNumber()), null); SignalServiceAddress primaryAddress = primaryDevice == null ? address : new SignalServiceAddress(primaryDevice); // We also need to use the original message id and not -1 syncMessage = new LokiSyncMessage(primaryAddress, templateMessageId); diff --git a/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java index 7c93b82c8b..bfd4839e36 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java @@ -32,6 +32,7 @@ import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException; import org.whispersystems.signalservice.loki.api.LokiStorageAPI; import org.whispersystems.signalservice.loki.messaging.LokiSyncMessage; +import org.whispersystems.signalservice.loki.utilities.PromiseUtil; import java.io.IOException; @@ -232,7 +233,7 @@ public class PushTextSendJob extends PushSendJob implements InjectableType { LokiSyncMessage syncMessage = null; if (shouldSendSyncMessage) { // Set the sync message destination to the primary device, this way it will show that we sent a message to the primary device and not a secondary device - String primaryDevice = LokiStorageAPI.shared.getPrimaryDevicePublicKey(address.getNumber()); + String primaryDevice = PromiseUtil.get(LokiStorageAPI.shared.getPrimaryDevicePublicKey(address.getNumber()), null); SignalServiceAddress primaryAddress = primaryDevice == null ? address : new SignalServiceAddress(primaryDevice); // We also need to use the original message id and not -1 syncMessage = new LokiSyncMessage(primaryAddress, templateMessageId); diff --git a/src/org/thoughtcrime/securesms/jobs/TypingSendJob.java b/src/org/thoughtcrime/securesms/jobs/TypingSendJob.java index 023afda792..babe7fe04e 100644 --- a/src/org/thoughtcrime/securesms/jobs/TypingSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/TypingSendJob.java @@ -20,6 +20,7 @@ import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair; import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage; import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage.Action; import org.whispersystems.signalservice.api.push.SignalServiceAddress; +import org.whispersystems.signalservice.loki.utilities.PromiseUtil; import java.util.Collections; import java.util.List; @@ -98,7 +99,10 @@ public class TypingSendJob extends BaseJob implements InjectableType { SignalServiceTypingMessage typingMessage = new SignalServiceTypingMessage(typing ? Action.STARTED : Action.STOPPED, System.currentTimeMillis(), groupId); // Loki - Don't send typing indicators in group chats or to ourselves - if (!recipient.isGroupRecipient() && !MultiDeviceUtilities.isOneOfOurDevices(context, recipient.getAddress())) { + if (recipient.isGroupRecipient()) { return; } + + boolean isOurDevice = PromiseUtil.get(MultiDeviceUtilities.isOneOfOurDevices(context, recipient.getAddress()), false); + if (!isOurDevice) { messageSender.sendTyping(0, addresses, unidentifiedAccess, typingMessage); } } diff --git a/src/org/thoughtcrime/securesms/loki/FriendRequestHandler.kt b/src/org/thoughtcrime/securesms/loki/FriendRequestHandler.kt index 70a0850f36..732588d2a2 100644 --- a/src/org/thoughtcrime/securesms/loki/FriendRequestHandler.kt +++ b/src/org/thoughtcrime/securesms/loki/FriendRequestHandler.kt @@ -5,6 +5,7 @@ import nl.komponents.kovenant.Promise import nl.komponents.kovenant.ui.alwaysUi import nl.komponents.kovenant.ui.successUi import org.thoughtcrime.securesms.database.DatabaseFactory +import org.thoughtcrime.securesms.util.Util import org.whispersystems.signalservice.loki.api.LokiStorageAPI import org.whispersystems.signalservice.loki.messaging.LokiMessageFriendRequestStatus import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus @@ -34,8 +35,10 @@ object FriendRequestHandler { if (type == ActionType.Sending) { // We only want to update message status if we aren't friends with another of their devices // This avoids spam in the ui where it would keep telling the user that they sent a friend request on every single message - if (!isFriendsWithAnyLinkedDevice(context, recipient) && friendRequestStatus == LokiMessageFriendRequestStatus.NONE) { - messageDatabase.setFriendRequestStatus(messageId, LokiMessageFriendRequestStatus.REQUEST_SENDING) + isFriendsWithAnyLinkedDevice(context, recipient).successUi { isFriends -> + if (!isFriends && friendRequestStatus == LokiMessageFriendRequestStatus.NONE) { + messageDatabase.setFriendRequestStatus(messageId, LokiMessageFriendRequestStatus.REQUEST_SENDING) + } } } else if (friendRequestStatus != LokiMessageFriendRequestStatus.NONE) { // Update the friend request status of the message if we have it @@ -70,23 +73,25 @@ object FriendRequestHandler { // We only want to update the last message status if we're not friends with any of their linked devices // This ensures that we don't spam the UI with accept/decline messages val recipient = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(threadId) ?: return - if (isFriendsWithAnyLinkedDevice(context, recipient)) { return } + isFriendsWithAnyLinkedDevice(context, recipient).successUi { isFriends -> + if (isFriends) { return@successUi } - // Since messages are forwarded to the primary device thread, we need to update it there - val messageCount = smsMessageDatabase.getMessageCountForThread(threadId) - val messageID = smsMessageDatabase.getIDForMessageAtIndex(threadId, messageCount - 1) // The message that was just received - if (messageID < 0) { return } + // Since messages are forwarded to the primary device thread, we need to update it there + val messageCount = smsMessageDatabase.getMessageCountForThread(threadId) + val messageID = smsMessageDatabase.getIDForMessageAtIndex(threadId, messageCount - 1) // The message that was just received + if (messageID < 0) { return@successUi } - val messageDatabase = DatabaseFactory.getLokiMessageDatabase(context) + val messageDatabase = DatabaseFactory.getLokiMessageDatabase(context) - // We need to go through and set all messages which are REQUEST_PENDING to NONE - smsMessageDatabase.getAllMessageIDs(threadId) - .filter { messageDatabase.getFriendRequestStatus(it) == LokiMessageFriendRequestStatus.REQUEST_PENDING } - .forEach { - messageDatabase.setFriendRequestStatus(it, LokiMessageFriendRequestStatus.NONE) - } + // We need to go through and set all messages which are REQUEST_PENDING to NONE + smsMessageDatabase.getAllMessageIDs(threadId) + .filter { messageDatabase.getFriendRequestStatus(it) == LokiMessageFriendRequestStatus.REQUEST_PENDING } + .forEach { + messageDatabase.setFriendRequestStatus(it, LokiMessageFriendRequestStatus.NONE) + } - // Set the last message to pending - messageDatabase.setFriendRequestStatus(messageID, LokiMessageFriendRequestStatus.REQUEST_PENDING) + // Set the last message to pending + messageDatabase.setFriendRequestStatus(messageID, LokiMessageFriendRequestStatus.REQUEST_PENDING) + } } } \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt index ccee4f7c2c..f80c89c75b 100644 --- a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt +++ b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt @@ -3,9 +3,9 @@ package org.thoughtcrime.securesms.loki import android.content.Context import android.os.Handler -import nl.komponents.kovenant.Promise +import nl.komponents.kovenant.* +import nl.komponents.kovenant.functional.bind import nl.komponents.kovenant.functional.map -import nl.komponents.kovenant.toFailVoid import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.database.Address @@ -20,28 +20,26 @@ import org.whispersystems.signalservice.api.push.SignalServiceAddress import org.whispersystems.signalservice.loki.api.LokiStorageAPI import org.whispersystems.signalservice.loki.api.PairingAuthorisation import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus +import org.whispersystems.signalservice.loki.utilities.recover import org.whispersystems.signalservice.loki.utilities.retryIfNeeded -/* - All functions within this class, excluding the ones which return promises, BLOCK the thread! Don't run them on the main thread! - */ - -fun getAllDeviceFriendRequestStatuses(context: Context, hexEncodedPublicKey: String): Map { +fun getAllDeviceFriendRequestStatuses(context: Context, hexEncodedPublicKey: String): Promise, Exception> { val lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context) - val keys = LokiStorageAPI.shared.getAllDevicePublicKeys(hexEncodedPublicKey) - val map = mutableMapOf() - for (devicePublicKey in keys) { - val device = Recipient.from(context, Address.fromSerialized(devicePublicKey), false) - val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(device) - val friendRequestStatus = if (threadID < 0) LokiThreadFriendRequestStatus.NONE else lokiThreadDatabase.getFriendRequestStatus(threadID) - map[devicePublicKey] = friendRequestStatus - } - return map + return LokiStorageAPI.shared.getAllDevicePublicKeys(hexEncodedPublicKey).map { keys -> + val map = mutableMapOf() + for (devicePublicKey in keys) { + val device = Recipient.from(context, Address.fromSerialized(devicePublicKey), false) + val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(device) + val friendRequestStatus = if (threadID < 0) LokiThreadFriendRequestStatus.NONE else lokiThreadDatabase.getFriendRequestStatus(threadID) + map[devicePublicKey] = friendRequestStatus + } + map + }.recover { mutableMapOf() } } fun getAllDevicePublicKeysWithFriendStatus(context: Context, hexEncodedPublicKey: String): Promise, Unit> { val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context) - return LokiStorageAPI.shared.getAllDevicePublicKeysAsync(hexEncodedPublicKey).map { keys -> + return LokiStorageAPI.shared.getAllDevicePublicKeys(hexEncodedPublicKey).map { keys -> val devices = keys.toMutableSet() if (hexEncodedPublicKey != userHexEncodedPublicKey) { devices.remove(userHexEncodedPublicKey) @@ -59,22 +57,31 @@ fun getFriendCount(context: Context, devices: Set): Int { return getFriendPublicKeys(context, devices).count() } -fun shouldAutomaticallyBecomeFriendsWithDevice(publicKey: String, context: Context): Boolean { +fun shouldAutomaticallyBecomeFriendsWithDevice(publicKey: String, context: Context): Promise { val lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context) val storageAPI = LokiStorageAPI.shared - // If the public key doesn't have any other devices then go through regular friend request logic - val primaryDevicePublicKey = storageAPI.getPrimaryDevicePublicKey(publicKey) ?: return false - - // If this is one of our devices then we should become friends - if (isOneOfOurDevices(context, publicKey)) { - return true + // If this public key is our primary device then we should become friends + if (publicKey == TextSecurePreferences.getMasterHexEncodedPublicKey(context)) { + return Promise.of(true) } - // If we are friends with the primary device then we should become friends - val primaryDevice = Recipient.from(context, Address.fromSerialized(primaryDevicePublicKey), false) - val primaryDeviceThreadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(primaryDevice) - return primaryDeviceThreadID >= 0 && lokiThreadDatabase.getFriendRequestStatus(primaryDeviceThreadID) == LokiThreadFriendRequestStatus.FRIENDS + return storageAPI.getPrimaryDevicePublicKey(publicKey).map { primaryDevicePublicKey -> + // If the public key doesn't have any other devices then go through regular friend request logic + if (primaryDevicePublicKey == null) { + return@map false + } + + // If the primary device public key matches our primary device then we should become friends since this is our other device + if (primaryDevicePublicKey == TextSecurePreferences.getMasterHexEncodedPublicKey(context)) { + return@map true + } + + // If we are friends with the primary device then we should become friends + val primaryDevice = Recipient.from(context, Address.fromSerialized(primaryDevicePublicKey), false) + val primaryDeviceThreadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(primaryDevice) + primaryDeviceThreadID >= 0 && lokiThreadDatabase.getFriendRequestStatus(primaryDeviceThreadID) == LokiThreadFriendRequestStatus.FRIENDS + } } fun sendPairingAuthorisationMessage(context: Context, contactHexEncodedPublicKey: String, authorisation: PairingAuthorisation): Promise { @@ -125,37 +132,55 @@ fun signAndSendPairingAuthorisationMessage(context: Context, pairingAuthorisatio } -fun shouldSendSycMessage(context: Context, address: Address): Boolean { +fun shouldSendSycMessage(context: Context, address: Address): Promise { if (address.isGroup || address.isEmail || address.isMmsGroup) { - return false + return Promise.of(false) } // Don't send sync messages if it's one of our devices - return !isOneOfOurDevices(context, address) + return isOneOfOurDevices(context, address).map { !it } } -fun isOneOfOurDevices(context: Context, publicKey: String): Boolean { - return isOneOfOurDevices(context, Address.fromSerialized(publicKey)) -} - -fun isOneOfOurDevices(context: Context, address: Address): Boolean { +fun isOneOfOurDevices(context: Context, address: Address): Promise { if (address.isGroup || address.isEmail || address.isMmsGroup) { - return false + return Promise.of(false) } val ourPublicKey = TextSecurePreferences.getLocalNumber(context) - val devices = LokiStorageAPI.shared.getAllDevicePublicKeys(ourPublicKey) - return devices.contains(address.serialize()) + return LokiStorageAPI.shared.getAllDevicePublicKeys(ourPublicKey).map { devices -> + devices.contains(address.serialize()) + } } -fun isFriendsWithAnyLinkedDevice(context: Context, recipient: Recipient): Boolean { - if (recipient.isGroupRecipient) return true +fun isFriendsWithAnyLinkedDevice(context: Context, recipient: Recipient): Promise { + if (recipient.isGroupRecipient) { return Promise.of(true) } - val map = getAllDeviceFriendRequestStatuses(context, recipient.address.serialize()) - for (status in map.values) { - if (status == LokiThreadFriendRequestStatus.FRIENDS) { - return true + return getAllDeviceFriendRequestStatuses(context, recipient.address.serialize()).map { map -> + for (status in map.values) { + if (status == LokiThreadFriendRequestStatus.FRIENDS) { + return@map true + } } + false } - return false -} \ No newline at end of file +} + +fun hasPendingFriendRequestWithAnyLinkedDevice(context: Context, recipient: Recipient): Promise { + if (recipient.isGroupRecipient) { return Promise.of(false) } + + return getAllDeviceFriendRequestStatuses(context, recipient.address.serialize()).map { map -> + for (status in map.values) { + if (status == LokiThreadFriendRequestStatus.REQUEST_SENDING || status == LokiThreadFriendRequestStatus.REQUEST_SENT || status == LokiThreadFriendRequestStatus.REQUEST_RECEIVED) { + return@map true + } + } + false + } +} + +fun shouldEnableUserInput(context: Context, recipient: Recipient): Promise { + // Input should be enabled if we don't have any pending requests OR we're friends with any linked device + return hasPendingFriendRequestWithAnyLinkedDevice(context, recipient).bind { hasPendingFriendRequest -> + if (!hasPendingFriendRequest) Promise.of(true) else isFriendsWithAnyLinkedDevice(context, recipient) + }.recover { true } +} diff --git a/src/org/thoughtcrime/securesms/sms/MessageSender.java b/src/org/thoughtcrime/securesms/sms/MessageSender.java index e15cda3de8..b0f77a6fe3 100644 --- a/src/org/thoughtcrime/securesms/sms/MessageSender.java +++ b/src/org/thoughtcrime/securesms/sms/MessageSender.java @@ -61,12 +61,15 @@ import org.whispersystems.signalservice.api.push.ContactTokenDetails; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.loki.api.LokiStorageAPI; import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus; +import org.whispersystems.signalservice.loki.utilities.PromiseUtil; import java.io.IOException; import java.util.Map; import java.util.Set; import kotlin.Unit; +import nl.komponents.kovenant.Kovenant; +import nl.komponents.kovenant.Promise; public class MessageSender { @@ -79,20 +82,24 @@ public class MessageSender { sendBackgroundMessage(context, contactHexEncodedPublicKey); // Go through the other devices and only send background messages if we're friends or we have received friend request - Set devices = LokiStorageAPI.shared.getAllDevicePublicKeys(contactHexEncodedPublicKey); - for (String device : devices) { - // Don't send message to the device we already have sent to - if (device.equals(contactHexEncodedPublicKey)) { continue; } - Recipient recipient = Recipient.from(context, Address.fromSerialized(device), false); - long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(recipient); - if (threadID < 0) { continue; } - LokiThreadFriendRequestStatus friendRequestStatus = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID); - if (friendRequestStatus == LokiThreadFriendRequestStatus.FRIENDS || friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_RECEIVED) { - sendBackgroundMessage(context, device); - } + LokiStorageAPI.shared.getAllDevicePublicKeys(contactHexEncodedPublicKey).success(devices -> { + Util.runOnMain(() -> { + for (String device : devices) { + // Don't send message to the device we already have sent to + if (device.equals(contactHexEncodedPublicKey)) { continue; } + Recipient recipient = Recipient.from(context, Address.fromSerialized(device), false); + long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(recipient); + if (threadID < 0) { continue; } + LokiThreadFriendRequestStatus friendRequestStatus = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID); + if (friendRequestStatus == LokiThreadFriendRequestStatus.FRIENDS || friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_RECEIVED) { + sendBackgroundMessage(context, device); + } - // TODO: Do we want to send a custom FR Message if we're not friends and we haven't received a friend request? - } + // TODO: Do we want to send a custom FR Message if we're not friends and we haven't received a friend request? + } + }); + return Unit.INSTANCE; + }); } public static void sendBackgroundMessage(Context context, String contactHexEncodedPublicKey) { @@ -199,15 +206,19 @@ public class MessageSender { final int ttl) { String ourPublicKey = TextSecurePreferences.getLocalNumber(context); JobManager jobManager = ApplicationContext.getInstance(context).getJobManager(); - Set devices = LokiStorageAPI.shared.getAllDevicePublicKeys(ourPublicKey); - for (String device : devices) { - // Don't send to ourselves - if (device.equals(ourPublicKey)) { continue; } + LokiStorageAPI.shared.getAllDevicePublicKeys(ourPublicKey).success(devices -> { + Util.runOnMain(() -> { + for (String device : devices) { + // Don't send to ourselves + if (device.equals(ourPublicKey)) { continue; } - // Create a send job for our device - Address address = Address.fromSerialized(device); - jobManager.add(new PushMessageSyncSendJob(messageID, address, timestamp, message, ttl)); - } + // Create a send job for our device + Address address = Address.fromSerialized(device); + jobManager.add(new PushMessageSyncSendJob(messageID, address, timestamp, message, ttl)); + } + }); + return Unit.INSTANCE; + }); } public static void resendGroupMessage(Context context, MessageRecord messageRecord, Address filterAddress) { @@ -267,9 +278,8 @@ public class MessageSender { 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 - // TODO: Test how badly this will block the thread String recipientPublicKey = recipient.getAddress().serialize(); - if (GeneralUtilitiesKt.isPublicChat(context, recipientPublicKey) || MultiDeviceUtilities.isOneOfOurDevices(context, recipient.getAddress())) { + if (GeneralUtilitiesKt.isPublicChat(context, recipientPublicKey) || PromiseUtil.get(MultiDeviceUtilities.isOneOfOurDevices(context, recipient.getAddress()), false)) { if (type == MessageType.MEDIA) { PushMediaSendJob.enqueue(context, jobManager, messageId, recipient.getAddress(), false); } else { @@ -292,13 +302,24 @@ public class MessageSender { 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] && MultiDeviceUtilities.shouldSendSycMessage(context, address); - if (type == MessageType.MEDIA) { - PushMediaSendJob.enqueue(context, jobManager, messageId, messageIDToUse, address, shouldSendSyncMessage); - } else { - jobManager.add(new PushTextSendJob(messageId, messageIDToUse, address, shouldSendSyncMessage)); + Promise promise = Promise.Companion.of(false, Kovenant.INSTANCE.getContext()); + if (!hasSentSyncMessage[0]) { + promise = MultiDeviceUtilities.shouldSendSycMessage(context, address).success(value -> { + hasSentSyncMessage[0] = value; + return Unit.INSTANCE; + }); } - hasSentSyncMessage[0] = shouldSendSyncMessage; + + promise.success(shouldSendSyncMessage -> { + Util.runOnMain(() -> { + if (type == MessageType.MEDIA) { + PushMediaSendJob.enqueue(context, jobManager, messageId, messageIDToUse, address, shouldSendSyncMessage); + } else { + jobManager.add(new PushTextSendJob(messageId, messageIDToUse, address, shouldSendSyncMessage)); + } + }); + return Unit.INSTANCE; + }); } 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.