mirror of
https://github.com/oxen-io/session-android.git
synced 2025-02-17 17:28:26 +00:00
Use promises instead of blocking the thread.
This commit is contained in:
parent
bdc0ed36eb
commit
789aa244b5
@ -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);
|
||||||
|
@ -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());
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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 }
|
||||||
|
}
|
||||||
|
@ -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.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user