mirror of
https://github.com/oxen-io/session-android.git
synced 2025-01-11 16:33:39 +00:00
Refactor PushDecryptJob
This commit is contained in:
parent
afe049c9c3
commit
6205b6c9f6
@ -652,9 +652,9 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
||||
|
||||
public void checkNeedsDatabaseReset() {
|
||||
if (TextSecurePreferences.getNeedsDatabaseReset(this)) {
|
||||
boolean wasUnlinked = TextSecurePreferences.setNeedsDatabaseResetFromUnlink(this);
|
||||
boolean wasUnlinked = TextSecurePreferences.getWasUnlinked(this);
|
||||
TextSecurePreferences.clearAll(this);
|
||||
TextSecurePreferences.setNeedDatabaseResetFromUnlink(this, wasUnlinked); // Loki - Re-set the preference so we can use it in the starting screen to determine whether device was unlinked or not
|
||||
TextSecurePreferences.setWasUnlinked(this, wasUnlinked); // Loki - Re-set the preference so we can use it in the starting screen to determine whether device was unlinked or not
|
||||
MasterSecretUtil.clear(this);
|
||||
if (this.deleteDatabase("signal.db")) {
|
||||
Log.d("Loki", "Deleted database");
|
||||
|
@ -99,7 +99,7 @@ public class GroupMessageProcessor {
|
||||
}
|
||||
|
||||
// Loki - Ignore message if needed
|
||||
if (ClosedGroupsProtocol.shouldIgnoreMessage(context, group)) {
|
||||
if (ClosedGroupsProtocol.shouldIgnoreGroupCreatedMessage(context, group)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -80,7 +80,7 @@ public class MultiDeviceContactUpdateJob extends BaseJob implements InjectableTy
|
||||
this(context, address, true);
|
||||
}
|
||||
|
||||
private MultiDeviceContactUpdateJob(@NonNull Context context, @Nullable Address address, boolean forceSync) {
|
||||
public MultiDeviceContactUpdateJob(@NonNull Context context, @Nullable Address address, boolean forceSync) {
|
||||
this(new Job.Parameters.Builder()
|
||||
.addConstraint(NetworkConstraint.KEY)
|
||||
.setQueue("MultiDeviceContactUpdateJob")
|
||||
|
@ -68,15 +68,18 @@ import org.thoughtcrime.securesms.linkpreview.Link;
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.loki.FriendRequestHandler;
|
||||
import org.thoughtcrime.securesms.loki.database.LokiMessageDatabase;
|
||||
import org.thoughtcrime.securesms.loki.protocol.LokiSessionResetImplementation;
|
||||
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase;
|
||||
import org.thoughtcrime.securesms.loki.MultiDeviceUtilities;
|
||||
import org.thoughtcrime.securesms.loki.activities.HomeActivity;
|
||||
import org.thoughtcrime.securesms.loki.utilities.MentionManagerUtilities;
|
||||
import org.thoughtcrime.securesms.loki.database.LokiMessageDatabase;
|
||||
import org.thoughtcrime.securesms.loki.database.LokiPreKeyBundleDatabase;
|
||||
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase;
|
||||
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocol;
|
||||
import org.thoughtcrime.securesms.loki.protocol.FriendRequestProtocol;
|
||||
import org.thoughtcrime.securesms.loki.protocol.LokiSessionResetImplementation;
|
||||
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol;
|
||||
import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol;
|
||||
import org.thoughtcrime.securesms.loki.protocol.SyncMessagesProtocol;
|
||||
import org.thoughtcrime.securesms.loki.utilities.Broadcaster;
|
||||
import org.thoughtcrime.securesms.loki.utilities.MentionManagerUtilities;
|
||||
import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities;
|
||||
import org.thoughtcrime.securesms.mms.IncomingMediaMessage;
|
||||
import org.thoughtcrime.securesms.mms.MmsException;
|
||||
@ -144,7 +147,7 @@ import org.whispersystems.signalservice.loki.protocol.mentions.MentionsManager;
|
||||
import org.whispersystems.signalservice.loki.protocol.meta.LokiServiceMessage;
|
||||
import org.whispersystems.signalservice.loki.protocol.multidevice.DeviceLink;
|
||||
import org.whispersystems.signalservice.loki.protocol.multidevice.DeviceLinkingSession;
|
||||
import org.whispersystems.signalservice.loki.protocol.multidevice.LokiDeviceLinkUtilities;
|
||||
import org.whispersystems.signalservice.loki.protocol.sessionmanagement.SessionManagementProtocol;
|
||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiMessageFriendRequestStatus;
|
||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus;
|
||||
import org.whispersystems.signalservice.loki.utilities.PromiseUtil;
|
||||
@ -285,9 +288,9 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
|
||||
SignalServiceContent content = cipher.decrypt(envelope);
|
||||
|
||||
// Loki - Ignore any friend requests that we got before restoration
|
||||
if (content.isFriendRequest() && content.getTimestamp() < TextSecurePreferences.getRestorationTime(context)) {
|
||||
Log.d("Loki", "Ignoring friend request received before restoration.");
|
||||
// Loki - Ignore any friend requests from before restoration
|
||||
if (FriendRequestProtocol.isFriendRequestFromBeforeRestoration(content)) {
|
||||
Log.d("Loki", "Ignoring friend request from before restoration.");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -297,18 +300,15 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
}
|
||||
|
||||
// Loki - Handle friend request acceptance if needed
|
||||
if (!content.isFriendRequest() && !isGroupChatMessage(content)) {
|
||||
becomeFriendsWithContactIfNeeded(content.getSender(), true, false);
|
||||
}
|
||||
FriendRequestProtocol.handleFriendRequestAcceptanceIfNeeded(content);
|
||||
|
||||
// Loki - Handle pre key bundle message if needed
|
||||
SessionManagementProtocol.handlePreKeyBundleMessageIfNeeded(content);
|
||||
|
||||
// Loki - Handle session request if needed
|
||||
handleSessionRequestIfNeeded(content);
|
||||
|
||||
// Loki - Store pre key bundle if needed
|
||||
if (!content.getDeviceLink().isPresent()) {
|
||||
storePreKeyBundleIfNeeded(content);
|
||||
}
|
||||
SessionManagementProtocol.handleSessionRequestIfNeeded(content);
|
||||
|
||||
// Loki - Handle address message if needed
|
||||
if (content.lokiServiceMessage.isPresent()) {
|
||||
LokiServiceMessage lokiMessage = content.lokiServiceMessage.get();
|
||||
if (lokiMessage.getAddressMessage() != null) {
|
||||
@ -316,47 +316,33 @@ 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 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( isOneOfOurDevices -> {
|
||||
if (!isOneOfOurDevices) { setDisplayName(content.getSender(), rawSenderDisplayName.get()); }
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
}
|
||||
// Loki - Handle profile update if needed
|
||||
SessionMetaProtocol.handleProfileUpdateIfNeeded(content);
|
||||
|
||||
if (content.getDeviceLink().isPresent()) {
|
||||
handleDeviceLinkMessage(content.getDeviceLink().get(), content);
|
||||
MultiDeviceProtocol.handleDeviceLinkMessageIfNeeded(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 master device
|
||||
String ourMasterDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
|
||||
if (ourMasterDevice != null && ourMasterDevice.equals(content.getSender())) {
|
||||
TextSecurePreferences.setNeedDatabaseResetFromUnlink(context, true);
|
||||
MultiDeviceUtilities.checkIsRevokedSlaveDevice(context);
|
||||
}
|
||||
// Loki - Handle unlinking request if needed
|
||||
if (message.isUnlinkingRequest()) {
|
||||
MultiDeviceProtocol.handleUnlinkingRequest(message);
|
||||
} else {
|
||||
// Loki - Don't process session restore message any further
|
||||
// Loki - Don't process session restoration requests or session requests any further
|
||||
if (message.isSessionRestorationRequest() || message.isSessionRequest()) { return; }
|
||||
|
||||
if (message.isEndSession()) handleEndSessionMessage(content, smsMessageId);
|
||||
else if (message.isGroupUpdate()) handleGroupMessage(content, message, smsMessageId);
|
||||
else if (message.isExpirationUpdate())
|
||||
if (message.isEndSession()) {
|
||||
handleEndSessionMessage(content, smsMessageId);
|
||||
} else if (message.isGroupUpdate()) {
|
||||
handleGroupMessage(content, message, smsMessageId);
|
||||
} else if (message.isExpirationUpdate()) {
|
||||
handleExpirationUpdate(content, message, smsMessageId);
|
||||
else if (isMediaMessage)
|
||||
} else if (isMediaMessage) {
|
||||
handleMediaMessage(content, message, smsMessageId, Optional.absent());
|
||||
else if (message.getBody().isPresent())
|
||||
} else if (message.getBody().isPresent()) {
|
||||
handleTextMessage(content, message, smsMessageId, Optional.absent());
|
||||
}
|
||||
|
||||
if (message.getGroupInfo().isPresent() && groupDatabase.isUnknownGroup(GroupUtil.getEncodedId(message.getGroupInfo().get()))) {
|
||||
handleUnknownGroupMessage(content, message.getGroupInfo().get());
|
||||
@ -366,25 +352,12 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
handleProfileKey(content, message);
|
||||
}
|
||||
|
||||
// Loki - This doesn't get invoked for group chats
|
||||
if (content.isNeedsReceipt()) {
|
||||
handleNeedsDeliveryReceipt(content, message);
|
||||
}
|
||||
|
||||
// If we received a friend request, but we were already friends with the user, reset the session
|
||||
if (content.isFriendRequest() && !message.isGroupMessage()) {
|
||||
Recipient sender = Recipient.from(context, Address.fromSerialized(content.getSender()), false);
|
||||
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
|
||||
long threadID = threadDatabase.getThreadIdIfExistsFor(sender);
|
||||
if (lokiThreadDatabase.getFriendRequestStatus(threadID) == LokiThreadFriendRequestStatus.FRIENDS) {
|
||||
resetSession(content.getSender());
|
||||
// Let our other devices know that we have reset the session
|
||||
MessageSender.syncContact(context, sender.getAddress());
|
||||
}
|
||||
}
|
||||
|
||||
// Loki - Handle friend request logic if needed
|
||||
updateFriendRequestStatusIfNeeded(content, message);
|
||||
// Loki - Handle friend request message if needed
|
||||
FriendRequestProtocol.handleFriendRequestMessageIfNeeded(content);
|
||||
}
|
||||
} else if (content.getSyncMessage().isPresent()) {
|
||||
TextSecurePreferences.setMultiDevice(context, true);
|
||||
@ -557,26 +530,11 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
}
|
||||
|
||||
if (threadId != null) {
|
||||
resetSession(content.getSender());
|
||||
SessionManagementProtocol.handleEndSessionMessage(content);
|
||||
MessageNotifier.updateNotification(context, threadId);
|
||||
}
|
||||
}
|
||||
|
||||
private void resetSession(String hexEncodedPublicKey) {
|
||||
TextSecureSessionStore sessionStore = new TextSecureSessionStore(context);
|
||||
LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context);
|
||||
|
||||
Log.d("Loki", "Received a session reset request from: " + hexEncodedPublicKey + "; archiving the session.");
|
||||
|
||||
sessionStore.archiveAllSessions(hexEncodedPublicKey);
|
||||
lokiThreadDatabase.setSessionResetStatus(hexEncodedPublicKey, LokiSessionResetStatus.REQUEST_RECEIVED);
|
||||
|
||||
Log.d("Loki", "Sending a ping back to " + hexEncodedPublicKey + ".");
|
||||
MessageSender.sendBackgroundMessage(context, hexEncodedPublicKey);
|
||||
|
||||
SecurityEvent.broadcastSecurityUpdateEvent(context);
|
||||
}
|
||||
|
||||
private long handleSynchronizeSentEndSessionMessage(@NonNull SentTranscriptMessage message)
|
||||
{
|
||||
SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
|
||||
@ -688,92 +646,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
}
|
||||
}
|
||||
|
||||
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> 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 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) {
|
||||
// TODO: We should ensure that our mapping has been uploaded to the server before sending out this message
|
||||
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());
|
||||
}
|
||||
|
||||
// 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 + ".");
|
||||
}
|
||||
}
|
||||
|
||||
private void handleGroupSyncMessage(@NonNull SignalServiceContent content, @NonNull SignalServiceAttachment groupMessage) {
|
||||
if (groupMessage.isStream()) {
|
||||
Log.d("Loki", "Received a group sync message.");
|
||||
try {
|
||||
InputStream in = groupMessage.asStream().getInputStream();
|
||||
DeviceGroupsInputStream groupsInputStream = new DeviceGroupsInputStream(in);
|
||||
List<DeviceGroup> groups = groupsInputStream.readAll();
|
||||
for (DeviceGroup group : groups) {
|
||||
SignalServiceGroup serviceGroup = new SignalServiceGroup(
|
||||
SignalServiceGroup.Type.UPDATE,
|
||||
group.getId(),
|
||||
SignalServiceGroup.GroupType.SIGNAL,
|
||||
group.getName().orNull(),
|
||||
group.getMembers(),
|
||||
group.getAvatar().orNull(),
|
||||
group.getAdmins()
|
||||
);
|
||||
SignalServiceDataMessage dataMessage = new SignalServiceDataMessage(content.getTimestamp(), serviceGroup, null, null);
|
||||
GroupMessageProcessor.process(context, content, dataMessage, false);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.d("Loki", "Failed to sync group due to error: " + e + ".");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleOpenGroupSyncMessage(@NonNull List<LokiPublicChat> openGroups) {
|
||||
try {
|
||||
for (LokiPublicChat openGroup : openGroups) {
|
||||
long threadID = GroupManager.getOpenGroupThreadID(openGroup.getId(), context);
|
||||
if (threadID > -1) continue;
|
||||
|
||||
String url = openGroup.getServer();
|
||||
long channel = openGroup.getChannel();
|
||||
OpenGroupUtilities.addGroup(context, url, channel).fail(e -> {
|
||||
Log.d("Loki", "Failed to sync open group: " + url + " due to error: " + e + ".");
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.d("Loki", "Failed to sync open groups due to error: " + e + ".");
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSynchronizeSentMessage(@NonNull SignalServiceContent content,
|
||||
@NonNull SentTranscriptMessage message)
|
||||
throws StorageFailedException
|
||||
@ -800,8 +672,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
handleUnknownGroupMessage(content, message.getMessage().getGroupInfo().get());
|
||||
}
|
||||
|
||||
String ourMasterDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
|
||||
boolean isSenderMasterDevice = ourMasterDevice != null && ourMasterDevice.equals(content.getSender());
|
||||
if (message.getMessage().getProfileKey().isPresent()) {
|
||||
Recipient recipient = null;
|
||||
|
||||
@ -813,16 +683,12 @@ 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 profile picture url
|
||||
if (isSenderMasterDevice) {
|
||||
handleProfileKey(content, message.getMessage());
|
||||
}
|
||||
// Loki - Handle profile key update if needed
|
||||
handleProfileKey(content, message.getMessage());
|
||||
}
|
||||
|
||||
// Loki - Update display name from master device
|
||||
if (isSenderMasterDevice && content.senderDisplayName.isPresent() && content.senderDisplayName.get().length() > 0) {
|
||||
TextSecurePreferences.setProfileName(context, content.senderDisplayName.get());
|
||||
}
|
||||
// Loki - Update profile if needed
|
||||
SessionMetaProtocol.handleProfileUpdateIfNeeded(content);
|
||||
|
||||
if (threadId != null) {
|
||||
DatabaseFactory.getThreadDatabase(context).setRead(threadId, true);
|
||||
@ -913,17 +779,13 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
Optional<List<LinkPreview>> linkPreviews = getLinkPreviews(message.getPreviews(), message.getBody().or(""));
|
||||
Optional<Attachment> sticker = getStickerAttachment(message.getSticker());
|
||||
|
||||
Address sender = masterRecipient.getAddress();
|
||||
Address masterAddress = masterRecipient.getAddress();
|
||||
|
||||
// If message is from group then we need to map it to get the sender of the message
|
||||
if (message.isGroupMessage()) {
|
||||
sender = getMasterRecipient(content.getSender()).getAddress();
|
||||
masterAddress = getMasterRecipient(content.getSender()).getAddress();
|
||||
}
|
||||
|
||||
// Ignore messages from ourselves
|
||||
if (sender.serialize().equalsIgnoreCase(TextSecurePreferences.getLocalNumber(context))) { return; }
|
||||
|
||||
IncomingMediaMessage mediaMessage = new IncomingMediaMessage(sender, message.getTimestamp(), -1,
|
||||
IncomingMediaMessage mediaMessage = new IncomingMediaMessage(masterAddress, message.getTimestamp(), -1,
|
||||
message.getExpiresInSeconds() * 1000L, false, content.isNeedsReceipt(), message.getBody(), message.getGroupInfo(), message.getAttachments(),
|
||||
quote, sharedContacts, linkPreviews, sticker);
|
||||
|
||||
@ -967,19 +829,16 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
MessageNotifier.updateNotification(context, insertResult.get().getThreadId());
|
||||
}
|
||||
|
||||
// Loki - Run database updates in the background, we should look into fixing this in the future
|
||||
AsyncTask.execute(() -> {
|
||||
// Loki - Store message server ID
|
||||
updateGroupChatMessageServerID(messageServerIDOrNull, insertResult);
|
||||
// Loki - Store message server ID if needed
|
||||
updateGroupChatMessageServerID(messageServerIDOrNull, insertResult);
|
||||
|
||||
// Loki - Update mapping of message to original thread ID
|
||||
if (insertResult.isPresent()) {
|
||||
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
|
||||
LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context);
|
||||
long originalThreadId = threadDatabase.getThreadIdFor(originalRecipient);
|
||||
lokiMessageDatabase.setOriginalThreadID(insertResult.get().getMessageId(), originalThreadId);
|
||||
}
|
||||
});
|
||||
// Loki - Update mapping of message ID to original thread ID
|
||||
if (insertResult.isPresent()) {
|
||||
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
|
||||
LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context);
|
||||
long originalThreadId = threadDatabase.getThreadIdFor(originalRecipient);
|
||||
lokiMessageDatabase.setOriginalThreadID(insertResult.get().getMessageId(), originalThreadId);
|
||||
}
|
||||
}
|
||||
|
||||
private long handleSynchronizeSentExpirationUpdate(@NonNull SentTranscriptMessage message) throws MmsException {
|
||||
@ -1108,14 +967,10 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
|
||||
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 = getMasterRecipient(content.getSender()).getAddress();
|
||||
}
|
||||
|
||||
// Ignore messages from ourselves
|
||||
if (sender.serialize().equalsIgnoreCase(TextSecurePreferences.getLocalNumber(context))) { return; }
|
||||
|
||||
IncomingTextMessage tm = new IncomingTextMessage(sender,
|
||||
content.getSenderDevice(),
|
||||
message.getTimestamp(), body,
|
||||
@ -1141,249 +996,23 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
MessageNotifier.updateNotification(context, threadId);
|
||||
}
|
||||
|
||||
// Loki - Run database updates in background, we should look into fixing this in the future
|
||||
AsyncTask.execute(() -> {
|
||||
if (insertResult.isPresent()) {
|
||||
InsertResult result = insertResult.get();
|
||||
// Loki - Cache the user hex encoded public key (for mentions)
|
||||
MentionManagerUtilities.INSTANCE.populateUserHexEncodedPublicKeyCacheIfNeeded(result.getThreadId(), context);
|
||||
MentionsManager.INSTANCE.cache(textMessage.getSender().serialize(), result.getThreadId());
|
||||
if (insertResult.isPresent()) {
|
||||
InsertResult result = insertResult.get();
|
||||
|
||||
// Loki - Store message server ID
|
||||
updateGroupChatMessageServerID(messageServerIDOrNull, insertResult);
|
||||
// Loki - Cache the user hex encoded public key (for mentions)
|
||||
MentionManagerUtilities.INSTANCE.populateUserHexEncodedPublicKeyCacheIfNeeded(result.getThreadId(), context);
|
||||
MentionsManager.shared.cache(textMessage.getSender().serialize(), result.getThreadId());
|
||||
|
||||
// Loki - Update mapping of message to original thread ID
|
||||
if (result.getMessageId() > -1) {
|
||||
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
|
||||
LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context);
|
||||
long originalThreadId = threadDatabase.getThreadIdFor(originalRecipient);
|
||||
lokiMessageDatabase.setOriginalThreadID(result.getMessageId(), originalThreadId);
|
||||
}
|
||||
// Loki - Store message server ID
|
||||
updateGroupChatMessageServerID(messageServerIDOrNull, insertResult);
|
||||
|
||||
// Loki - Update mapping of message to original thread ID
|
||||
if (result.getMessageId() > -1) {
|
||||
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
|
||||
LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context);
|
||||
long originalThreadId = threadDatabase.getThreadIdFor(originalRecipient);
|
||||
lokiMessageDatabase.setOriginalThreadID(result.getMessageId(), originalThreadId);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isValidDeviceLinkMessage(@NonNull DeviceLink authorisation) {
|
||||
boolean isSecondaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context) != null;
|
||||
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context);
|
||||
boolean isRequest = (authorisation.getType() == DeviceLink.Type.REQUEST);
|
||||
if (authorisation.getRequestSignature() == null) {
|
||||
Log.d("Loki", "Ignoring pairing request message without a request signature.");
|
||||
return false;
|
||||
} else if (isRequest && isSecondaryDevice) {
|
||||
Log.d("Loki", "Ignoring unexpected pairing request message (the device is already paired as a secondary device).");
|
||||
return false;
|
||||
} else if (isRequest && !authorisation.getMasterHexEncodedPublicKey().equals(userHexEncodedPublicKey)) {
|
||||
Log.d("Loki", "Ignoring pairing request message addressed to another user.");
|
||||
return false;
|
||||
} else if (isRequest && authorisation.getSlaveHexEncodedPublicKey().equals(userHexEncodedPublicKey)) {
|
||||
Log.d("Loki", "Ignoring pairing request message from self.");
|
||||
return false;
|
||||
}
|
||||
return authorisation.verify();
|
||||
}
|
||||
|
||||
private void handleDeviceLinkMessage(@NonNull DeviceLink deviceLink, @NonNull SignalServiceContent content) {
|
||||
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context);
|
||||
if (deviceLink.getType() == DeviceLink.Type.REQUEST) {
|
||||
handleDeviceLinkRequestMessage(deviceLink, content);
|
||||
} else if (deviceLink.getSlaveHexEncodedPublicKey().equals(userHexEncodedPublicKey)) {
|
||||
handleDeviceLinkAuthorizedMessage(deviceLink, content);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleDeviceLinkRequestMessage(@NonNull DeviceLink deviceLink, @NonNull SignalServiceContent content) {
|
||||
DeviceLinkingSession linkingSession = DeviceLinkingSession.Companion.getShared();
|
||||
if (!linkingSession.isListeningForLinkingRequests()) {
|
||||
new Broadcaster(context).broadcast("unexpectedDeviceLinkRequestReceived");
|
||||
return;
|
||||
}
|
||||
boolean isValid = isValidDeviceLinkMessage(deviceLink);
|
||||
if (!isValid) { return; }
|
||||
storePreKeyBundleIfNeeded(content);
|
||||
linkingSession.processLinkingRequest(deviceLink);
|
||||
}
|
||||
|
||||
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 = isValidDeviceLinkMessage(deviceLink);
|
||||
if (!isValid) {
|
||||
Log.d("Loki", "Ignoring invalid device link message.");
|
||||
return;
|
||||
}
|
||||
if (!DeviceLinkingSession.Companion.getShared().isListeningForLinkingRequests()) {
|
||||
Log.d("Loki", "Ignoring device link message.");
|
||||
return;
|
||||
}
|
||||
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(deviceLink);
|
||||
// Store the master device's ID
|
||||
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context);
|
||||
DatabaseFactory.getLokiAPIDatabase(context).clearDeviceLinks(userHexEncodedPublicKey);
|
||||
DatabaseFactory.getLokiAPIDatabase(context).addDeviceLink(deviceLink);
|
||||
TextSecurePreferences.setMasterHexEncodedPublicKey(context, deviceLink.getMasterHexEncodedPublicKey());
|
||||
TextSecurePreferences.setMultiDevice(context, true);
|
||||
// Send a background message to the master device
|
||||
MessageSender.sendBackgroundMessage(context, deviceLink.getMasterHexEncodedPublicKey());
|
||||
/*
|
||||
Update device link on the file server.
|
||||
We put this here because after receiving the authorisation message, we will also receive all sync messages.
|
||||
If these sync messages are contact syncs then we need to send them friend requests so that we can establish multi-device communication.
|
||||
If our device mapping is not stored on the server before the other party receives our message, they will think that they got a friend request from a non-multi-device user.
|
||||
*/
|
||||
try {
|
||||
PromiseUtil.timeout(LokiFileServerAPI.shared.addDeviceLink(deviceLink), 8000).get();
|
||||
} catch (Exception e) {
|
||||
Log.w("Loki", "Failed to upload device links to the file server! " + e);
|
||||
}
|
||||
// Update display name if needed
|
||||
if (content.senderDisplayName.isPresent() && content.senderDisplayName.get().length() > 0) {
|
||||
TextSecurePreferences.setProfileName(context, content.senderDisplayName.get());
|
||||
}
|
||||
// Update profile picture if needed
|
||||
if (content.getDataMessage().isPresent()) {
|
||||
handleProfileKey(content, content.getDataMessage().get());
|
||||
}
|
||||
// Handle contact sync if needed
|
||||
if (content.getSyncMessage().isPresent() && content.getSyncMessage().get().getContacts().isPresent()) {
|
||||
handleContactSyncMessage(content.getSyncMessage().get().getContacts().get());
|
||||
}
|
||||
}
|
||||
|
||||
private void setDisplayName(String hexEncodedPublicKey, String profileName) {
|
||||
String displayName = profileName + " (..." + hexEncodedPublicKey.substring(hexEncodedPublicKey.length() - 8) + ")";
|
||||
DatabaseFactory.getLokiUserDatabase(context).setDisplayName(hexEncodedPublicKey, displayName);
|
||||
}
|
||||
|
||||
private void updateGroupChatMessageServerID(Optional<Long> messageServerIDOrNull, Optional<InsertResult> insertResult) {
|
||||
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()) { return; }
|
||||
LokiServiceMessage lokiMessage = content.lokiServiceMessage.get();
|
||||
if (lokiMessage.getPreKeyBundleMessage() == null) { return; }
|
||||
int registrationID = TextSecurePreferences.getLocalRegistrationId(context);
|
||||
LokiPreKeyBundleDatabase lokiPreKeyBundleDatabase = DatabaseFactory.getLokiPreKeyBundleDatabase(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);
|
||||
|
||||
}
|
||||
|
||||
private void handleSessionRequestIfNeeded(@NonNull SignalServiceContent content) {
|
||||
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).isClosedGroupMember(sender);
|
||||
boolean shouldAcceptSessionRequest = isOurFriend || isInOneOfOurGroups;
|
||||
if (shouldAcceptSessionRequest) {
|
||||
MessageSender.sendBackgroundMessage(context, content.getSender()); // Send a background message to acknowledge
|
||||
}
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
}
|
||||
|
||||
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(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 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 needed
|
||||
if (requiresContactSync) {
|
||||
MessageSender.syncContact(context, contactID.getAddress());
|
||||
}
|
||||
// Enable profile sharing with the recipient
|
||||
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(contactID, true);
|
||||
// Update the last message if needed
|
||||
LokiDeviceLinkUtilities.INSTANCE.getMasterHexEncodedPublicKey(hexEncodedPublicKey).success( masterHexEncodedPublicKey -> {
|
||||
Util.runOnMain(() -> {
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
private void updateFriendRequestStatusIfNeeded(@NonNull SignalServiceContent content, @NonNull SignalServiceDataMessage message) {
|
||||
if (!content.isFriendRequest() || message.isGroupMessage() || message.isSessionRequest()) { return; }
|
||||
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
|
||||
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 = 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(masterRecipient);
|
||||
LokiThreadFriendRequestStatus threadFriendRequestStatus = lokiThreadDatabase.getFriendRequestStatus(threadID);
|
||||
|
||||
if (threadFriendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_SENT) {
|
||||
// This can happen if Alice sent Bob a friend request, Bob declined, but then Bob changed his
|
||||
// mind and sent a friend request to Alice. In this case we want Alice to auto-accept the request
|
||||
// and send a friend request accepted message back to Bob. We don't check that sending the
|
||||
// friend request accepted message succeeded. Even if it doesn't, the thread's current friend
|
||||
// request status will be set to `FRIENDS` for Alice making it possible
|
||||
// for Alice to send messages to Bob. When Bob receives a message, his thread's friend request status
|
||||
// will then be set to `FRIENDS`. If we do check for a successful send
|
||||
// before updating Alice's thread's friend request status to `FRIENDS`,
|
||||
// we can end up in a deadlock where both users' threads' friend request statuses are
|
||||
// `REQUEST_SENT`.
|
||||
lokiThreadDatabase.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.FRIENDS);
|
||||
// Since messages are forwarded to the primary device thread, we need to update it there
|
||||
FriendRequestHandler.updateLastFriendRequestMessage(context, primaryDeviceThreadID, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED);
|
||||
// Accept the friend request
|
||||
MessageSender.sendBackgroundMessage(context, content.getSender());
|
||||
// Send contact sync message
|
||||
MessageSender.syncContact(context, originalRecipient.getAddress());
|
||||
} else if (threadFriendRequestStatus != LokiThreadFriendRequestStatus.FRIENDS) {
|
||||
// Checking that the sender of the message isn't already a friend is necessary because otherwise
|
||||
// the following situation can occur: Alice and Bob are friends. Bob loses his database and his
|
||||
// friend request status is reset to `NONE`. Bob now sends Alice a friend
|
||||
// request. Alice's thread's friend request status is reset to
|
||||
// `REQUEST_RECEIVED`.
|
||||
lokiThreadDatabase.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.REQUEST_RECEIVED);
|
||||
|
||||
// Since messages are forwarded to the primary device thread, we need to update it there
|
||||
FriendRequestHandler.receivedIncomingFriendRequestMessage(context, primaryDeviceThreadID);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1464,21 +1093,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
}
|
||||
}
|
||||
|
||||
private SmsMessageRecord getLastMessage(String sender) {
|
||||
try {
|
||||
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
|
||||
Recipient recipient = Recipient.from(context, Address.fromSerialized(sender), false);
|
||||
long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(recipient);
|
||||
if (threadID < 0) { return null; }
|
||||
int messageCount = smsDatabase.getMessageCountForThread(threadID);
|
||||
if (messageCount <= 0) { return null; }
|
||||
long lastMessageID = smsDatabase.getIDForMessageAtIndex(threadID, messageCount - 1);
|
||||
return smsDatabase.getMessage(lastMessageID);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void handleCorruptMessage(@NonNull String sender, int senderDevice, long timestamp,
|
||||
@NonNull Optional<Long> smsMessageId)
|
||||
{
|
||||
@ -1521,14 +1135,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
triggerSessionRestorePrompt(sender);
|
||||
}
|
||||
|
||||
private void triggerSessionRestorePrompt(@NonNull String sender) {
|
||||
Recipient primaryRecipient = getMasterRecipient(sender);
|
||||
if (!primaryRecipient.isGroupRecipient()) {
|
||||
long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(primaryRecipient);
|
||||
DatabaseFactory.getLokiThreadDatabase(context).addSessionRestoreDevice(threadID, sender);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleLegacyMessage(@NonNull String sender, int senderDevice, long timestamp,
|
||||
@NonNull Optional<Long> smsMessageId)
|
||||
{
|
||||
@ -1580,10 +1186,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
String url = content.senderProfilePictureURL.or("");
|
||||
ApplicationContext.getInstance(context).getJobManager().add(new RetrieveProfileAvatarJob(recipient, url));
|
||||
|
||||
// Loki - If the recipient is our master device then we need to go and update our avatar mappings on the public chats
|
||||
if (recipient.isUserMasterDevice()) {
|
||||
ApplicationContext.getInstance(context).updatePublicChatProfilePictureIfNeeded();
|
||||
}
|
||||
SessionMetaProtocol.handleProfileKeyUpdateIfNeeded(content, message);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1647,7 +1250,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
long threadId;
|
||||
|
||||
if (typingMessage.getGroupId().isPresent()) {
|
||||
// Typing messages should only apply to signal groups, thus we use `getEncodedId`
|
||||
// Typing messages should only apply to closed groups, thus we use `getEncodedId`
|
||||
Address groupAddress = Address.fromSerialized(GroupUtil.getEncodedId(typingMessage.getGroupId().get(), false));
|
||||
Recipient groupRecipient = Recipient.from(context, groupAddress, false);
|
||||
|
||||
@ -1814,45 +1417,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
}
|
||||
}
|
||||
|
||||
private Recipient getRecipientForMessage(SignalServiceContent content, SignalServiceDataMessage message) {
|
||||
if (message.isGroupMessage()) {
|
||||
return Recipient.from(context, Address.fromSerialized(GroupUtil.getEncodedId(message.getGroupInfo().get())), false);
|
||||
} else {
|
||||
return Recipient.from(context, Address.fromSerialized(content.getSender()), false);
|
||||
}
|
||||
}
|
||||
|
||||
private Recipient getMasterRecipientForMessage(SignalServiceContent content, SignalServiceDataMessage message) {
|
||||
if (message.isGroupMessage()) {
|
||||
return getRecipientForMessage(content, message);
|
||||
} else {
|
||||
return getMasterRecipient(content.getSender());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the master device recipient of the provided 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 getMasterRecipient(String hexEncodedPublicKey) {
|
||||
try {
|
||||
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(targetHexEncodedPublicKey), false);
|
||||
} catch (Exception e) {
|
||||
Log.d("Loki", "Failed to get master device for: " + hexEncodedPublicKey + ". " + e.getMessage());
|
||||
return Recipient.from(context, Address.fromSerialized(hexEncodedPublicKey), false);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyTypingStoppedFromIncomingMessage(@NonNull Recipient conversationRecipient, @NonNull String sender, int device) {
|
||||
Recipient author = Recipient.from(context, Address.fromSerialized(sender), false);
|
||||
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(conversationRecipient);
|
||||
@ -1895,53 +1459,20 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
boolean isGroupActive = groupId.isPresent() && groupDatabase.isActive(groupId.get());
|
||||
boolean isLeaveMessage = message.getGroupInfo().isPresent() && message.getGroupInfo().get().getType() == SignalServiceGroup.Type.QUIT;
|
||||
|
||||
boolean isClosedGroup = conversation.getAddress().isClosedGroup();
|
||||
boolean isGroupMember = true;
|
||||
|
||||
// Only allow messages from group members
|
||||
if (isClosedGroup) {
|
||||
String senderHexEncodedPublicKey = content.getSender();
|
||||
|
||||
try {
|
||||
String masterHexEncodedPublicKey = PromiseUtil.timeout(LokiDeviceLinkUtilities.INSTANCE.getMasterHexEncodedPublicKey(content.getSender()), 5000).get();
|
||||
if (masterHexEncodedPublicKey != null) {
|
||||
senderHexEncodedPublicKey = masterHexEncodedPublicKey;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
Recipient senderMasterAddress = Recipient.from(context, Address.fromSerialized(senderHexEncodedPublicKey), false);
|
||||
|
||||
isGroupMember = groupId.isPresent() && groupDatabase.getGroupMembers(groupId.get(), true).contains(senderMasterAddress);
|
||||
}
|
||||
|
||||
return (isContentMessage && !isGroupActive) || (sender.isBlocked() && !isLeaveMessage) || (isContentMessage && !isGroupMember);
|
||||
boolean shouldIgnoreContentMessage = ClosedGroupsProtocol.shouldIgnoreContentMessage(context, conversation, groupId.orNull(), content);
|
||||
return (isContentMessage && !isGroupActive) || (sender.isBlocked() && !isLeaveMessage) || (isContentMessage && !shouldIgnoreContentMessage);
|
||||
} else {
|
||||
return sender.isBlocked();
|
||||
}
|
||||
} else if (content.getCallMessage().isPresent() || content.getTypingMessage().isPresent()) {
|
||||
return sender.isBlocked();
|
||||
} else if (content.getSyncMessage().isPresent()) {
|
||||
try {
|
||||
// We should ignore a sync message if the sender is not one of our devices
|
||||
boolean isOurDevice = PromiseUtil.timeout(MultiDeviceUtilities.isOneOfOurDevices(context, sender.getAddress()), 5000).get();
|
||||
if (!isOurDevice) {
|
||||
Log.w(TAG, "Got a sync message from a device that is not ours!.");
|
||||
}
|
||||
return !isOurDevice;
|
||||
} catch (Exception e) {
|
||||
return true;
|
||||
}
|
||||
return SyncMessagesProtocol.shouldIgnoreSyncMessage(context, sender);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isSessionRequest(SignalServiceContent content) {
|
||||
return content.getDataMessage().isPresent() && content.getDataMessage().get().isSessionRequest();
|
||||
}
|
||||
|
||||
private boolean isGroupChatMessage(SignalServiceContent content) {
|
||||
return content.getDataMessage().isPresent() && content.getDataMessage().get().isGroupMessage();
|
||||
}
|
||||
|
@ -1,93 +0,0 @@
|
||||
package org.thoughtcrime.securesms.loki
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil
|
||||
import org.thoughtcrime.securesms.database.Address
|
||||
import org.thoughtcrime.securesms.dependencies.InjectableType
|
||||
import org.thoughtcrime.securesms.jobmanager.Data
|
||||
import org.thoughtcrime.securesms.jobmanager.Job
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint
|
||||
import org.thoughtcrime.securesms.jobs.BaseJob
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender
|
||||
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
class PushMessageSyncSendJob private constructor(
|
||||
parameters: Parameters,
|
||||
private val messageID: Long,
|
||||
private val recipient: Address,
|
||||
private val timestamp: Long,
|
||||
private val message: ByteArray,
|
||||
private val ttl: Int
|
||||
) : BaseJob(parameters), InjectableType {
|
||||
|
||||
companion object {
|
||||
const val KEY = "PushMessageSyncSendJob"
|
||||
|
||||
private val TAG = PushMessageSyncSendJob::class.java.simpleName
|
||||
|
||||
private val KEY_MESSAGE_ID = "message_id"
|
||||
private val KEY_RECIPIENT = "recipient"
|
||||
private val KEY_TIMESTAMP = "timestamp"
|
||||
private val KEY_MESSAGE = "message"
|
||||
private val KEY_TTL = "ttl"
|
||||
}
|
||||
|
||||
@Inject
|
||||
lateinit var messageSender: SignalServiceMessageSender
|
||||
|
||||
constructor(messageID: Long, recipient: Address, timestamp: Long, message: ByteArray, ttl: Int) : this(Parameters.Builder()
|
||||
.addConstraint(NetworkConstraint.KEY)
|
||||
.setQueue(KEY)
|
||||
.setLifespan(TimeUnit.DAYS.toMillis(1))
|
||||
.setMaxAttempts(1)
|
||||
.build(),
|
||||
messageID, recipient, timestamp, message, ttl)
|
||||
|
||||
override fun serialize(): Data {
|
||||
return Data.Builder()
|
||||
.putLong(KEY_MESSAGE_ID, messageID)
|
||||
.putString(KEY_RECIPIENT, recipient.serialize())
|
||||
.putLong(KEY_TIMESTAMP, timestamp)
|
||||
.putByteArray(KEY_MESSAGE, message)
|
||||
.putInt(KEY_TTL, ttl)
|
||||
.build()
|
||||
}
|
||||
|
||||
override fun getFactoryKey(): String {
|
||||
return KEY
|
||||
}
|
||||
|
||||
@Throws(IOException::class, UntrustedIdentityException::class)
|
||||
public override fun onRun() {
|
||||
// Don't send sync messages to a group
|
||||
if (recipient.isGroup || recipient.isEmail) { return }
|
||||
val unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, Recipient.from(context, recipient, false))
|
||||
messageSender.lokiSendSyncMessage(messageID, SignalServiceAddress(recipient.toPhoneString()), unidentifiedAccess, timestamp, message, ttl)
|
||||
}
|
||||
|
||||
public override fun onShouldRetry(e: Exception): Boolean {
|
||||
// Loki - Disable since we have our own retrying when sending messages
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onCanceled() {}
|
||||
|
||||
class Factory : Job.Factory<PushMessageSyncSendJob> {
|
||||
override fun create(parameters: Parameters, data: Data): PushMessageSyncSendJob {
|
||||
try {
|
||||
return PushMessageSyncSendJob(parameters,
|
||||
data.getLong(KEY_MESSAGE_ID),
|
||||
Address.fromSerialized(data.getString(KEY_RECIPIENT)),
|
||||
data.getLong(KEY_TIMESTAMP),
|
||||
data.getByteArray(KEY_MESSAGE),
|
||||
data.getInt(KEY_TTL))
|
||||
} catch (e: IOException) {
|
||||
throw AssertionError(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -40,7 +40,7 @@ class LandingActivity : BaseActionBarActivity(), LinkDeviceSlaveModeDialogDelega
|
||||
registerButton.setOnClickListener { register() }
|
||||
restoreButton.setOnClickListener { restore() }
|
||||
linkButton.setOnClickListener { linkDevice() }
|
||||
if (TextSecurePreferences.setNeedsDatabaseResetFromUnlink(this)) {
|
||||
if (TextSecurePreferences.getWasUnlinked(this)) {
|
||||
Toast.makeText(this, "Your device was unlinked successfully", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
@ -5,11 +5,13 @@ import org.thoughtcrime.securesms.ApplicationContext
|
||||
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore
|
||||
import org.thoughtcrime.securesms.database.Address
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.loki.utilities.recipient
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.sms.MessageSender
|
||||
import org.thoughtcrime.securesms.util.GroupUtil
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.whispersystems.libsignal.SignalProtocolAddress
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceContent
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceGroup
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
||||
import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol
|
||||
@ -17,10 +19,20 @@ import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDevicePro
|
||||
object ClosedGroupsProtocol {
|
||||
|
||||
@JvmStatic
|
||||
fun shouldIgnoreMessage(context: Context, group: SignalServiceGroup): Boolean {
|
||||
fun shouldIgnoreContentMessage(context: Context, conversation: Recipient, groupID: String?, content: SignalServiceContent): Boolean {
|
||||
if (!conversation.address.isClosedGroup || groupID == null) { return false }
|
||||
val senderPublicKey = content.sender
|
||||
val senderMasterPublicKey = MultiDeviceProtocol.shared.getMasterDevice(senderPublicKey)
|
||||
val publicKeyToCheckFor = senderMasterPublicKey ?: senderPublicKey
|
||||
val members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(groupID, true)
|
||||
return !members.contains(recipient(context, publicKeyToCheckFor))
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun shouldIgnoreGroupCreatedMessage(context: Context, group: SignalServiceGroup): Boolean {
|
||||
val members = group.members
|
||||
val masterDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context)
|
||||
return !members.isPresent || !members.get().contains(masterDevice)
|
||||
val userMasterDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context)
|
||||
return !members.isPresent || !members.get().contains(userMasterDevice)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@ -31,19 +43,20 @@ object ClosedGroupsProtocol {
|
||||
result.add(Address.fromSerialized(groupID))
|
||||
return result
|
||||
} else {
|
||||
// A closed group's members should never include slave devices
|
||||
val members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(groupID, false)
|
||||
val recipients = members.flatMap { member ->
|
||||
val destinations = members.flatMap { member ->
|
||||
MultiDeviceProtocol.shared.getAllLinkedDevices(member.address.serialize()).map { Address.fromSerialized(it) }
|
||||
}.toMutableSet()
|
||||
val masterPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context)
|
||||
if (masterPublicKey != null && recipients.contains(Address.fromSerialized(masterPublicKey))) {
|
||||
recipients.remove(Address.fromSerialized(masterPublicKey))
|
||||
val userMasterPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context)
|
||||
if (userMasterPublicKey != null && destinations.contains(Address.fromSerialized(userMasterPublicKey))) {
|
||||
destinations.remove(Address.fromSerialized(userMasterPublicKey))
|
||||
}
|
||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||
if (userPublicKey != null && recipients.contains(Address.fromSerialized(userPublicKey))) {
|
||||
recipients.remove(Address.fromSerialized(userPublicKey))
|
||||
if (userPublicKey != null && destinations.contains(Address.fromSerialized(userPublicKey))) {
|
||||
destinations.remove(Address.fromSerialized(userPublicKey))
|
||||
}
|
||||
return recipients.toList()
|
||||
return destinations.toList()
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,39 +67,36 @@ object ClosedGroupsProtocol {
|
||||
val message = GroupUtil.createGroupLeaveMessage(context, recipient)
|
||||
if (threadID < 0 || !message.isPresent) { return false }
|
||||
MessageSender.send(context, message.get(), threadID, false, null)
|
||||
// Remove the *master* device from the group
|
||||
// Remove the master device from the group (a closed group's members should never include slave devices)
|
||||
val masterPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context)
|
||||
val publicKeyToUse = masterPublicKey ?: TextSecurePreferences.getLocalNumber(context)
|
||||
val publicKeyToRemove = masterPublicKey ?: TextSecurePreferences.getLocalNumber(context)
|
||||
val groupDatabase = DatabaseFactory.getGroupDatabase(context)
|
||||
val groupID = recipient.address.toGroupString()
|
||||
groupDatabase.setActive(groupID, false)
|
||||
groupDatabase.remove(groupID, Address.fromSerialized(publicKeyToUse))
|
||||
groupDatabase.remove(groupID, Address.fromSerialized(publicKeyToRemove))
|
||||
return true
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun establishSessionsWithMembersIfNeeded(context: Context, members: List<String>) {
|
||||
// A closed group's members should never include slave devices
|
||||
val allDevices = members.flatMap { member ->
|
||||
MultiDeviceProtocol.shared.getAllLinkedDevices(member)
|
||||
}.toMutableSet()
|
||||
val masterPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context)
|
||||
if (masterPublicKey != null && allDevices.contains(masterPublicKey)) {
|
||||
allDevices.remove(masterPublicKey)
|
||||
val userMasterPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context)
|
||||
if (userMasterPublicKey != null && allDevices.contains(userMasterPublicKey)) {
|
||||
allDevices.remove(userMasterPublicKey)
|
||||
}
|
||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||
if (userPublicKey != null && allDevices.contains(userPublicKey)) {
|
||||
allDevices.remove(userPublicKey)
|
||||
}
|
||||
for (device in allDevices) {
|
||||
val address = SignalProtocolAddress(device, SignalServiceAddress.DEFAULT_DEVICE_ID)
|
||||
val hasSession = TextSecureSessionStore(context).containsSession(address)
|
||||
if (!hasSession) { sendSessionRequest(context, device) }
|
||||
val deviceAsAddress = SignalProtocolAddress(device, SignalServiceAddress.DEFAULT_DEVICE_ID)
|
||||
val hasSession = TextSecureSessionStore(context).containsSession(deviceAsAddress)
|
||||
if (hasSession) { continue }
|
||||
val sessionRequest = EphemeralMessage.createSessionRequest(device)
|
||||
ApplicationContext.getInstance(context).jobManager.add(PushEphemeralMessageSendJob(sessionRequest))
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun sendSessionRequest(context: Context, publicKey: String) {
|
||||
val sessionRequest = EphemeralMessage.createSessionRequest(publicKey)
|
||||
ApplicationContext.getInstance(context).jobManager.add(PushEphemeralMessageSendJob(sessionRequest))
|
||||
}
|
||||
}
|
@ -16,7 +16,7 @@ class EphemeralMessage private constructor(val data: Map<*, *>) {
|
||||
fun createSessionRestorationRequest(publicKey: String) = EphemeralMessage(mapOf( "recipient" to publicKey, "friendRequest" to true, "sessionRestore" to true ))
|
||||
|
||||
@JvmStatic
|
||||
fun createSessionRequest(publicKey: String) = EphemeralMessage(mapOf("recipient" to publicKey, "friendRequest" to true, "sessionRequest" to true))
|
||||
fun createSessionRequest(publicKey: String) = EphemeralMessage(mapOf( "recipient" to publicKey, "friendRequest" to true, "sessionRequest" to true ))
|
||||
|
||||
internal fun parse(serialized: String): EphemeralMessage {
|
||||
val data = JsonUtil.fromJson(serialized, Map::class.java) ?: throw IllegalArgumentException("Couldn't parse string to JSON")
|
||||
|
@ -1,15 +1,122 @@
|
||||
package org.thoughtcrime.securesms.loki.protocol
|
||||
|
||||
import android.content.Context
|
||||
import org.thoughtcrime.securesms.ApplicationContext
|
||||
import org.thoughtcrime.securesms.database.Address
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.loki.utilities.recipient
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage
|
||||
import org.thoughtcrime.securesms.sms.OutgoingTextMessage
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceContent
|
||||
import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol
|
||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiMessageFriendRequestStatus
|
||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus
|
||||
|
||||
object FriendRequestProtocol {
|
||||
|
||||
private fun getLastMessageID(context: Context, threadID: Long): Long? {
|
||||
val db = DatabaseFactory.getSmsDatabase(context)
|
||||
val messageCount = db.getMessageCountForThread(threadID)
|
||||
if (messageCount == 0) { return null }
|
||||
return db.getIDForMessageAtIndex(threadID, messageCount - 1)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun handleFriendRequestAcceptanceIfNeeded(context: Context, publicKey: String, content: SignalServiceContent) {
|
||||
// If we get an envelope that isn't a friend request, then we can infer that we had to use
|
||||
// Signal cipher decryption and thus that we have a session with the other person.
|
||||
if (content.isFriendRequest) { return }
|
||||
val recipient = recipient(context, publicKey)
|
||||
// Friend requests don't apply to groups
|
||||
if (recipient.isGroupRecipient) { return }
|
||||
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient)
|
||||
val lokiThreadDB = DatabaseFactory.getLokiThreadDatabase(context)
|
||||
val threadFRStatus = lokiThreadDB.getFriendRequestStatus(threadID)
|
||||
// Guard against invalid state transitions
|
||||
if (threadFRStatus != LokiThreadFriendRequestStatus.REQUEST_SENDING && threadFRStatus != LokiThreadFriendRequestStatus.REQUEST_SENT
|
||||
&& threadFRStatus != LokiThreadFriendRequestStatus.REQUEST_RECEIVED) { return }
|
||||
lokiThreadDB.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.FRIENDS)
|
||||
val lastMessageID = getLastMessageID(context, threadID)
|
||||
if (lastMessageID != null) {
|
||||
DatabaseFactory.getLokiMessageDatabase(context).setFriendRequestStatus(lastMessageID, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED)
|
||||
}
|
||||
// Send a contact sync message if needed
|
||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||
val allUserDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(userPublicKey)
|
||||
if (allUserDevices.contains(publicKey)) { return }
|
||||
val deviceToSync = MultiDeviceProtocol.shared.getMasterDevice(publicKey) ?: publicKey
|
||||
SyncMessagesProtocol.syncContact(context, Address.fromSerialized(deviceToSync))
|
||||
}
|
||||
|
||||
private fun canFriendRequestBeAutoAccepted(context: Context, publicKey: String): Boolean {
|
||||
val recipient = recipient(context, publicKey)
|
||||
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient)
|
||||
val lokiThreadDB = DatabaseFactory.getLokiThreadDatabase(context)
|
||||
val threadFRStatus = lokiThreadDB.getFriendRequestStatus(threadID)
|
||||
if (threadFRStatus == LokiThreadFriendRequestStatus.REQUEST_SENT) {
|
||||
// This can happen if Alice sent Bob a friend request, Bob declined, but then Bob changed his
|
||||
// mind and sent a friend request to Alice. In this case we want Alice to auto-accept the request
|
||||
// and send a friend request accepted message back to Bob. We don't check that sending the
|
||||
// friend request accepted message succeeds. Even if it doesn't, the thread's current friend
|
||||
// request status will be set to FRIENDS for Alice making it possible for Alice to send messages
|
||||
// to Bob. When Bob receives a message, his thread's friend request status will then be set to
|
||||
// FRIENDS. If we do check for a successful send before updating Alice's thread's friend request
|
||||
// status to FRIENDS, we can end up in a deadlock where both users' threads' friend request statuses
|
||||
// are SENT.
|
||||
return true
|
||||
}
|
||||
// Auto-accept any friend requests from the user's own linked devices
|
||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||
val allUserDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(userPublicKey)
|
||||
if (allUserDevices.contains(publicKey)) { return true }
|
||||
// Auto-accept if the user is friends with any of the sender's linked devices.
|
||||
val allSenderDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(publicKey)
|
||||
if (allSenderDevices.any { device ->
|
||||
val deviceAsRecipient = recipient(context, publicKey)
|
||||
val deviceThreadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(deviceAsRecipient)
|
||||
lokiThreadDB.getFriendRequestStatus(deviceThreadID) == LokiThreadFriendRequestStatus.FRIENDS
|
||||
}) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun handleFriendRequestMessageIfNeeded(context: Context, publicKey: String, content: SignalServiceContent) {
|
||||
if (!content.isFriendRequest) { return }
|
||||
val recipient = recipient(context, publicKey)
|
||||
// Friend requests don't apply to groups
|
||||
if (recipient.isGroupRecipient) { return }
|
||||
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient)
|
||||
val lokiThreadDB = DatabaseFactory.getLokiThreadDatabase(context)
|
||||
val threadFRStatus = lokiThreadDB.getFriendRequestStatus(threadID)
|
||||
if (canFriendRequestBeAutoAccepted(context, publicKey)) {
|
||||
lokiThreadDB.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.FRIENDS)
|
||||
val lastMessageID = getLastMessageID(context, threadID)
|
||||
if (lastMessageID != null) {
|
||||
DatabaseFactory.getLokiMessageDatabase(context).setFriendRequestStatus(lastMessageID, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED)
|
||||
}
|
||||
val ephemeralMessage = EphemeralMessage.create(publicKey)
|
||||
ApplicationContext.getInstance(context).jobManager.add(PushEphemeralMessageSendJob(ephemeralMessage))
|
||||
} else if (threadFRStatus != LokiThreadFriendRequestStatus.FRIENDS) {
|
||||
// Checking that the sender of the message isn't already a friend is necessary because otherwise
|
||||
// the following situation can occur: Alice and Bob are friends. Bob loses his database and his
|
||||
// friend request status is reset to NONE. Bob now sends Alice a friend request. Alice's thread's
|
||||
// friend request status is reset to RECEIVED
|
||||
lokiThreadDB.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.REQUEST_RECEIVED)
|
||||
val lastMessageID = getLastMessageID(context, threadID)
|
||||
if (lastMessageID != null) {
|
||||
DatabaseFactory.getLokiMessageDatabase(context).setFriendRequestStatus(lastMessageID, LokiMessageFriendRequestStatus.REQUEST_PENDING)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun isFriendRequestFromBeforeRestoration(context: Context, content: SignalServiceContent): Boolean {
|
||||
return content.isFriendRequest && content.timestamp < TextSecurePreferences.getRestorationTime(context)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun shouldUpdateFriendRequestStatusFromOutgoingTextMessage(context: Context, message: OutgoingTextMessage): Boolean {
|
||||
// The order of these checks matters
|
||||
@ -43,4 +150,34 @@ object FriendRequestProtocol {
|
||||
threadDB.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.REQUEST_SENDING)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun setFriendRequestStatusToSentIfNeeded(context: Context, messageID: Long, threadID: Long) {
|
||||
val messageDB = DatabaseFactory.getLokiMessageDatabase(context)
|
||||
val messageFRStatus = messageDB.getFriendRequestStatus(messageID)
|
||||
if (messageFRStatus == LokiMessageFriendRequestStatus.NONE || messageFRStatus == LokiMessageFriendRequestStatus.REQUEST_EXPIRED
|
||||
|| messageFRStatus == LokiMessageFriendRequestStatus.REQUEST_SENDING) {
|
||||
messageDB.setFriendRequestStatus(messageID, LokiMessageFriendRequestStatus.REQUEST_PENDING)
|
||||
}
|
||||
val threadDB = DatabaseFactory.getLokiThreadDatabase(context)
|
||||
val threadFRStatus = threadDB.getFriendRequestStatus(threadID)
|
||||
if (threadFRStatus == LokiThreadFriendRequestStatus.NONE || threadFRStatus == LokiThreadFriendRequestStatus.REQUEST_EXPIRED
|
||||
|| threadFRStatus == LokiThreadFriendRequestStatus.REQUEST_SENDING) {
|
||||
threadDB.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.REQUEST_SENT)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun setFriendRequestStatusToFailedIfNeeded(context: Context, messageID: Long, threadID: Long) {
|
||||
val messageDB = DatabaseFactory.getLokiMessageDatabase(context)
|
||||
val messageFRStatus = messageDB.getFriendRequestStatus(messageID)
|
||||
if (messageFRStatus == LokiMessageFriendRequestStatus.REQUEST_SENDING) {
|
||||
messageDB.setFriendRequestStatus(messageID, LokiMessageFriendRequestStatus.REQUEST_FAILED)
|
||||
}
|
||||
val threadDB = DatabaseFactory.getLokiThreadDatabase(context)
|
||||
val threadFRStatus = threadDB.getFriendRequestStatus(threadID)
|
||||
if (threadFRStatus == LokiThreadFriendRequestStatus.REQUEST_SENDING) {
|
||||
threadDB.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.NONE)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package org.thoughtcrime.securesms.loki.protocol
|
||||
|
||||
import android.content.Context
|
||||
import org.thoughtcrime.securesms.ApplicationContext
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.whispersystems.libsignal.loki.LokiSessionResetProtocol
|
||||
import org.whispersystems.libsignal.loki.LokiSessionResetStatus
|
||||
@ -18,7 +19,8 @@ class LokiSessionResetImplementation(private val context: Context) : LokiSession
|
||||
|
||||
override fun onNewSessionAdopted(hexEncodedPublicKey: String, oldSessionResetStatus: LokiSessionResetStatus) {
|
||||
if (oldSessionResetStatus == LokiSessionResetStatus.IN_PROGRESS) {
|
||||
SessionMetaProtocol.sendEphemeralMessage(context, hexEncodedPublicKey)
|
||||
val ephemeralMessage = EphemeralMessage.create(hexEncodedPublicKey)
|
||||
ApplicationContext.getInstance(context).jobManager.add(PushEphemeralMessageSendJob(ephemeralMessage))
|
||||
}
|
||||
// TODO: Show session reset succeed message
|
||||
}
|
||||
|
@ -19,7 +19,6 @@ import javax.inject.Inject
|
||||
class MultiDeviceOpenGroupUpdateJob private constructor(parameters: Parameters) : BaseJob(parameters), InjectableType {
|
||||
|
||||
companion object {
|
||||
|
||||
const val KEY = "MultiDeviceOpenGroupUpdateJob"
|
||||
}
|
||||
|
||||
@ -35,7 +34,7 @@ class MultiDeviceOpenGroupUpdateJob private constructor(parameters: Parameters)
|
||||
|
||||
override fun getFactoryKey(): String { return KEY }
|
||||
|
||||
override fun serialize(): Data { return Data.EMPTY }
|
||||
override fun serialize(): Data { return Data.EMPTY } // TODO: Should we implement this?
|
||||
|
||||
@Throws(Exception::class)
|
||||
public override fun onRun() {
|
||||
|
@ -1,26 +1,28 @@
|
||||
package org.thoughtcrime.securesms.loki.protocol
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import org.thoughtcrime.securesms.ApplicationContext
|
||||
import org.thoughtcrime.securesms.database.Address
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.jobs.PushMediaSendJob
|
||||
import org.thoughtcrime.securesms.jobs.PushSendJob
|
||||
import org.thoughtcrime.securesms.jobs.PushTextSendJob
|
||||
import org.thoughtcrime.securesms.loki.utilities.Broadcaster
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceContent
|
||||
import org.whispersystems.signalservice.loki.api.fileserver.LokiFileServerAPI
|
||||
import org.whispersystems.signalservice.loki.protocol.meta.SessionMetaProtocol
|
||||
import org.whispersystems.signalservice.loki.protocol.multidevice.DeviceLink
|
||||
import org.whispersystems.signalservice.loki.protocol.multidevice.DeviceLinkingSession
|
||||
import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol
|
||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiMessageFriendRequestStatus
|
||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus
|
||||
|
||||
object MultiDeviceProtocol {
|
||||
|
||||
@JvmStatic
|
||||
fun sendUnlinkingRequest(context: Context, publicKey: String) {
|
||||
val unlinkingRequest = EphemeralMessage.createUnlinkingRequest(publicKey)
|
||||
ApplicationContext.getInstance(context).jobManager.add(PushEphemeralMessageSendJob(unlinkingRequest))
|
||||
}
|
||||
// TODO: Closed groups
|
||||
|
||||
enum class MessageType { Text, Media }
|
||||
|
||||
@ -34,7 +36,6 @@ object MultiDeviceProtocol {
|
||||
sendMessagePush(context, recipient, messageID, MessageType.Media)
|
||||
}
|
||||
|
||||
// TODO: Closed groups
|
||||
private fun sendMessagePushToDevice(context: Context, recipient: Recipient, messageID: Long, messageType: MessageType): PushSendJob {
|
||||
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient)
|
||||
val threadFRStatus = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID)
|
||||
@ -92,4 +93,93 @@ object MultiDeviceProtocol {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun handleDeviceLinkMessageIfNeeded(context: Context, deviceLink: DeviceLink, content: SignalServiceContent) {
|
||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||
if (deviceLink.type == DeviceLink.Type.REQUEST) {
|
||||
handleDeviceLinkRequestMessage(context, deviceLink, content)
|
||||
} else if (deviceLink.slaveHexEncodedPublicKey == userPublicKey) {
|
||||
handleDeviceLinkAuthorizedMessage(context, deviceLink, content)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isValidDeviceLinkMessage(context: Context, deviceLink: DeviceLink): Boolean {
|
||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||
val isRequest = (deviceLink.type == DeviceLink.Type.REQUEST)
|
||||
if (deviceLink.requestSignature == null) {
|
||||
Log.d("Loki", "Ignoring device link without a request signature.")
|
||||
return false
|
||||
} else if (isRequest && TextSecurePreferences.getMasterHexEncodedPublicKey(context) != null) {
|
||||
Log.d("Loki", "Ignoring unexpected device link message (the device is a slave device).")
|
||||
return false
|
||||
} else if (isRequest && deviceLink.masterHexEncodedPublicKey != userPublicKey) {
|
||||
Log.d("Loki", "Ignoring device linking message addressed to another user.")
|
||||
return false
|
||||
} else if (isRequest && deviceLink.slaveHexEncodedPublicKey == userPublicKey) {
|
||||
Log.d("Loki", "Ignoring device linking request message from self.")
|
||||
return false
|
||||
}
|
||||
return deviceLink.verify()
|
||||
}
|
||||
|
||||
private fun handleDeviceLinkRequestMessage(context: Context, deviceLink: DeviceLink, content: SignalServiceContent) {
|
||||
val linkingSession = DeviceLinkingSession.shared
|
||||
if (!linkingSession.isListeningForLinkingRequests) {
|
||||
return Broadcaster(context).broadcast("unexpectedDeviceLinkRequestReceived")
|
||||
}
|
||||
val isValid = isValidDeviceLinkMessage(context, deviceLink)
|
||||
if (!isValid) { return }
|
||||
SessionManagementProtocol.handlePreKeyBundleMessageIfNeeded(context, content)
|
||||
linkingSession.processLinkingRequest(deviceLink)
|
||||
}
|
||||
|
||||
private fun handleDeviceLinkAuthorizedMessage(context: Context, deviceLink: DeviceLink, content: SignalServiceContent) {
|
||||
val linkingSession = DeviceLinkingSession.shared
|
||||
if (!linkingSession.isListeningForLinkingRequests) {
|
||||
return
|
||||
}
|
||||
val isValid = isValidDeviceLinkMessage(context, deviceLink)
|
||||
if (!isValid) { return }
|
||||
SessionManagementProtocol.handlePreKeyBundleMessageIfNeeded(context, content)
|
||||
linkingSession.processLinkingAuthorization(deviceLink)
|
||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||
DatabaseFactory.getLokiAPIDatabase(context).clearDeviceLinks(userPublicKey)
|
||||
DatabaseFactory.getLokiAPIDatabase(context).addDeviceLink(deviceLink)
|
||||
TextSecurePreferences.setMasterHexEncodedPublicKey(context, deviceLink.masterHexEncodedPublicKey)
|
||||
TextSecurePreferences.setMultiDevice(context, true)
|
||||
LokiFileServerAPI.shared.addDeviceLink(deviceLink)
|
||||
org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol.handleProfileUpdateIfNeeded(context, content)
|
||||
org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol.handleProfileKeyUpdateIfNeeded(context, content)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun handleUnlinkingRequestIfNeeded(context: Context, content: SignalServiceContent) {
|
||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||
// Check that the request was sent by the user's master device
|
||||
val masterDevicePublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context) ?: return
|
||||
val wasSentByMasterDevice = (content.sender == masterDevicePublicKey)
|
||||
if (!wasSentByMasterDevice) { return }
|
||||
// Ignore the request if we don't know about the device link in question
|
||||
val masterDeviceLinks = DatabaseFactory.getLokiAPIDatabase(context).getDeviceLinks(masterDevicePublicKey)
|
||||
if (masterDeviceLinks.none {
|
||||
it.masterHexEncodedPublicKey == masterDevicePublicKey && it.slaveHexEncodedPublicKey == userPublicKey
|
||||
}) {
|
||||
return
|
||||
}
|
||||
LokiFileServerAPI.shared.getDeviceLinks(userPublicKey, true).success { slaveDeviceLinks ->
|
||||
// Check that the device link IS present on the file server.
|
||||
// Note that the device link as seen from the master device's perspective has been deleted at this point, but the
|
||||
// device link as seen from the slave perspective hasn't.
|
||||
if (slaveDeviceLinks.any {
|
||||
it.masterHexEncodedPublicKey == masterDevicePublicKey && it.slaveHexEncodedPublicKey == userPublicKey
|
||||
}) {
|
||||
for (slaveDeviceLink in slaveDeviceLinks) { // In theory there should only be one
|
||||
LokiFileServerAPI.shared.removeDeviceLink(slaveDeviceLink) // Attempt to clean up on the file server
|
||||
}
|
||||
TextSecurePreferences.setWasUnlinked(context, true)
|
||||
ApplicationContext.getInstance(context).clearData()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,8 +18,7 @@ import java.util.concurrent.TimeUnit
|
||||
class PushEphemeralMessageSendJob private constructor(parameters: Parameters, private val message: EphemeralMessage) : BaseJob(parameters) {
|
||||
|
||||
companion object {
|
||||
private val KEY_MESSAGE = "message"
|
||||
|
||||
private const val KEY_MESSAGE = "message"
|
||||
const val KEY = "PushBackgroundMessageSendJob"
|
||||
}
|
||||
|
||||
@ -32,14 +31,13 @@ class PushEphemeralMessageSendJob private constructor(parameters: Parameters, pr
|
||||
message)
|
||||
|
||||
override fun serialize(): Data {
|
||||
// TODO: Is this correct?
|
||||
return Data.Builder()
|
||||
.putString(KEY_MESSAGE, message.serialize())
|
||||
.build()
|
||||
}
|
||||
|
||||
override fun getFactoryKey(): String {
|
||||
return KEY
|
||||
}
|
||||
override fun getFactoryKey(): String { return KEY }
|
||||
|
||||
public override fun onRun() {
|
||||
val recipient = message.get<String?>("recipient", null) ?: throw IllegalStateException()
|
||||
|
@ -5,8 +5,16 @@ import android.util.Log
|
||||
import org.thoughtcrime.securesms.ApplicationContext
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
||||
import org.thoughtcrime.securesms.crypto.PreKeyUtil
|
||||
import org.thoughtcrime.securesms.crypto.SecurityEvent
|
||||
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.jobs.CleanPreKeysJob
|
||||
import org.thoughtcrime.securesms.loki.utilities.recipient
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.whispersystems.libsignal.loki.LokiSessionResetStatus
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceContent
|
||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus
|
||||
|
||||
|
||||
object SessionManagementProtocol {
|
||||
|
||||
@ -24,8 +32,50 @@ object SessionManagementProtocol {
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun sendSessionRestorationRequest(context: Context, publicKey: String) {
|
||||
val sessionRestorationRequest = EphemeralMessage.createSessionRestorationRequest(publicKey)
|
||||
ApplicationContext.getInstance(context).jobManager.add(PushEphemeralMessageSendJob(sessionRestorationRequest))
|
||||
fun handlePreKeyBundleMessageIfNeeded(context: Context, content: SignalServiceContent) {
|
||||
val recipient = recipient(context, content.sender)
|
||||
if (recipient.isGroupRecipient) { return }
|
||||
val preKeyBundleMessage = content.lokiServiceMessage.orNull()?.preKeyBundleMessage ?: return
|
||||
val registrationID = TextSecurePreferences.getLocalRegistrationId(context) // TODO: It seems wrong to use the local registration ID for this?
|
||||
val lokiPreKeyBundleDatabase = DatabaseFactory.getLokiPreKeyBundleDatabase(context)
|
||||
Log.d("Loki", "Received a pre key bundle from: " + content.sender.toString() + ".")
|
||||
val preKeyBundle = preKeyBundleMessage.getPreKeyBundle(registrationID)
|
||||
lokiPreKeyBundleDatabase.setPreKeyBundle(content.sender, preKeyBundle)
|
||||
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient)
|
||||
val lokiThreadDB = DatabaseFactory.getLokiThreadDatabase(context)
|
||||
val threadFRStatus = lokiThreadDB.getFriendRequestStatus(threadID)
|
||||
// If we received a friend request (i.e. also a new pre key bundle), but we were already friends with the other user, reset the session.
|
||||
if (content.isFriendRequest && threadFRStatus == LokiThreadFriendRequestStatus.FRIENDS) {
|
||||
val sessionStore = TextSecureSessionStore(context)
|
||||
sessionStore.archiveAllSessions(content.sender)
|
||||
val ephemeralMessage = EphemeralMessage.create(content.sender)
|
||||
ApplicationContext.getInstance(context).jobManager.add(PushEphemeralMessageSendJob(ephemeralMessage))
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun handleSessionRequestIfNeeded(context: Context, content: SignalServiceContent) {
|
||||
// Auto-accept all session requests
|
||||
val ephemeralMessage = EphemeralMessage.create(content.sender)
|
||||
ApplicationContext.getInstance(context).jobManager.add(PushEphemeralMessageSendJob(ephemeralMessage))
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun handleEndSessionMessage(context: Context, content: SignalServiceContent) {
|
||||
// TODO: Notify the user
|
||||
val sessionStore = TextSecureSessionStore(context)
|
||||
val lokiThreadDB = DatabaseFactory.getLokiThreadDatabase(context)
|
||||
Log.d("Loki", "Received a session reset request from: ${content.sender}; archiving the session.")
|
||||
sessionStore.archiveAllSessions(content.sender)
|
||||
lokiThreadDB.setSessionResetStatus(content.sender, LokiSessionResetStatus.REQUEST_RECEIVED)
|
||||
Log.d("Loki", "Sending an ephemeral message back to: ${content.sender}.")
|
||||
val ephemeralMessage = EphemeralMessage.create(content.sender)
|
||||
ApplicationContext.getInstance(context).jobManager.add(PushEphemeralMessageSendJob(ephemeralMessage))
|
||||
SecurityEvent.broadcastSecurityUpdateEvent(context)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
private fun isSessionRequest(content: SignalServiceContent): Boolean {
|
||||
return content.dataMessage.isPresent && content.dataMessage.get().isSessionRequest
|
||||
}
|
||||
}
|
@ -5,14 +5,38 @@ import org.thoughtcrime.securesms.ApplicationContext
|
||||
import org.thoughtcrime.securesms.database.Address
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceContent
|
||||
import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol
|
||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus
|
||||
|
||||
object SessionMetaProtocol {
|
||||
|
||||
@JvmStatic
|
||||
fun sendEphemeralMessage(context: Context, publicKey: String) {
|
||||
val ephemeralMessage = EphemeralMessage.create(publicKey)
|
||||
ApplicationContext.getInstance(context).jobManager.add(PushEphemeralMessageSendJob(ephemeralMessage))
|
||||
fun handleProfileUpdateIfNeeded(context: Context, content: SignalServiceContent) {
|
||||
val rawDisplayName = content.senderDisplayName.orNull() ?: return
|
||||
if (rawDisplayName.isBlank()) { return }
|
||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||
val userMasterPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context)
|
||||
val sender = content.sender.toLowerCase()
|
||||
if (userMasterPublicKey == sender) {
|
||||
// Update the user's local name if the message came from their master device
|
||||
TextSecurePreferences.setProfileName(context, rawDisplayName)
|
||||
}
|
||||
// Don't overwrite if the message came from a linked device; the device name is
|
||||
// stored as a user name
|
||||
val allUserDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(userPublicKey)
|
||||
if (!allUserDevices.contains(sender)) {
|
||||
val displayName = rawDisplayName + " (..." + sender.substring(sender.length - 8) + ")"
|
||||
DatabaseFactory.getLokiUserDatabase(context).setDisplayName(sender, displayName)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun handleProfileKeyUpdateIfNeeded(context: Context, content: SignalServiceContent) {
|
||||
val userMasterPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context)
|
||||
if (userMasterPublicKey != content.sender) { return }
|
||||
ApplicationContext.getInstance(context).updatePublicChatProfilePictureIfNeeded()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -10,12 +10,24 @@ import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob
|
||||
import org.thoughtcrime.securesms.jobs.MultiDeviceGroupUpdateJob
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol
|
||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus
|
||||
import org.whispersystems.signalservice.loki.utilities.PublicKeyValidation
|
||||
import java.util.*
|
||||
|
||||
object SyncMessagesProtocol {
|
||||
|
||||
@JvmStatic
|
||||
fun shouldIgnoreSyncMessage(context: Context, sender: Recipient): Boolean {
|
||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||
return MultiDeviceProtocol.shared.getAllLinkedDevices(userPublicKey).contains(sender.address.serialize())
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun syncContact(context: Context, address: Address) {
|
||||
ApplicationContext.getInstance(context).jobManager.add(MultiDeviceContactUpdateJob(context, address, true))
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun syncAllContacts(context: Context) {
|
||||
ApplicationContext.getInstance(context).jobManager.add(MultiDeviceContactUpdateJob(context, true))
|
||||
|
@ -0,0 +1,9 @@
|
||||
package org.thoughtcrime.securesms.loki.utilities
|
||||
|
||||
import android.content.Context
|
||||
import org.thoughtcrime.securesms.database.Address
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
|
||||
fun recipient(context: Context, publicKey: String): Recipient {
|
||||
return Recipient.from(context, Address.fromSerialized(publicKey), false)
|
||||
}
|
@ -3,7 +3,7 @@ package org.thoughtcrime.securesms.push;
|
||||
import android.content.Context;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.SecurityEvent;
|
||||
import org.thoughtcrime.securesms.loki.FriendRequestHandler;
|
||||
import org.thoughtcrime.securesms.loki.protocol.FriendRequestProtocol;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
|
||||
@ -23,14 +23,14 @@ public class MessageSenderEventListener implements SignalServiceMessageSender.Ev
|
||||
}
|
||||
|
||||
@Override public void onFriendRequestSending(long messageID, long threadID) {
|
||||
FriendRequestHandler.updateFriendRequestState(context, FriendRequestHandler.ActionType.Sending, messageID, threadID);
|
||||
FriendRequestProtocol.setFriendRequestStatusToSendingIfNeeded(context, messageID, threadID);
|
||||
}
|
||||
|
||||
@Override public void onFriendRequestSent(long messageID, long threadID) {
|
||||
FriendRequestHandler.updateFriendRequestState(context, FriendRequestHandler.ActionType.Sent, messageID, threadID);
|
||||
FriendRequestProtocol.setFriendRequestStatusToSentIfNeeded(context, messageID, threadID);
|
||||
}
|
||||
|
||||
@Override public void onFriendRequestSendingFailed(long messageID, long threadID) {
|
||||
FriendRequestHandler.updateFriendRequestState(context, FriendRequestHandler.ActionType.Failed, messageID, threadID);
|
||||
FriendRequestProtocol.setFriendRequestStatusToFailedIfNeeded(context, messageID, threadID);
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ public class WelcomeActivity extends BaseActionBarActivity {
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
if (TextSecurePreferences.setNeedsDatabaseResetFromUnlink(this)) {
|
||||
if (TextSecurePreferences.getWasUnlinked(this)) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle(R.string.dialog_device_unlink_title);
|
||||
builder.setMessage(R.string.dialog_device_unlink_message);
|
||||
@ -36,7 +36,7 @@ public class WelcomeActivity extends BaseActionBarActivity {
|
||||
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialog) {
|
||||
TextSecurePreferences.setNeedDatabaseResetFromUnlink(getBaseContext(), false);
|
||||
TextSecurePreferences.setWasUnlinked(getBaseContext(), false);
|
||||
}
|
||||
});
|
||||
builder.show();
|
||||
|
@ -1264,12 +1264,12 @@ public class TextSecurePreferences {
|
||||
return getBooleanPreference(context, "database_reset", false);
|
||||
}
|
||||
|
||||
public static void setNeedDatabaseResetFromUnlink(Context context, boolean value) {
|
||||
public static void setWasUnlinked(Context context, boolean value) {
|
||||
// We do it this way so that it gets persisted in storage straight away
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean("database_reset_unpair", value).commit();
|
||||
}
|
||||
|
||||
public static boolean setNeedsDatabaseResetFromUnlink(Context context) {
|
||||
public static boolean getWasUnlinked(Context context) {
|
||||
return getBooleanPreference(context, "database_reset_unpair", false);
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user