Use promises instead of blocking the thread.

This commit is contained in:
Mikunj 2019-11-01 12:15:40 +11:00
parent bdc0ed36eb
commit 789aa244b5
9 changed files with 182 additions and 127 deletions

View File

@ -91,7 +91,7 @@ public class TypingStatusSender {
ApplicationContext.getInstance(context).getJobManager().add(new TypingSendJob(threadId, typingStarted)); ApplicationContext.getInstance(context).getJobManager().add(new TypingSendJob(threadId, typingStarted));
return; return;
} }
LokiStorageAPI.shared.getAllDevicePublicKeysAsync(recipient.getAddress().serialize()).success(devices -> { LokiStorageAPI.shared.getAllDevicePublicKeys(recipient.getAddress().serialize()).success(devices -> {
for (String device : devices) { for (String device : devices) {
Recipient deviceRecipient = Recipient.from(context, Address.fromSerialized(device), false); Recipient deviceRecipient = Recipient.from(context, Address.fromSerialized(device), false);
long deviceThreadID = threadDatabase.getThreadIdIfExistsFor(deviceRecipient); long deviceThreadID = threadDatabase.getThreadIdIfExistsFor(deviceRecipient);

View File

@ -127,6 +127,7 @@ import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher;
import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable; import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable;
import org.thoughtcrime.securesms.crypto.SecurityEvent; import org.thoughtcrime.securesms.crypto.SecurityEvent;
import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.Database;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.DraftDatabase; import org.thoughtcrime.securesms.database.DraftDatabase;
import org.thoughtcrime.securesms.database.DraftDatabase.Draft; import org.thoughtcrime.securesms.database.DraftDatabase.Draft;
@ -2192,7 +2193,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
if (threadID != this.threadId) { if (threadID != this.threadId) {
Recipient threadRecipient = DatabaseFactory.getThreadDatabase(this).getRecipientForThreadId(threadID); Recipient threadRecipient = DatabaseFactory.getThreadDatabase(this).getRecipientForThreadId(threadID);
if (threadRecipient != null && !threadRecipient.isGroupRecipient()) { 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 // We should update our input if this thread is a part of the other threads device
if (devices.contains(recipient.getAddress().serialize())) { if (devices.contains(recipient.getAddress().serialize())) {
this.updateInputPanel(); this.updateInputPanel();
@ -2212,10 +2213,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
return; return;
} }
task(() -> { // It could take a while before our promise resolves, so we assume the best case
// Run the functions below in a background thread since they are blocking LokiThreadFriendRequestStatus friendRequestStatus = DatabaseFactory.getLokiThreadDatabase(this).getFriendRequestStatus(threadId);
return MultiDeviceUtilities.isFriendsWithAnyLinkedDevice(this, recipient) || !hasPendingFriendRequestWithAnyLinkedDevice(); boolean isPending = friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_SENDING || friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_SENT || friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_RECEIVED;
}).success(shouldEnableInput -> { setInputPanelEnabled(!isPending);
// This promise correctly updates the UI for multidevice
MultiDeviceUtilities.shouldEnableUserInput(this, recipient).success(shouldEnableInput -> {
setInputPanelEnabled(shouldEnableInput); setInputPanelEnabled(shouldEnableInput);
return Unit.INSTANCE; return Unit.INSTANCE;
}); });
@ -3054,20 +3058,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
updateInputPanel(); updateInputPanel();
} }
public boolean hasPendingFriendRequestWithAnyLinkedDevice() {
if (recipient.isGroupRecipient()) return false;
// This call will block the thread that is being run on! be careful
Map<String, LokiThreadFriendRequestStatus> 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() { public boolean isNoteToSelf() {
return TextSecurePreferences.getLocalNumber(this).equalsIgnoreCase(recipient.getAddress().serialize()); return TextSecurePreferences.getLocalNumber(this).equalsIgnoreCase(recipient.getAddress().serialize());
} }

View File

@ -134,6 +134,7 @@ import org.whispersystems.signalservice.loki.messaging.LokiMessageFriendRequestS
import org.whispersystems.signalservice.loki.messaging.LokiServiceMessage; import org.whispersystems.signalservice.loki.messaging.LokiServiceMessage;
import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus; import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus;
import org.whispersystems.signalservice.loki.messaging.LokiThreadSessionResetStatus; import org.whispersystems.signalservice.loki.messaging.LokiThreadSessionResetStatus;
import org.whispersystems.signalservice.loki.utilities.PromiseUtil;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.SecureRandom; 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. // it must be a friend request accepted message. Declining a friend request doesn't send a message.
lokiThreadDatabase.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.FRIENDS); lokiThreadDatabase.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.FRIENDS);
// Update the last message if needed // Update the last message if needed
String primaryDevice = LokiStorageAPI.shared.getPrimaryDevicePublicKey(pubKey); LokiStorageAPI.shared.getPrimaryDevicePublicKey(pubKey).success(primaryDevice -> {
long primaryDeviceThreadID = primaryDevice == null ? threadID : DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, Address.fromSerialized(primaryDevice), false)); Util.runOnMain(() -> {
FriendRequestHandler.updateLastFriendRequestMessage(context, primaryDeviceThreadID, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED); 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) { private void updateFriendRequestStatusIfNeeded(@NonNull SignalServiceEnvelope envelope, @NonNull SignalServiceContent content, @NonNull SignalServiceDataMessage message) {
if (!envelope.isFriendRequest() || isGroupChatMessage(message)) { return; } if (!envelope.isFriendRequest() || isGroupChatMessage(message)) { return; }
// This handles the case where another user sends us a regular message without authorisation // 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) { if (shouldBecomeFriends) {
// Become friends AND update the message they sent // Become friends AND update the message they sent
becomeFriendsWithContact(content.getSender()); becomeFriendsWithContact(content.getSender());
@ -1113,9 +1118,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
Recipient originalRecipient = getMessageDestination(content, message); Recipient originalRecipient = getMessageDestination(content, message);
Recipient primaryDeviceRecipient = getMessagePrimaryDestination(content, message); Recipient primaryDeviceRecipient = getMessagePrimaryDestination(content, message);
LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context); 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 threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(originalRecipient);
long primaryDeviceThreadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(primaryDeviceRecipient); long primaryDeviceThreadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(primaryDeviceRecipient);
@ -1532,7 +1534,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
private Recipient getPrimaryDeviceRecipient(String recipient) { private Recipient getPrimaryDeviceRecipient(String recipient) {
try { try {
String primaryDevice = LokiStorageAPI.shared.getPrimaryDevicePublicKey(recipient); String primaryDevice = LokiStorageAPI.shared.getPrimaryDevicePublicKey(recipient).get();
String publicKey = (primaryDevice != null) ? primaryDevice : recipient; 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) // 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); String ourPrimaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
@ -1593,10 +1595,16 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
} else if (content.getCallMessage().isPresent() || content.getTypingMessage().isPresent()) { } else if (content.getCallMessage().isPresent() || content.getTypingMessage().isPresent()) {
return sender.isBlocked(); return sender.isBlocked();
} else if (content.getSyncMessage().isPresent()) { } else if (content.getSyncMessage().isPresent()) {
// We should ignore a sync message if the sender is not one of our devices try {
boolean isOurDevice = MultiDeviceUtilities.isOneOfOurDevices(context, sender.getAddress()); // We should ignore a sync message if the sender is not one of our devices
if (!isOurDevice) { Log.w(TAG, "Got a sync message from a device that is not ours!."); } boolean isOurDevice = MultiDeviceUtilities.isOneOfOurDevices(context, sender.getAddress()).get();
return !isOurDevice; 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; return false;

View File

@ -45,6 +45,7 @@ import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException; import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
import org.whispersystems.signalservice.loki.api.LokiStorageAPI; import org.whispersystems.signalservice.loki.api.LokiStorageAPI;
import org.whispersystems.signalservice.loki.messaging.LokiSyncMessage; import org.whispersystems.signalservice.loki.messaging.LokiSyncMessage;
import org.whispersystems.signalservice.loki.utilities.PromiseUtil;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
@ -281,7 +282,7 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
LokiSyncMessage syncMessage = null; LokiSyncMessage syncMessage = null;
if (shouldSendSyncMessage) { 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 // 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); SignalServiceAddress primaryAddress = primaryDevice == null ? address : new SignalServiceAddress(primaryDevice);
// We also need to use the original message id and not -1 // We also need to use the original message id and not -1
syncMessage = new LokiSyncMessage(primaryAddress, templateMessageId); syncMessage = new LokiSyncMessage(primaryAddress, templateMessageId);

View File

@ -32,6 +32,7 @@ import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException; import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
import org.whispersystems.signalservice.loki.api.LokiStorageAPI; import org.whispersystems.signalservice.loki.api.LokiStorageAPI;
import org.whispersystems.signalservice.loki.messaging.LokiSyncMessage; import org.whispersystems.signalservice.loki.messaging.LokiSyncMessage;
import org.whispersystems.signalservice.loki.utilities.PromiseUtil;
import java.io.IOException; import java.io.IOException;
@ -232,7 +233,7 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
LokiSyncMessage syncMessage = null; LokiSyncMessage syncMessage = null;
if (shouldSendSyncMessage) { 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 // 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); SignalServiceAddress primaryAddress = primaryDevice == null ? address : new SignalServiceAddress(primaryDevice);
// We also need to use the original message id and not -1 // We also need to use the original message id and not -1
syncMessage = new LokiSyncMessage(primaryAddress, templateMessageId); syncMessage = new LokiSyncMessage(primaryAddress, templateMessageId);

View File

@ -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;
import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage.Action; import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage.Action;
import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.loki.utilities.PromiseUtil;
import java.util.Collections; import java.util.Collections;
import java.util.List; 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); SignalServiceTypingMessage typingMessage = new SignalServiceTypingMessage(typing ? Action.STARTED : Action.STOPPED, System.currentTimeMillis(), groupId);
// Loki - Don't send typing indicators in group chats or to ourselves // 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); messageSender.sendTyping(0, addresses, unidentifiedAccess, typingMessage);
} }
} }

View File

@ -5,6 +5,7 @@ import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.ui.alwaysUi import nl.komponents.kovenant.ui.alwaysUi
import nl.komponents.kovenant.ui.successUi import nl.komponents.kovenant.ui.successUi
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.loki.api.LokiStorageAPI import org.whispersystems.signalservice.loki.api.LokiStorageAPI
import org.whispersystems.signalservice.loki.messaging.LokiMessageFriendRequestStatus import org.whispersystems.signalservice.loki.messaging.LokiMessageFriendRequestStatus
import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus
@ -34,8 +35,10 @@ object FriendRequestHandler {
if (type == ActionType.Sending) { if (type == ActionType.Sending) {
// We only want to update message status if we aren't friends with another of their devices // 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 // 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) { isFriendsWithAnyLinkedDevice(context, recipient).successUi { isFriends ->
messageDatabase.setFriendRequestStatus(messageId, LokiMessageFriendRequestStatus.REQUEST_SENDING) if (!isFriends && friendRequestStatus == LokiMessageFriendRequestStatus.NONE) {
messageDatabase.setFriendRequestStatus(messageId, LokiMessageFriendRequestStatus.REQUEST_SENDING)
}
} }
} else if (friendRequestStatus != LokiMessageFriendRequestStatus.NONE) { } else if (friendRequestStatus != LokiMessageFriendRequestStatus.NONE) {
// Update the friend request status of the message if we have it // 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 // 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 // This ensures that we don't spam the UI with accept/decline messages
val recipient = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(threadId) ?: return 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 // Since messages are forwarded to the primary device thread, we need to update it there
val messageCount = smsMessageDatabase.getMessageCountForThread(threadId) val messageCount = smsMessageDatabase.getMessageCountForThread(threadId)
val messageID = smsMessageDatabase.getIDForMessageAtIndex(threadId, messageCount - 1) // The message that was just received val messageID = smsMessageDatabase.getIDForMessageAtIndex(threadId, messageCount - 1) // The message that was just received
if (messageID < 0) { return } 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 // We need to go through and set all messages which are REQUEST_PENDING to NONE
smsMessageDatabase.getAllMessageIDs(threadId) smsMessageDatabase.getAllMessageIDs(threadId)
.filter { messageDatabase.getFriendRequestStatus(it) == LokiMessageFriendRequestStatus.REQUEST_PENDING } .filter { messageDatabase.getFriendRequestStatus(it) == LokiMessageFriendRequestStatus.REQUEST_PENDING }
.forEach { .forEach {
messageDatabase.setFriendRequestStatus(it, LokiMessageFriendRequestStatus.NONE) messageDatabase.setFriendRequestStatus(it, LokiMessageFriendRequestStatus.NONE)
} }
// Set the last message to pending // Set the last message to pending
messageDatabase.setFriendRequestStatus(messageID, LokiMessageFriendRequestStatus.REQUEST_PENDING) messageDatabase.setFriendRequestStatus(messageID, LokiMessageFriendRequestStatus.REQUEST_PENDING)
}
} }
} }

View File

@ -3,9 +3,9 @@ package org.thoughtcrime.securesms.loki
import android.content.Context import android.content.Context
import android.os.Handler 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.functional.map
import nl.komponents.kovenant.toFailVoid
import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
import org.thoughtcrime.securesms.database.Address 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.LokiStorageAPI
import org.whispersystems.signalservice.loki.api.PairingAuthorisation import org.whispersystems.signalservice.loki.api.PairingAuthorisation
import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus
import org.whispersystems.signalservice.loki.utilities.recover
import org.whispersystems.signalservice.loki.utilities.retryIfNeeded import org.whispersystems.signalservice.loki.utilities.retryIfNeeded
/* fun getAllDeviceFriendRequestStatuses(context: Context, hexEncodedPublicKey: String): Promise<Map<String, LokiThreadFriendRequestStatus>, Exception> {
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<String, LokiThreadFriendRequestStatus> {
val lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context) val lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context)
val keys = LokiStorageAPI.shared.getAllDevicePublicKeys(hexEncodedPublicKey) return LokiStorageAPI.shared.getAllDevicePublicKeys(hexEncodedPublicKey).map { keys ->
val map = mutableMapOf<String, LokiThreadFriendRequestStatus>() val map = mutableMapOf<String, LokiThreadFriendRequestStatus>()
for (devicePublicKey in keys) { for (devicePublicKey in keys) {
val device = Recipient.from(context, Address.fromSerialized(devicePublicKey), false) val device = Recipient.from(context, Address.fromSerialized(devicePublicKey), false)
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(device) val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(device)
val friendRequestStatus = if (threadID < 0) LokiThreadFriendRequestStatus.NONE else lokiThreadDatabase.getFriendRequestStatus(threadID) val friendRequestStatus = if (threadID < 0) LokiThreadFriendRequestStatus.NONE else lokiThreadDatabase.getFriendRequestStatus(threadID)
map[devicePublicKey] = friendRequestStatus map[devicePublicKey] = friendRequestStatus
} }
return map map
}.recover { mutableMapOf() }
} }
fun getAllDevicePublicKeysWithFriendStatus(context: Context, hexEncodedPublicKey: String): Promise<Map<String, Boolean>, Unit> { fun getAllDevicePublicKeysWithFriendStatus(context: Context, hexEncodedPublicKey: String): Promise<Map<String, Boolean>, Unit> {
val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context) val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
return LokiStorageAPI.shared.getAllDevicePublicKeysAsync(hexEncodedPublicKey).map { keys -> return LokiStorageAPI.shared.getAllDevicePublicKeys(hexEncodedPublicKey).map { keys ->
val devices = keys.toMutableSet() val devices = keys.toMutableSet()
if (hexEncodedPublicKey != userHexEncodedPublicKey) { if (hexEncodedPublicKey != userHexEncodedPublicKey) {
devices.remove(userHexEncodedPublicKey) devices.remove(userHexEncodedPublicKey)
@ -59,22 +57,31 @@ fun getFriendCount(context: Context, devices: Set<String>): Int {
return getFriendPublicKeys(context, devices).count() return getFriendPublicKeys(context, devices).count()
} }
fun shouldAutomaticallyBecomeFriendsWithDevice(publicKey: String, context: Context): Boolean { fun shouldAutomaticallyBecomeFriendsWithDevice(publicKey: String, context: Context): Promise<Boolean, Exception> {
val lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context) val lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context)
val storageAPI = LokiStorageAPI.shared val storageAPI = LokiStorageAPI.shared
// If the public key doesn't have any other devices then go through regular friend request logic // If this public key is our primary device then we should become friends
val primaryDevicePublicKey = storageAPI.getPrimaryDevicePublicKey(publicKey) ?: return false if (publicKey == TextSecurePreferences.getMasterHexEncodedPublicKey(context)) {
return Promise.of(true)
// If this is one of our devices then we should become friends
if (isOneOfOurDevices(context, publicKey)) {
return true
} }
// If we are friends with the primary device then we should become friends return storageAPI.getPrimaryDevicePublicKey(publicKey).map { primaryDevicePublicKey ->
val primaryDevice = Recipient.from(context, Address.fromSerialized(primaryDevicePublicKey), false) // If the public key doesn't have any other devices then go through regular friend request logic
val primaryDeviceThreadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(primaryDevice) if (primaryDevicePublicKey == null) {
return primaryDeviceThreadID >= 0 && lokiThreadDatabase.getFriendRequestStatus(primaryDeviceThreadID) == LokiThreadFriendRequestStatus.FRIENDS 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<Unit, Exception> { fun sendPairingAuthorisationMessage(context: Context, contactHexEncodedPublicKey: String, authorisation: PairingAuthorisation): Promise<Unit, Exception> {
@ -125,37 +132,55 @@ fun signAndSendPairingAuthorisationMessage(context: Context, pairingAuthorisatio
} }
fun shouldSendSycMessage(context: Context, address: Address): Boolean { fun shouldSendSycMessage(context: Context, address: Address): Promise<Boolean, Exception> {
if (address.isGroup || address.isEmail || address.isMmsGroup) { 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 // 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 { fun isOneOfOurDevices(context: Context, address: Address): Promise<Boolean, Exception> {
return isOneOfOurDevices(context, Address.fromSerialized(publicKey))
}
fun isOneOfOurDevices(context: Context, address: Address): Boolean {
if (address.isGroup || address.isEmail || address.isMmsGroup) { if (address.isGroup || address.isEmail || address.isMmsGroup) {
return false return Promise.of(false)
} }
val ourPublicKey = TextSecurePreferences.getLocalNumber(context) val ourPublicKey = TextSecurePreferences.getLocalNumber(context)
val devices = LokiStorageAPI.shared.getAllDevicePublicKeys(ourPublicKey) return LokiStorageAPI.shared.getAllDevicePublicKeys(ourPublicKey).map { devices ->
return devices.contains(address.serialize()) devices.contains(address.serialize())
}
} }
fun isFriendsWithAnyLinkedDevice(context: Context, recipient: Recipient): Boolean { fun isFriendsWithAnyLinkedDevice(context: Context, recipient: Recipient): Promise<Boolean, Exception> {
if (recipient.isGroupRecipient) return true if (recipient.isGroupRecipient) { return Promise.of(true) }
val map = getAllDeviceFriendRequestStatuses(context, recipient.address.serialize()) return getAllDeviceFriendRequestStatuses(context, recipient.address.serialize()).map { map ->
for (status in map.values) { for (status in map.values) {
if (status == LokiThreadFriendRequestStatus.FRIENDS) { if (status == LokiThreadFriendRequestStatus.FRIENDS) {
return true return@map true
}
} }
false
} }
return false }
}
fun hasPendingFriendRequestWithAnyLinkedDevice(context: Context, recipient: Recipient): Promise<Boolean, Exception> {
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<Boolean, Exception> {
// 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 }
}

View File

@ -61,12 +61,15 @@ import org.whispersystems.signalservice.api.push.ContactTokenDetails;
import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.loki.api.LokiStorageAPI; import org.whispersystems.signalservice.loki.api.LokiStorageAPI;
import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus; import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus;
import org.whispersystems.signalservice.loki.utilities.PromiseUtil;
import java.io.IOException; import java.io.IOException;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import kotlin.Unit; import kotlin.Unit;
import nl.komponents.kovenant.Kovenant;
import nl.komponents.kovenant.Promise;
public class MessageSender { public class MessageSender {
@ -79,20 +82,24 @@ public class MessageSender {
sendBackgroundMessage(context, contactHexEncodedPublicKey); sendBackgroundMessage(context, contactHexEncodedPublicKey);
// Go through the other devices and only send background messages if we're friends or we have received friend request // Go through the other devices and only send background messages if we're friends or we have received friend request
Set<String> devices = LokiStorageAPI.shared.getAllDevicePublicKeys(contactHexEncodedPublicKey); LokiStorageAPI.shared.getAllDevicePublicKeys(contactHexEncodedPublicKey).success(devices -> {
for (String device : devices) { Util.runOnMain(() -> {
// Don't send message to the device we already have sent to for (String device : devices) {
if (device.equals(contactHexEncodedPublicKey)) { continue; } // Don't send message to the device we already have sent to
Recipient recipient = Recipient.from(context, Address.fromSerialized(device), false); if (device.equals(contactHexEncodedPublicKey)) { continue; }
long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(recipient); Recipient recipient = Recipient.from(context, Address.fromSerialized(device), false);
if (threadID < 0) { continue; } long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(recipient);
LokiThreadFriendRequestStatus friendRequestStatus = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID); if (threadID < 0) { continue; }
if (friendRequestStatus == LokiThreadFriendRequestStatus.FRIENDS || friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_RECEIVED) { LokiThreadFriendRequestStatus friendRequestStatus = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID);
sendBackgroundMessage(context, device); 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) { public static void sendBackgroundMessage(Context context, String contactHexEncodedPublicKey) {
@ -199,15 +206,19 @@ public class MessageSender {
final int ttl) { final int ttl) {
String ourPublicKey = TextSecurePreferences.getLocalNumber(context); String ourPublicKey = TextSecurePreferences.getLocalNumber(context);
JobManager jobManager = ApplicationContext.getInstance(context).getJobManager(); JobManager jobManager = ApplicationContext.getInstance(context).getJobManager();
Set<String> devices = LokiStorageAPI.shared.getAllDevicePublicKeys(ourPublicKey); LokiStorageAPI.shared.getAllDevicePublicKeys(ourPublicKey).success(devices -> {
for (String device : devices) { Util.runOnMain(() -> {
// Don't send to ourselves for (String device : devices) {
if (device.equals(ourPublicKey)) { continue; } // Don't send to ourselves
if (device.equals(ourPublicKey)) { continue; }
// Create a send job for our device // Create a send job for our device
Address address = Address.fromSerialized(device); Address address = Address.fromSerialized(device);
jobManager.add(new PushMessageSyncSendJob(messageID, address, timestamp, message, ttl)); jobManager.add(new PushMessageSyncSendJob(messageID, address, timestamp, message, ttl));
} }
});
return Unit.INSTANCE;
});
} }
public static void resendGroupMessage(Context context, MessageRecord messageRecord, Address filterAddress) { public static void resendGroupMessage(Context context, MessageRecord messageRecord, Address filterAddress) {
@ -267,9 +278,8 @@ public class MessageSender {
JobManager jobManager = ApplicationContext.getInstance(context).getJobManager(); 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 // 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(); 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) { if (type == MessageType.MEDIA) {
PushMediaSendJob.enqueue(context, jobManager, messageId, recipient.getAddress(), false); PushMediaSendJob.enqueue(context, jobManager, messageId, recipient.getAddress(), false);
} else { } else {
@ -292,13 +302,24 @@ public class MessageSender {
if (isFriend) { if (isFriend) {
// Send a normal message if the user is friends with the recipient // 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 // We should also send a sync message if we haven't already sent one
boolean shouldSendSyncMessage = !hasSentSyncMessage[0] && MultiDeviceUtilities.shouldSendSycMessage(context, address); Promise<Boolean, Exception> promise = Promise.Companion.of(false, Kovenant.INSTANCE.getContext());
if (type == MessageType.MEDIA) { if (!hasSentSyncMessage[0]) {
PushMediaSendJob.enqueue(context, jobManager, messageId, messageIDToUse, address, shouldSendSyncMessage); promise = MultiDeviceUtilities.shouldSendSycMessage(context, address).success(value -> {
} else { hasSentSyncMessage[0] = value;
jobManager.add(new PushTextSendJob(messageId, messageIDToUse, address, shouldSendSyncMessage)); 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 { } else {
// Send friend requests to non friends. If the user is friends with any // Send friend requests to non friends. If the user is friends with any
// of the devices then send out a default friend request message. // of the devices then send out a default friend request message.