Simplify PushDecryptJob a bit

This commit is contained in:
Niels Andriesse 2020-02-13 10:18:05 +11:00
parent 27fdfe4ee8
commit 9c71a4c3cd

View File

@ -290,13 +290,14 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
}
// Loki - Handle friend request acceptance if needed
acceptFriendRequestIfNeeded(content);
if (!content.isFriendRequest() && !isGroupChatMessage(content)) {
becomeFriendsWithContactIfNeeded(content.getSender(), true, false);
}
// Loki - Session requests
// Loki - Handle session request if needed
handleSessionRequestIfNeeded(content);
// Loki - Store pre key bundle
// We shouldn't store it if it's a pairing message
// Loki - Store pre key bundle if needed
if (!content.getDeviceLink().isPresent()) {
storePreKeyBundleIfNeeded(content);
}
@ -311,29 +312,29 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
// Loki - Store the sender display name if needed
Optional<String> rawSenderDisplayName = content.senderDisplayName;
if (rawSenderDisplayName.isPresent() && rawSenderDisplayName.get().length() > 0) {
// If we got a name from our primary device then we set our profile name to match it
String ourPrimaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
if (ourPrimaryDevice != null && content.getSender().equals(ourPrimaryDevice)) {
// If we got a name from our master device then set our display name to match
String ourMasterDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
if (ourMasterDevice != null && content.getSender().equals(ourMasterDevice)) {
TextSecurePreferences.setProfileName(context, rawSenderDisplayName.get());
}
// If we receive a message from our device then don't set the display name in the database (as we probably have a alias set for them)
MultiDeviceUtilities.isOneOfOurDevices(context, Address.fromSerialized(content.getSender())).success(isOneOfOurDevice -> {
if (!isOneOfOurDevice) { setDisplayName(content.getSender(), rawSenderDisplayName.get()); }
MultiDeviceUtilities.isOneOfOurDevices(context, Address.fromSerialized(content.getSender())).success( isOneOfOurDevices -> {
if (!isOneOfOurDevices) { setDisplayName(content.getSender(), rawSenderDisplayName.get()); }
return Unit.INSTANCE;
});
}
if (content.getDeviceLink().isPresent()) {
handlePairingMessage(content.getDeviceLink().get(), content);
handleDeviceLinkMessage(content.getDeviceLink().get(), content);
} else if (content.getDataMessage().isPresent()) {
SignalServiceDataMessage message = content.getDataMessage().get();
boolean isMediaMessage = message.getAttachments().isPresent() || message.getQuote().isPresent() || message.getSharedContacts().isPresent() || message.getPreviews().isPresent() || message.getSticker().isPresent();
if (!content.isFriendRequest() && message.isUnlinkingRequest()) {
// Make sure we got the request from our primary device
String ourPrimaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
if (ourPrimaryDevice != null && ourPrimaryDevice.equals(content.getSender())) {
// Make sure we got the request from our master device
String ourMasterDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
if (ourMasterDevice != null && ourMasterDevice.equals(content.getSender())) {
TextSecurePreferences.setDatabaseResetFromUnpair(context, true);
MultiDeviceUtilities.checkForRevocation(context);
}
@ -376,7 +377,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
else if (syncMessage.getRead().isPresent()) handleSynchronizeReadMessage(syncMessage.getRead().get(), content.getTimestamp());
else if (syncMessage.getVerified().isPresent()) handleSynchronizeVerifiedMessage(syncMessage.getVerified().get());
else if (syncMessage.getStickerPackOperations().isPresent()) handleSynchronizeStickerPackOperation(syncMessage.getStickerPackOperations().get());
else if (syncMessage.getContacts().isPresent()) handleSynchronizeContactMessage(syncMessage.getContacts().get());
else if (syncMessage.getContacts().isPresent()) handleContactSyncMessage(syncMessage.getContacts().get());
else Log.w(TAG, "Contains no known sync types...");
} else if (content.getCallMessage().isPresent()) {
Log.i(TAG, "Got call message...");
@ -594,7 +595,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
{
GroupMessageProcessor.process(context, content, message, false);
if (message.getExpiresInSeconds() != 0 && message.getExpiresInSeconds() != getMessageDestination(content, message).getExpireMessages()) {
if (message.getExpiresInSeconds() != 0 && message.getExpiresInSeconds() != getRecipientForMessage(content, message).getExpireMessages()) {
handleExpirationUpdate(content, message, Optional.absent());
}
@ -620,7 +621,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
{
try {
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
Recipient recipient = getMessageDestination(content, message);
Recipient recipient = getRecipientForMessage(content, message);
IncomingMediaMessage mediaMessage = new IncomingMediaMessage(Address.fromSerialized(content.getSender()),
message.getTimestamp(), -1,
message.getExpiresInSeconds() * 1000L, true,
@ -671,45 +672,44 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
}
}
private void handleSynchronizeContactMessage(@NonNull ContactsMessage contactsMessage) {
if (contactsMessage.getContactsStream().isStream()) {
Log.d("Loki", "Received contact sync message");
private void handleContactSyncMessage(@NonNull ContactsMessage contactsMessage) {
if (!contactsMessage.getContactsStream().isStream()) { return; }
Log.d("Loki", "Received contact sync message.");
try {
InputStream in = contactsMessage.getContactsStream().asStream().getInputStream();
DeviceContactsInputStream contactsInputStream = new DeviceContactsInputStream(in);
List<DeviceContact> devices = contactsInputStream.readAll();
for (DeviceContact deviceContact : devices) {
// Check if we have the contact as a friend and that we're not trying to sync our own device
String pubKey = deviceContact.getNumber();
Address address = Address.fromSerialized(pubKey);
if (!address.isPhone() || address.toPhoneString().equals(TextSecurePreferences.getLocalNumber(context))) { continue; }
try {
InputStream in = contactsMessage.getContactsStream().asStream().getInputStream();
DeviceContactsInputStream contactsInputStream = new DeviceContactsInputStream(in);
List<DeviceContact> deviceContacts = contactsInputStream.readAll();
for (DeviceContact deviceContact : deviceContacts) {
// Check if we have the contact as a friend and that we're not trying to sync our own device
String hexEncodedPublicKey = deviceContact.getNumber();
Address address = Address.fromSerialized(hexEncodedPublicKey);
if (!address.isPhone() || address.toPhoneString().equals(TextSecurePreferences.getLocalNumber(context))) { continue; }
/*
If we're not friends with the contact we received or our friend request expired then we should send them a friend request
otherwise if we have received a friend request with from them then we should automatically accept the friend request
*/
Recipient recipient = Recipient.from(context, address, false);
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
LokiThreadFriendRequestStatus status = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadId);
if (status == LokiThreadFriendRequestStatus.NONE || status == LokiThreadFriendRequestStatus.REQUEST_EXPIRED) {
MessageSender.sendBackgroundFriendRequest(context, pubKey, "Please accept to enable messages to be synced across devices");
Log.d("Loki", "Sent friend request to " + pubKey);
} else if (status == LokiThreadFriendRequestStatus.REQUEST_RECEIVED) {
// Accept the incoming friend request
becomeFriendsWithContact(pubKey, false, false);
// Send them an accept message back
MessageSender.sendBackgroundMessage(context, pubKey);
Log.d("Loki", "Became friends with " + deviceContact.getNumber());
}
// TODO: Handle blocked - If user is not blocked then we should do the friend request logic otherwise add them to our block list
// TODO: Handle expiration timer - Update expiration timer?
// TODO: Handle avatar - Download and set avatar?
/*
If we're not friends with the contact we received or our friend request expired then we should send them a friend request.
Otherwise, if we have received a friend request from them, automatically accept the friend request.
*/
Recipient recipient = Recipient.from(context, address, false);
long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
LokiThreadFriendRequestStatus status = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID);
if (status == LokiThreadFriendRequestStatus.NONE || status == LokiThreadFriendRequestStatus.REQUEST_EXPIRED) {
MessageSender.sendBackgroundFriendRequest(context, hexEncodedPublicKey, "Please accept to enable messages to be synced across devices");
Log.d("Loki", "Sent friend request to " + hexEncodedPublicKey);
} else if (status == LokiThreadFriendRequestStatus.REQUEST_RECEIVED) {
// Accept the incoming friend request
becomeFriendsWithContactIfNeeded(hexEncodedPublicKey, false, false);
// Send them an accept message back
MessageSender.sendBackgroundMessage(context, hexEncodedPublicKey);
Log.d("Loki", "Became friends with " + deviceContact.getNumber());
}
} catch (Exception e) {
Log.d("Loki", "Failed to sync contact: " + e);
// TODO: Handle blocked - If user is not blocked then we should do the friend request logic otherwise add them to our block list
// TODO: Handle expiration timer - Update expiration timer?
// TODO: Handle avatar - Download and set avatar?
}
} catch (Exception e) {
Log.d("Loki", "Failed to sync contact: " + e + ".");
}
}
@ -752,7 +752,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true);
}
// Loki - If we received a sync message from our master device then we need to extract the avatar url
// Loki - If we received a sync message from our master device then we need to extract the profile picture url
if (isSenderMasterDevice) {
handleProfileKey(content, message.getMessage());
}
@ -842,21 +842,21 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
@NonNull Optional<Long> messageServerIDOrNull)
throws StorageFailedException
{
Recipient originalRecipient = getMessageDestination(content, message);
Recipient primaryDeviceRecipient = getMessagePrimaryDestination(content, message);
Recipient originalRecipient = getRecipientForMessage(content, message);
Recipient masterRecipient = getMasterRecipientForMessage(content, message);
notifyTypingStoppedFromIncomingMessage(primaryDeviceRecipient, content.getSender(), content.getSenderDevice());
notifyTypingStoppedFromIncomingMessage(masterRecipient, content.getSender(), content.getSenderDevice());
Optional<QuoteModel> quote = getValidatedQuote(message.getQuote());
Optional<List<Contact>> sharedContacts = getContacts(message.getSharedContacts());
Optional<List<LinkPreview>> linkPreviews = getLinkPreviews(message.getPreviews(), message.getBody().or(""));
Optional<Attachment> sticker = getStickerAttachment(message.getSticker());
Address sender = primaryDeviceRecipient.getAddress();
Address sender = masterRecipient.getAddress();
// If message is from group then we need to map it to get the sender of the message
if (message.isGroupMessage()) {
sender = getPrimaryDeviceRecipient(content.getSender()).getAddress();
sender = getMasterRecipient(content.getSender()).getAddress();
}
// Ignore messages from ourselves
@ -1031,8 +1031,8 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
{
SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
String body = message.getBody().isPresent() ? message.getBody().get() : "";
Recipient originalRecipient = getMessageDestination(content, message);
Recipient primaryDeviceRecipient = getMessagePrimaryDestination(content, message);
Recipient originalRecipient = getRecipientForMessage(content, message);
Recipient masterRecipient = getMasterRecipientForMessage(content, message);
if (message.getExpiresInSeconds() != originalRecipient.getExpireMessages()) {
handleExpirationUpdate(content, message, Optional.absent());
@ -1043,13 +1043,13 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
if (smsMessageId.isPresent() && !message.getGroupInfo().isPresent()) {
threadId = database.updateBundleMessageBody(smsMessageId.get(), body).second;
} else {
notifyTypingStoppedFromIncomingMessage(primaryDeviceRecipient, content.getSender(), content.getSenderDevice());
notifyTypingStoppedFromIncomingMessage(masterRecipient, content.getSender(), content.getSenderDevice());
Address sender = primaryDeviceRecipient.getAddress();
Address sender = masterRecipient.getAddress();
// If message is from group then we need to map it to get the sender of the message
if (message.isGroupMessage()) {
sender = getPrimaryDeviceRecipient(content.getSender()).getAddress();
sender = getMasterRecipient(content.getSender()).getAddress();
}
// Ignore messages from ourselves
@ -1103,7 +1103,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
}
}
private boolean isValidPairingMessage(@NonNull DeviceLink authorisation) {
private boolean isValidDeviceLinkMessage(@NonNull DeviceLink authorisation) {
boolean isSecondaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context) != null;
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context);
boolean isRequest = (authorisation.getType() == DeviceLink.Type.REQUEST);
@ -1123,74 +1123,66 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
return authorisation.verify();
}
private void handleProfileAvatar(SignalServiceContent content, String url) {
Recipient primaryDevice = getPrimaryDeviceRecipient(content.getSender());
ApplicationContext.getInstance(context).getJobManager().add(new RetrieveProfileAvatarJob(primaryDevice, url));
}
private void handlePairingMessage(@NonNull DeviceLink authorisation, @NonNull SignalServiceContent content) {
private void handleDeviceLinkMessage(@NonNull DeviceLink deviceLink, @NonNull SignalServiceContent content) {
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context);
if (authorisation.getType() == DeviceLink.Type.REQUEST) {
handlePairingRequestMessage(authorisation, content);
} else if (authorisation.getSlaveHexEncodedPublicKey().equals(userHexEncodedPublicKey)) {
handlePairingAuthorisationMessage(authorisation, content);
if (deviceLink.getType() == DeviceLink.Type.REQUEST) {
handleDeviceLinkRequestMessage(deviceLink, content);
} else if (deviceLink.getSlaveHexEncodedPublicKey().equals(userHexEncodedPublicKey)) {
handleDeviceLinkAuthorizedMessage(deviceLink, content);
}
}
private void handlePairingRequestMessage(@NonNull DeviceLink authorisation, @NonNull SignalServiceContent content) {
boolean isValid = isValidPairingMessage(authorisation);
private void handleDeviceLinkRequestMessage(@NonNull DeviceLink deviceLink, @NonNull SignalServiceContent content) {
boolean isValid = isValidDeviceLinkMessage(deviceLink);
DeviceLinkingSession linkingSession = DeviceLinkingSession.Companion.getShared();
if (isValid && linkingSession.isListeningForLinkingRequests()) {
// Loki - If we successfully received a request then we should store the PreKeyBundle
storePreKeyBundleIfNeeded(content);
linkingSession.processLinkingRequest(authorisation);
}
if (!isValid || !linkingSession.isListeningForLinkingRequests()) { return; }
storePreKeyBundleIfNeeded(content);
linkingSession.processLinkingRequest(deviceLink);
}
private void handlePairingAuthorisationMessage(@NonNull DeviceLink authorisation, @NonNull SignalServiceContent content) {
// Prepare
boolean isSecondaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context) != null;
if (isSecondaryDevice) {
Log.d("Loki", "Ignoring unexpected pairing authorisation message (the device is already paired as a secondary device).");
private void handleDeviceLinkAuthorizedMessage(@NonNull DeviceLink deviceLink, @NonNull SignalServiceContent content) {
// Check preconditions
boolean hasExistingDeviceLink = TextSecurePreferences.getMasterHexEncodedPublicKey(context) != null;
if (hasExistingDeviceLink) {
Log.d("Loki", "Ignoring unexpected device link message (the device is already linked as a slave device).");
return;
}
boolean isValid = isValidPairingMessage(authorisation);
boolean isValid = isValidDeviceLinkMessage(deviceLink);
if (!isValid) {
Log.d("Loki", "Ignoring invalid pairing authorisation message.");
Log.d("Loki", "Ignoring invalid device link message.");
return;
}
if (!DeviceLinkingSession.Companion.getShared().isListeningForLinkingRequests()) {
Log.d("Loki", "Ignoring pairing authorisation message.");
Log.d("Loki", "Ignoring device link message.");
return;
}
if (authorisation.getType() != DeviceLink.Type.AUTHORIZATION) { return; }
Log.d("Loki", "Received pairing authorisation message from: " + authorisation.getMasterHexEncodedPublicKey() + ".");
// Save PreKeyBundle if for whatever reason we got one
if (deviceLink.getType() != DeviceLink.Type.AUTHORIZATION) { return; }
Log.d("Loki", "Received device link authorized message from: " + deviceLink.getMasterHexEncodedPublicKey() + ".");
// Save pre key bundle if we somehow got one
storePreKeyBundleIfNeeded(content);
// Process
DeviceLinkingSession.Companion.getShared().processLinkingAuthorization(authorisation);
// Store the primary device's public key
DeviceLinkingSession.Companion.getShared().processLinkingAuthorization(deviceLink);
// Store the master device's ID
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context);
DatabaseFactory.getLokiAPIDatabase(context).clearDeviceLinks(userHexEncodedPublicKey);
DatabaseFactory.getLokiAPIDatabase(context).addDeviceLink(authorisation);
TextSecurePreferences.setMasterHexEncodedPublicKey(context, authorisation.getMasterHexEncodedPublicKey());
DatabaseFactory.getLokiAPIDatabase(context).addDeviceLink(deviceLink);
TextSecurePreferences.setMasterHexEncodedPublicKey(context, deviceLink.getMasterHexEncodedPublicKey());
TextSecurePreferences.setMultiDevice(context, true);
// Send a background message to the primary device
MessageSender.sendBackgroundMessage(context, authorisation.getMasterHexEncodedPublicKey());
// Send a background message to the master device
MessageSender.sendBackgroundMessage(context, deviceLink.getMasterHexEncodedPublicKey());
// Propagate the updates to the file server
LokiFileServerAPI storageAPI = LokiFileServerAPI.Companion.getShared();
storageAPI.updateUserDeviceLinks();
// Update display names
LokiFileServerAPI.Companion.getShared().updateUserDeviceLinks();
// Update display name if needed
if (content.senderDisplayName.isPresent() && content.senderDisplayName.get().length() > 0) {
TextSecurePreferences.setProfileName(context, content.senderDisplayName.get());
}
// Profile avatar updates
// Update profile picture if needed
if (content.getDataMessage().isPresent()) {
handleProfileKey(content, content.getDataMessage().get());
}
// Contact sync
// Handle contact sync if needed
if (content.getSyncMessage().isPresent() && content.getSyncMessage().get().getContacts().isPresent()) {
handleSynchronizeContactMessage(content.getSyncMessage().get().getContacts().get());
handleContactSyncMessage(content.getSyncMessage().get().getContacts().get());
}
}
@ -1200,95 +1192,79 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
}
private void updateGroupChatMessageServerID(Optional<Long> messageServerIDOrNull, Optional<InsertResult> insertResult) {
if (insertResult.isPresent() && messageServerIDOrNull.isPresent()) {
long messageID = insertResult.get().getMessageId();
long messageServerID = messageServerIDOrNull.get();
DatabaseFactory.getLokiMessageDatabase(context).setServerID(messageID, messageServerID);
}
if (!insertResult.isPresent() || !messageServerIDOrNull.isPresent()) { return; }
long messageID = insertResult.get().getMessageId();
long messageServerID = messageServerIDOrNull.get();
DatabaseFactory.getLokiMessageDatabase(context).setServerID(messageID, messageServerID);
}
private void storePreKeyBundleIfNeeded(@NonNull SignalServiceContent content) {
Recipient sender = Recipient.from(context, Address.fromSerialized(content.getSender()), false);
if (!sender.isGroupRecipient() && content.lokiServiceMessage.isPresent()) {
LokiServiceMessage lokiMessage = content.lokiServiceMessage.get();
if (lokiMessage.getPreKeyBundleMessage() != null) {
int registrationID = TextSecurePreferences.getLocalRegistrationId(context);
LokiPreKeyBundleDatabase lokiPreKeyBundleDatabase = DatabaseFactory.getLokiPreKeyBundleDatabase(context);
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context);
// Loki - Store the latest pre key bundle
if (registrationID > 0) {
Log.d("Loki", "Received a pre key bundle from: " + content.getSender() + ".");
PreKeyBundle preKeyBundle = lokiMessage.getPreKeyBundleMessage().getPreKeyBundle(registrationID);
lokiPreKeyBundleDatabase.setPreKeyBundle(content.getSender(), preKeyBundle);
// Loki - If we received a friend request, but we were already friends with this user, then reset the session
if (content.isFriendRequest()) {
long threadID = threadDatabase.getThreadIdIfExistsFor(sender);
if (lokiThreadDatabase.getFriendRequestStatus(threadID) == LokiThreadFriendRequestStatus.FRIENDS) {
resetSession(content.getSender(), threadID);
// Let our other devices know that we have reset the session
MessageSender.syncContact(context, sender.getAddress());
}
}
}
if (sender.isGroupRecipient() || !content.lokiServiceMessage.isPresent()) { return; }
LokiServiceMessage lokiMessage = content.lokiServiceMessage.get();
if (lokiMessage.getPreKeyBundleMessage() == null) { return; }
int registrationID = TextSecurePreferences.getLocalRegistrationId(context);
LokiPreKeyBundleDatabase lokiPreKeyBundleDatabase = DatabaseFactory.getLokiPreKeyBundleDatabase(context);
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context);
if (registrationID <= 0) { return; }
Log.d("Loki", "Received a pre key bundle from: " + content.getSender() + ".");
PreKeyBundle preKeyBundle = lokiMessage.getPreKeyBundleMessage().getPreKeyBundle(registrationID);
lokiPreKeyBundleDatabase.setPreKeyBundle(content.getSender(), preKeyBundle);
// If we received a friend request, but we were already friends with the user, reset the session
if (content.isFriendRequest()) {
long threadID = threadDatabase.getThreadIdIfExistsFor(sender);
if (lokiThreadDatabase.getFriendRequestStatus(threadID) == LokiThreadFriendRequestStatus.FRIENDS) {
resetSession(content.getSender(), threadID);
// Let our other devices know that we have reset the session
MessageSender.syncContact(context, sender.getAddress());
}
}
}
private void acceptFriendRequestIfNeeded(@NonNull SignalServiceContent content) {
// If we get anything other than a friend request, we can assume that we have a session with the other user
if (content.isFriendRequest() || isGroupChatMessage(content)) { return; }
becomeFriendsWithContact(content.getSender(), true, false);
}
private void handleSessionRequestIfNeeded(@NonNull SignalServiceContent content) {
if (content.isFriendRequest() && isSessionRequest(content)) {
// Check if the session request from a member in one of our groups or our friend
LokiDeviceLinkUtilities.INSTANCE.getMasterHexEncodedPublicKey(content.getSender()).success(primaryDevicePublicKey -> {
String sender = primaryDevicePublicKey != null ? primaryDevicePublicKey : content.getSender();
long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, Address.fromSerialized(sender), false));
LokiThreadFriendRequestStatus threadFriendRequestStatus = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID);
boolean isOurFriend = threadFriendRequestStatus == LokiThreadFriendRequestStatus.FRIENDS;
boolean isInOneOfOurGroups = DatabaseFactory.getGroupDatabase(context).signalGroupsHaveMember(sender);
boolean shouldAcceptSessionRequest = isOurFriend || isInOneOfOurGroups;
if (shouldAcceptSessionRequest) {
// Send a background message to acknowledge session request
MessageSender.sendBackgroundMessage(context, content.getSender());
}
return Unit.INSTANCE;
});
}
if (!content.isFriendRequest() || !isSessionRequest(content)) { return; }
// Check if the session request came from a member in one of our groups or one of our friends
LokiDeviceLinkUtilities.INSTANCE.getMasterHexEncodedPublicKey(content.getSender()).success( masterHexEncodedPublicKey -> {
String sender = masterHexEncodedPublicKey != null ? masterHexEncodedPublicKey : content.getSender();
long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, Address.fromSerialized(sender), false));
LokiThreadFriendRequestStatus threadFriendRequestStatus = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID);
boolean isOurFriend = threadFriendRequestStatus == LokiThreadFriendRequestStatus.FRIENDS;
boolean isInOneOfOurGroups = DatabaseFactory.getGroupDatabase(context).signalGroupsHaveMember(sender);
boolean shouldAcceptSessionRequest = isOurFriend || isInOneOfOurGroups;
if (shouldAcceptSessionRequest) {
MessageSender.sendBackgroundMessage(context, content.getSender()); // Send a background message to acknowledge
}
return Unit.INSTANCE;
});
}
private void becomeFriendsWithContact(String pubKey, boolean syncContact, boolean force) {
private void becomeFriendsWithContactIfNeeded(String hexEncodedPublicKey, boolean requiresContactSync, boolean canSkip) {
// Ignore friend requests to group recipients
LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context);
Recipient contactID = Recipient.from(context, Address.fromSerialized(pubKey), false);
Recipient contactID = Recipient.from(context, Address.fromSerialized(hexEncodedPublicKey), false);
if (contactID.isGroupRecipient()) return;
// Ignore friend requests to recipients we're already friends with
long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(contactID);
LokiThreadFriendRequestStatus threadFriendRequestStatus = lokiThreadDatabase.getFriendRequestStatus(threadID);
if (threadFriendRequestStatus == LokiThreadFriendRequestStatus.FRIENDS) { return; }
// We shouldn't be able to skip from None -> Friends in normal circumstances.
// Multi-device is the exception to this rule because we want to automatically be friends with a secondary device
if (!force && threadFriendRequestStatus == LokiThreadFriendRequestStatus.NONE) { return; }
// If the thread's friend request status is not `FRIENDS`, but we're receiving a message,
// We shouldn't be able to skip from NONE to FRIENDS under normal circumstances.
// Multi-device is the one exception to this rule because we want to automatically become friends with slave devices.
if (!canSkip && threadFriendRequestStatus == LokiThreadFriendRequestStatus.NONE) { return; }
// If the thread's friend request status is not `FRIENDS` or `NONE`, but we're receiving a message,
// it must be a friend request accepted message. Declining a friend request doesn't send a message.
lokiThreadDatabase.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.FRIENDS);
// Send out a contact sync message
if (syncContact) {
// Send out a contact sync message if needed
if (requiresContactSync) {
MessageSender.syncContact(context, contactID.getAddress());
}
// Allow profile sharing with contact
// Enable profile sharing with the recipient
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(contactID, true);
// Update the last message if needed
LokiDeviceLinkUtilities.INSTANCE.getMasterHexEncodedPublicKey(pubKey).success(primaryDevice -> {
LokiDeviceLinkUtilities.INSTANCE.getMasterHexEncodedPublicKey(hexEncodedPublicKey).success( masterHexEncodedPublicKey -> {
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);
long masterThreadID = (masterHexEncodedPublicKey == null) ? threadID : DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, Address.fromSerialized(masterHexEncodedPublicKey), false));
FriendRequestHandler.updateLastFriendRequestMessage(context, masterThreadID, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED);
});
return Unit.INSTANCE;
});
@ -1296,25 +1272,24 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
private void updateFriendRequestStatusIfNeeded(@NonNull SignalServiceContent content, @NonNull SignalServiceDataMessage message) {
if (!content.isFriendRequest() || message.isGroupMessage() || message.isSessionRequest()) { return; }
// This handles the case where another user sends us a regular message without authorisation
Promise<Boolean, Exception> promise = PromiseUtil.timeout(MultiDeviceUtilities.shouldAutomaticallyBecomeFriendsWithDevice(content.getSender(), context), 8000);
boolean shouldBecomeFriends = PromiseUtil.get(promise, false);
if (shouldBecomeFriends) {
// Become friends AND update the message they sent
becomeFriendsWithContact(content.getSender(), true, true);
becomeFriendsWithContactIfNeeded(content.getSender(), true, true);
// Send them an accept message back
MessageSender.sendBackgroundMessage(context, content.getSender());
} else {
// Do regular friend request logic checks
Recipient originalRecipient = getMessageDestination(content, message);
Recipient primaryDeviceRecipient = getMessagePrimaryDestination(content, message);
Recipient originalRecipient = getRecipientForMessage(content, message);
Recipient masterRecipient = getMasterRecipientForMessage(content, message);
LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context);
// Loki - Friend requests only work in direct chats
if (!originalRecipient.getAddress().isPhone()) { return; }
long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(originalRecipient);
long primaryDeviceThreadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(primaryDeviceRecipient);
long primaryDeviceThreadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(masterRecipient);
LokiThreadFriendRequestStatus threadFriendRequestStatus = lokiThreadDatabase.getFriendRequestStatus(threadID);
if (threadFriendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_SENT) {
@ -1483,7 +1458,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
}
private void triggerSessionRestorePrompt(@NonNull String sender) {
Recipient primaryRecipient = getPrimaryDeviceRecipient(sender);
Recipient primaryRecipient = getMasterRecipient(sender);
if (!primaryRecipient.isGroupRecipient()) {
long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(primaryRecipient);
DatabaseFactory.getLokiThreadDatabase(context).addSessionRestoreDevice(threadID, sender);
@ -1560,11 +1535,11 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
private void handleDeliveryReceipt(@NonNull SignalServiceContent content,
@NonNull SignalServiceReceiptMessage message)
{
// Redirect message to primary device conversation
// Redirect message to master device conversation
Address sender = Address.fromSerialized(content.getSender());
if (sender.isPhone()) {
Recipient primaryDevice = getPrimaryDeviceRecipient(content.getSender());
sender = primaryDevice.getAddress();
Recipient masterDevice = getMasterRecipient(content.getSender());
sender = masterDevice.getAddress();
}
for (long timestamp : message.getTimestamps()) {
@ -1580,11 +1555,11 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
{
if (TextSecurePreferences.isReadReceiptsEnabled(context)) {
// Redirect message to primary device conversation
// Redirect message to master device conversation
Address sender = Address.fromSerialized(content.getSender());
if (sender.isPhone()) {
Recipient primaryDevice = getPrimaryDeviceRecipient(content.getSender());
sender = primaryDevice.getAddress();
Recipient masterDevice = getMasterRecipient(content.getSender());
sender = masterDevice.getAddress();
}
for (long timestamp : message.getTimestamps()) {
@ -1615,7 +1590,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(groupRecipient);
} else {
// See if we need to redirect the message
author = getPrimaryDeviceRecipient(content.getSender());
author = getMasterRecipient(content.getSender());
threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(author);
}
@ -1749,9 +1724,9 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
}
private Optional<InsertResult> insertPlaceholder(@NonNull String sender, int senderDevice, long timestamp) {
Recipient primaryDevice = getPrimaryDeviceRecipient(sender);
SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
IncomingTextMessage textMessage = new IncomingTextMessage(primaryDevice.getAddress(),
Recipient masterDevice = getMasterRecipient(sender);
SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
IncomingTextMessage textMessage = new IncomingTextMessage(masterDevice.getAddress(),
senderDevice, timestamp, "",
Optional.absent(), 0, false);
@ -1771,11 +1746,11 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
if (message.getMessage().isGroupMessage()) {
return getSyncMessageDestination(message);
} else {
return getPrimaryDeviceRecipient(message.getDestination().get());
return getMasterRecipient(message.getDestination().get());
}
}
private Recipient getMessageDestination(SignalServiceContent content, SignalServiceDataMessage message) {
private Recipient getRecipientForMessage(SignalServiceContent content, SignalServiceDataMessage message) {
if (message.isGroupMessage()) {
return Recipient.from(context, Address.fromSerialized(GroupUtil.getEncodedId(message.getGroupInfo().get())), false);
} else {
@ -1783,34 +1758,34 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
}
}
private Recipient getMessagePrimaryDestination(SignalServiceContent content, SignalServiceDataMessage message) {
private Recipient getMasterRecipientForMessage(SignalServiceContent content, SignalServiceDataMessage message) {
if (message.isGroupMessage()) {
return getMessageDestination(content, message);
return getRecipientForMessage(content, message);
} else {
return getPrimaryDeviceRecipient(content.getSender());
return getMasterRecipient(content.getSender());
}
}
/**
* Get the primary device recipient of the passed in device.
* Get the master device recipient of the provided device.
*
* If the device doesn't have a primary device then it will return the same device.
* If the device is our primary device then it will return our current device.
* Otherwise it will return the primary device.
* If the device doesn't have a master device this will return the same device.
* If the device is our master device then it will return our current device.
* Otherwise it will return the master device.
*/
private Recipient getPrimaryDeviceRecipient(String pubKey) {
private Recipient getMasterRecipient(String hexEncodedPublicKey) {
try {
String primaryDevice = PromiseUtil.timeout(LokiDeviceLinkUtilities.INSTANCE.getMasterHexEncodedPublicKey(pubKey), 5000).get();
String publicKey = (primaryDevice != null) ? primaryDevice : pubKey;
// 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);
if (ourPrimaryDevice != null && ourPrimaryDevice.equals(publicKey)) {
publicKey = TextSecurePreferences.getLocalNumber(context);
String masterHexEncodedPublicKey = PromiseUtil.timeout(LokiDeviceLinkUtilities.INSTANCE.getMasterHexEncodedPublicKey(hexEncodedPublicKey), 5000).get();
String targetHexEncodedPublicKey = (masterHexEncodedPublicKey != null) ? masterHexEncodedPublicKey : hexEncodedPublicKey;
// If the public key matches our master device then we need to forward the message to ourselves (note to self)
String ourMasterHexEncodedPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
if (ourMasterHexEncodedPublicKey != null && ourMasterHexEncodedPublicKey.equals(targetHexEncodedPublicKey)) {
targetHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context);
}
return Recipient.from(context, Address.fromSerialized(publicKey), false);
return Recipient.from(context, Address.fromSerialized(targetHexEncodedPublicKey), false);
} catch (Exception e) {
Log.d("Loki", "Failed to get primary device public key for " + pubKey + ". " + e.getMessage());
return Recipient.from(context, Address.fromSerialized(pubKey), false);
Log.d("Loki", "Failed to get master device for: " + hexEncodedPublicKey + ". " + e.getMessage());
return Recipient.from(context, Address.fromSerialized(hexEncodedPublicKey), false);
}
}
@ -1836,7 +1811,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
return false;
} else if (content.getDataMessage().isPresent()) {
SignalServiceDataMessage message = content.getDataMessage().get();
Recipient conversation = getMessageDestination(content, message);
Recipient conversation = getRecipientForMessage(content, message);
if (conversation.isGroupRecipient() && conversation.isBlocked()) {
return true;