mirror of
https://github.com/oxen-io/session-android.git
synced 2025-05-05 11:00:51 +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() {
|
public void checkNeedsDatabaseReset() {
|
||||||
if (TextSecurePreferences.getNeedsDatabaseReset(this)) {
|
if (TextSecurePreferences.getNeedsDatabaseReset(this)) {
|
||||||
boolean wasUnlinked = TextSecurePreferences.setNeedsDatabaseResetFromUnlink(this);
|
boolean wasUnlinked = TextSecurePreferences.getWasUnlinked(this);
|
||||||
TextSecurePreferences.clearAll(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);
|
MasterSecretUtil.clear(this);
|
||||||
if (this.deleteDatabase("signal.db")) {
|
if (this.deleteDatabase("signal.db")) {
|
||||||
Log.d("Loki", "Deleted database");
|
Log.d("Loki", "Deleted database");
|
||||||
|
@ -99,7 +99,7 @@ public class GroupMessageProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Loki - Ignore message if needed
|
// Loki - Ignore message if needed
|
||||||
if (ClosedGroupsProtocol.shouldIgnoreMessage(context, group)) {
|
if (ClosedGroupsProtocol.shouldIgnoreGroupCreatedMessage(context, group)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,7 +80,7 @@ public class MultiDeviceContactUpdateJob extends BaseJob implements InjectableTy
|
|||||||
this(context, address, true);
|
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()
|
this(new Job.Parameters.Builder()
|
||||||
.addConstraint(NetworkConstraint.KEY)
|
.addConstraint(NetworkConstraint.KEY)
|
||||||
.setQueue("MultiDeviceContactUpdateJob")
|
.setQueue("MultiDeviceContactUpdateJob")
|
||||||
|
@ -68,15 +68,18 @@ import org.thoughtcrime.securesms.linkpreview.Link;
|
|||||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
||||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
|
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
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.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.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.Broadcaster;
|
||||||
|
import org.thoughtcrime.securesms.loki.utilities.MentionManagerUtilities;
|
||||||
import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities;
|
import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities;
|
||||||
import org.thoughtcrime.securesms.mms.IncomingMediaMessage;
|
import org.thoughtcrime.securesms.mms.IncomingMediaMessage;
|
||||||
import org.thoughtcrime.securesms.mms.MmsException;
|
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.meta.LokiServiceMessage;
|
||||||
import org.whispersystems.signalservice.loki.protocol.multidevice.DeviceLink;
|
import org.whispersystems.signalservice.loki.protocol.multidevice.DeviceLink;
|
||||||
import org.whispersystems.signalservice.loki.protocol.multidevice.DeviceLinkingSession;
|
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.LokiMessageFriendRequestStatus;
|
||||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus;
|
import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus;
|
||||||
import org.whispersystems.signalservice.loki.utilities.PromiseUtil;
|
import org.whispersystems.signalservice.loki.utilities.PromiseUtil;
|
||||||
@ -285,9 +288,9 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
|
|
||||||
SignalServiceContent content = cipher.decrypt(envelope);
|
SignalServiceContent content = cipher.decrypt(envelope);
|
||||||
|
|
||||||
// Loki - Ignore any friend requests that we got before restoration
|
// Loki - Ignore any friend requests from before restoration
|
||||||
if (content.isFriendRequest() && content.getTimestamp() < TextSecurePreferences.getRestorationTime(context)) {
|
if (FriendRequestProtocol.isFriendRequestFromBeforeRestoration(content)) {
|
||||||
Log.d("Loki", "Ignoring friend request received before restoration.");
|
Log.d("Loki", "Ignoring friend request from before restoration.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -297,18 +300,15 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Loki - Handle friend request acceptance if needed
|
// Loki - Handle friend request acceptance if needed
|
||||||
if (!content.isFriendRequest() && !isGroupChatMessage(content)) {
|
FriendRequestProtocol.handleFriendRequestAcceptanceIfNeeded(content);
|
||||||
becomeFriendsWithContactIfNeeded(content.getSender(), true, false);
|
|
||||||
}
|
// Loki - Handle pre key bundle message if needed
|
||||||
|
SessionManagementProtocol.handlePreKeyBundleMessageIfNeeded(content);
|
||||||
|
|
||||||
// Loki - Handle session request if needed
|
// Loki - Handle session request if needed
|
||||||
handleSessionRequestIfNeeded(content);
|
SessionManagementProtocol.handleSessionRequestIfNeeded(content);
|
||||||
|
|
||||||
// Loki - Store pre key bundle if needed
|
|
||||||
if (!content.getDeviceLink().isPresent()) {
|
|
||||||
storePreKeyBundleIfNeeded(content);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Loki - Handle address message if needed
|
||||||
if (content.lokiServiceMessage.isPresent()) {
|
if (content.lokiServiceMessage.isPresent()) {
|
||||||
LokiServiceMessage lokiMessage = content.lokiServiceMessage.get();
|
LokiServiceMessage lokiMessage = content.lokiServiceMessage.get();
|
||||||
if (lokiMessage.getAddressMessage() != null) {
|
if (lokiMessage.getAddressMessage() != null) {
|
||||||
@ -316,47 +316,33 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loki - Store the sender display name if needed
|
// Loki - Handle profile update if needed
|
||||||
Optional<String> rawSenderDisplayName = content.senderDisplayName;
|
SessionMetaProtocol.handleProfileUpdateIfNeeded(content);
|
||||||
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;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (content.getDeviceLink().isPresent()) {
|
if (content.getDeviceLink().isPresent()) {
|
||||||
handleDeviceLinkMessage(content.getDeviceLink().get(), content);
|
MultiDeviceProtocol.handleDeviceLinkMessageIfNeeded(content);
|
||||||
} else if (content.getDataMessage().isPresent()) {
|
} else if (content.getDataMessage().isPresent()) {
|
||||||
SignalServiceDataMessage message = content.getDataMessage().get();
|
SignalServiceDataMessage message = content.getDataMessage().get();
|
||||||
boolean isMediaMessage = message.getAttachments().isPresent() || message.getQuote().isPresent() || message.getSharedContacts().isPresent() || message.getPreviews().isPresent() || message.getSticker().isPresent();
|
boolean isMediaMessage = message.getAttachments().isPresent() || message.getQuote().isPresent() || message.getSharedContacts().isPresent() || message.getPreviews().isPresent() || message.getSticker().isPresent();
|
||||||
|
|
||||||
if (!content.isFriendRequest() && message.isUnlinkingRequest()) {
|
// Loki - Handle unlinking request if needed
|
||||||
// Make sure we got the request from our master device
|
if (message.isUnlinkingRequest()) {
|
||||||
String ourMasterDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
|
MultiDeviceProtocol.handleUnlinkingRequest(message);
|
||||||
if (ourMasterDevice != null && ourMasterDevice.equals(content.getSender())) {
|
|
||||||
TextSecurePreferences.setNeedDatabaseResetFromUnlink(context, true);
|
|
||||||
MultiDeviceUtilities.checkIsRevokedSlaveDevice(context);
|
|
||||||
}
|
|
||||||
} else {
|
} 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.isSessionRestorationRequest() || message.isSessionRequest()) { return; }
|
||||||
|
|
||||||
if (message.isEndSession()) handleEndSessionMessage(content, smsMessageId);
|
if (message.isEndSession()) {
|
||||||
else if (message.isGroupUpdate()) handleGroupMessage(content, message, smsMessageId);
|
handleEndSessionMessage(content, smsMessageId);
|
||||||
else if (message.isExpirationUpdate())
|
} else if (message.isGroupUpdate()) {
|
||||||
|
handleGroupMessage(content, message, smsMessageId);
|
||||||
|
} else if (message.isExpirationUpdate()) {
|
||||||
handleExpirationUpdate(content, message, smsMessageId);
|
handleExpirationUpdate(content, message, smsMessageId);
|
||||||
else if (isMediaMessage)
|
} else if (isMediaMessage) {
|
||||||
handleMediaMessage(content, message, smsMessageId, Optional.absent());
|
handleMediaMessage(content, message, smsMessageId, Optional.absent());
|
||||||
else if (message.getBody().isPresent())
|
} else if (message.getBody().isPresent()) {
|
||||||
handleTextMessage(content, message, smsMessageId, Optional.absent());
|
handleTextMessage(content, message, smsMessageId, Optional.absent());
|
||||||
|
}
|
||||||
|
|
||||||
if (message.getGroupInfo().isPresent() && groupDatabase.isUnknownGroup(GroupUtil.getEncodedId(message.getGroupInfo().get()))) {
|
if (message.getGroupInfo().isPresent() && groupDatabase.isUnknownGroup(GroupUtil.getEncodedId(message.getGroupInfo().get()))) {
|
||||||
handleUnknownGroupMessage(content, message.getGroupInfo().get());
|
handleUnknownGroupMessage(content, message.getGroupInfo().get());
|
||||||
@ -366,25 +352,12 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
handleProfileKey(content, message);
|
handleProfileKey(content, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loki - This doesn't get invoked for group chats
|
|
||||||
if (content.isNeedsReceipt()) {
|
if (content.isNeedsReceipt()) {
|
||||||
handleNeedsDeliveryReceipt(content, message);
|
handleNeedsDeliveryReceipt(content, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we received a friend request, but we were already friends with the user, reset the session
|
// Loki - Handle friend request message if needed
|
||||||
if (content.isFriendRequest() && !message.isGroupMessage()) {
|
FriendRequestProtocol.handleFriendRequestMessageIfNeeded(content);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
} else if (content.getSyncMessage().isPresent()) {
|
} else if (content.getSyncMessage().isPresent()) {
|
||||||
TextSecurePreferences.setMultiDevice(context, true);
|
TextSecurePreferences.setMultiDevice(context, true);
|
||||||
@ -557,26 +530,11 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (threadId != null) {
|
if (threadId != null) {
|
||||||
resetSession(content.getSender());
|
SessionManagementProtocol.handleEndSessionMessage(content);
|
||||||
MessageNotifier.updateNotification(context, threadId);
|
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)
|
private long handleSynchronizeSentEndSessionMessage(@NonNull SentTranscriptMessage message)
|
||||||
{
|
{
|
||||||
SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
|
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,
|
private void handleSynchronizeSentMessage(@NonNull SignalServiceContent content,
|
||||||
@NonNull SentTranscriptMessage message)
|
@NonNull SentTranscriptMessage message)
|
||||||
throws StorageFailedException
|
throws StorageFailedException
|
||||||
@ -800,8 +672,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
handleUnknownGroupMessage(content, message.getMessage().getGroupInfo().get());
|
handleUnknownGroupMessage(content, message.getMessage().getGroupInfo().get());
|
||||||
}
|
}
|
||||||
|
|
||||||
String ourMasterDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
|
|
||||||
boolean isSenderMasterDevice = ourMasterDevice != null && ourMasterDevice.equals(content.getSender());
|
|
||||||
if (message.getMessage().getProfileKey().isPresent()) {
|
if (message.getMessage().getProfileKey().isPresent()) {
|
||||||
Recipient recipient = null;
|
Recipient recipient = null;
|
||||||
|
|
||||||
@ -813,16 +683,12 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true);
|
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
|
// Loki - Handle profile key update if needed
|
||||||
if (isSenderMasterDevice) {
|
|
||||||
handleProfileKey(content, message.getMessage());
|
handleProfileKey(content, message.getMessage());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Loki - Update display name from master device
|
// Loki - Update profile if needed
|
||||||
if (isSenderMasterDevice && content.senderDisplayName.isPresent() && content.senderDisplayName.get().length() > 0) {
|
SessionMetaProtocol.handleProfileUpdateIfNeeded(content);
|
||||||
TextSecurePreferences.setProfileName(context, content.senderDisplayName.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (threadId != null) {
|
if (threadId != null) {
|
||||||
DatabaseFactory.getThreadDatabase(context).setRead(threadId, true);
|
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<List<LinkPreview>> linkPreviews = getLinkPreviews(message.getPreviews(), message.getBody().or(""));
|
||||||
Optional<Attachment> sticker = getStickerAttachment(message.getSticker());
|
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()) {
|
if (message.isGroupMessage()) {
|
||||||
sender = getMasterRecipient(content.getSender()).getAddress();
|
masterAddress = getMasterRecipient(content.getSender()).getAddress();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore messages from ourselves
|
IncomingMediaMessage mediaMessage = new IncomingMediaMessage(masterAddress, message.getTimestamp(), -1,
|
||||||
if (sender.serialize().equalsIgnoreCase(TextSecurePreferences.getLocalNumber(context))) { return; }
|
|
||||||
|
|
||||||
IncomingMediaMessage mediaMessage = new IncomingMediaMessage(sender, message.getTimestamp(), -1,
|
|
||||||
message.getExpiresInSeconds() * 1000L, false, content.isNeedsReceipt(), message.getBody(), message.getGroupInfo(), message.getAttachments(),
|
message.getExpiresInSeconds() * 1000L, false, content.isNeedsReceipt(), message.getBody(), message.getGroupInfo(), message.getAttachments(),
|
||||||
quote, sharedContacts, linkPreviews, sticker);
|
quote, sharedContacts, linkPreviews, sticker);
|
||||||
|
|
||||||
@ -967,19 +829,16 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
MessageNotifier.updateNotification(context, insertResult.get().getThreadId());
|
MessageNotifier.updateNotification(context, insertResult.get().getThreadId());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loki - Run database updates in the background, we should look into fixing this in the future
|
// Loki - Store message server ID if needed
|
||||||
AsyncTask.execute(() -> {
|
|
||||||
// Loki - Store message server ID
|
|
||||||
updateGroupChatMessageServerID(messageServerIDOrNull, insertResult);
|
updateGroupChatMessageServerID(messageServerIDOrNull, insertResult);
|
||||||
|
|
||||||
// Loki - Update mapping of message to original thread ID
|
// Loki - Update mapping of message ID to original thread ID
|
||||||
if (insertResult.isPresent()) {
|
if (insertResult.isPresent()) {
|
||||||
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
|
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
|
||||||
LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context);
|
LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context);
|
||||||
long originalThreadId = threadDatabase.getThreadIdFor(originalRecipient);
|
long originalThreadId = threadDatabase.getThreadIdFor(originalRecipient);
|
||||||
lokiMessageDatabase.setOriginalThreadID(insertResult.get().getMessageId(), originalThreadId);
|
lokiMessageDatabase.setOriginalThreadID(insertResult.get().getMessageId(), originalThreadId);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private long handleSynchronizeSentExpirationUpdate(@NonNull SentTranscriptMessage message) throws MmsException {
|
private long handleSynchronizeSentExpirationUpdate(@NonNull SentTranscriptMessage message) throws MmsException {
|
||||||
@ -1108,14 +967,10 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
|
|
||||||
Address sender = masterRecipient.getAddress();
|
Address sender = masterRecipient.getAddress();
|
||||||
|
|
||||||
// If message is from group then we need to map it to get the sender of the message
|
|
||||||
if (message.isGroupMessage()) {
|
if (message.isGroupMessage()) {
|
||||||
sender = getMasterRecipient(content.getSender()).getAddress();
|
sender = getMasterRecipient(content.getSender()).getAddress();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore messages from ourselves
|
|
||||||
if (sender.serialize().equalsIgnoreCase(TextSecurePreferences.getLocalNumber(context))) { return; }
|
|
||||||
|
|
||||||
IncomingTextMessage tm = new IncomingTextMessage(sender,
|
IncomingTextMessage tm = new IncomingTextMessage(sender,
|
||||||
content.getSenderDevice(),
|
content.getSenderDevice(),
|
||||||
message.getTimestamp(), body,
|
message.getTimestamp(), body,
|
||||||
@ -1141,13 +996,12 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
MessageNotifier.updateNotification(context, threadId);
|
MessageNotifier.updateNotification(context, threadId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loki - Run database updates in background, we should look into fixing this in the future
|
|
||||||
AsyncTask.execute(() -> {
|
|
||||||
if (insertResult.isPresent()) {
|
if (insertResult.isPresent()) {
|
||||||
InsertResult result = insertResult.get();
|
InsertResult result = insertResult.get();
|
||||||
|
|
||||||
// Loki - Cache the user hex encoded public key (for mentions)
|
// Loki - Cache the user hex encoded public key (for mentions)
|
||||||
MentionManagerUtilities.INSTANCE.populateUserHexEncodedPublicKeyCacheIfNeeded(result.getThreadId(), context);
|
MentionManagerUtilities.INSTANCE.populateUserHexEncodedPublicKeyCacheIfNeeded(result.getThreadId(), context);
|
||||||
MentionsManager.INSTANCE.cache(textMessage.getSender().serialize(), result.getThreadId());
|
MentionsManager.shared.cache(textMessage.getSender().serialize(), result.getThreadId());
|
||||||
|
|
||||||
// Loki - Store message server ID
|
// Loki - Store message server ID
|
||||||
updateGroupChatMessageServerID(messageServerIDOrNull, insertResult);
|
updateGroupChatMessageServerID(messageServerIDOrNull, insertResult);
|
||||||
@ -1160,231 +1014,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
lokiMessageDatabase.setOriginalThreadID(result.getMessageId(), originalThreadId);
|
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,
|
private void handleCorruptMessage(@NonNull String sender, int senderDevice, long timestamp,
|
||||||
@NonNull Optional<Long> smsMessageId)
|
@NonNull Optional<Long> smsMessageId)
|
||||||
{
|
{
|
||||||
@ -1521,14 +1135,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
triggerSessionRestorePrompt(sender);
|
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,
|
private void handleLegacyMessage(@NonNull String sender, int senderDevice, long timestamp,
|
||||||
@NonNull Optional<Long> smsMessageId)
|
@NonNull Optional<Long> smsMessageId)
|
||||||
{
|
{
|
||||||
@ -1580,10 +1186,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
String url = content.senderProfilePictureURL.or("");
|
String url = content.senderProfilePictureURL.or("");
|
||||||
ApplicationContext.getInstance(context).getJobManager().add(new RetrieveProfileAvatarJob(recipient, url));
|
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
|
SessionMetaProtocol.handleProfileKeyUpdateIfNeeded(content, message);
|
||||||
if (recipient.isUserMasterDevice()) {
|
|
||||||
ApplicationContext.getInstance(context).updatePublicChatProfilePictureIfNeeded();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1647,7 +1250,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
long threadId;
|
long threadId;
|
||||||
|
|
||||||
if (typingMessage.getGroupId().isPresent()) {
|
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));
|
Address groupAddress = Address.fromSerialized(GroupUtil.getEncodedId(typingMessage.getGroupId().get(), false));
|
||||||
Recipient groupRecipient = Recipient.from(context, groupAddress, 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) {
|
private void notifyTypingStoppedFromIncomingMessage(@NonNull Recipient conversationRecipient, @NonNull String sender, int device) {
|
||||||
Recipient author = Recipient.from(context, Address.fromSerialized(sender), false);
|
Recipient author = Recipient.from(context, Address.fromSerialized(sender), false);
|
||||||
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(conversationRecipient);
|
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 isGroupActive = groupId.isPresent() && groupDatabase.isActive(groupId.get());
|
||||||
boolean isLeaveMessage = message.getGroupInfo().isPresent() && message.getGroupInfo().get().getType() == SignalServiceGroup.Type.QUIT;
|
boolean isLeaveMessage = message.getGroupInfo().isPresent() && message.getGroupInfo().get().getType() == SignalServiceGroup.Type.QUIT;
|
||||||
|
|
||||||
boolean isClosedGroup = conversation.getAddress().isClosedGroup();
|
boolean shouldIgnoreContentMessage = ClosedGroupsProtocol.shouldIgnoreContentMessage(context, conversation, groupId.orNull(), content);
|
||||||
boolean isGroupMember = true;
|
return (isContentMessage && !isGroupActive) || (sender.isBlocked() && !isLeaveMessage) || (isContentMessage && !shouldIgnoreContentMessage);
|
||||||
|
|
||||||
// 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);
|
|
||||||
} else {
|
} else {
|
||||||
return sender.isBlocked();
|
return sender.isBlocked();
|
||||||
}
|
}
|
||||||
} else if (content.getCallMessage().isPresent() || content.getTypingMessage().isPresent()) {
|
} else if (content.getCallMessage().isPresent() || content.getTypingMessage().isPresent()) {
|
||||||
return sender.isBlocked();
|
return sender.isBlocked();
|
||||||
} else if (content.getSyncMessage().isPresent()) {
|
} else if (content.getSyncMessage().isPresent()) {
|
||||||
try {
|
return SyncMessagesProtocol.shouldIgnoreSyncMessage(context, sender);
|
||||||
// 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 false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isSessionRequest(SignalServiceContent content) {
|
|
||||||
return content.getDataMessage().isPresent() && content.getDataMessage().get().isSessionRequest();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isGroupChatMessage(SignalServiceContent content) {
|
private boolean isGroupChatMessage(SignalServiceContent content) {
|
||||||
return content.getDataMessage().isPresent() && content.getDataMessage().get().isGroupMessage();
|
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() }
|
registerButton.setOnClickListener { register() }
|
||||||
restoreButton.setOnClickListener { restore() }
|
restoreButton.setOnClickListener { restore() }
|
||||||
linkButton.setOnClickListener { linkDevice() }
|
linkButton.setOnClickListener { linkDevice() }
|
||||||
if (TextSecurePreferences.setNeedsDatabaseResetFromUnlink(this)) {
|
if (TextSecurePreferences.getWasUnlinked(this)) {
|
||||||
Toast.makeText(this, "Your device was unlinked successfully", Toast.LENGTH_LONG).show()
|
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.crypto.storage.TextSecureSessionStore
|
||||||
import org.thoughtcrime.securesms.database.Address
|
import org.thoughtcrime.securesms.database.Address
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
|
import org.thoughtcrime.securesms.loki.utilities.recipient
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
import org.thoughtcrime.securesms.sms.MessageSender
|
import org.thoughtcrime.securesms.sms.MessageSender
|
||||||
import org.thoughtcrime.securesms.util.GroupUtil
|
import org.thoughtcrime.securesms.util.GroupUtil
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||||
import org.whispersystems.libsignal.SignalProtocolAddress
|
import org.whispersystems.libsignal.SignalProtocolAddress
|
||||||
|
import org.whispersystems.signalservice.api.messages.SignalServiceContent
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceGroup
|
import org.whispersystems.signalservice.api.messages.SignalServiceGroup
|
||||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
||||||
import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol
|
import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol
|
||||||
@ -17,10 +19,20 @@ import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDevicePro
|
|||||||
object ClosedGroupsProtocol {
|
object ClosedGroupsProtocol {
|
||||||
|
|
||||||
@JvmStatic
|
@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 members = group.members
|
||||||
val masterDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context)
|
val userMasterDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context)
|
||||||
return !members.isPresent || !members.get().contains(masterDevice)
|
return !members.isPresent || !members.get().contains(userMasterDevice)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@ -31,19 +43,20 @@ object ClosedGroupsProtocol {
|
|||||||
result.add(Address.fromSerialized(groupID))
|
result.add(Address.fromSerialized(groupID))
|
||||||
return result
|
return result
|
||||||
} else {
|
} else {
|
||||||
|
// A closed group's members should never include slave devices
|
||||||
val members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(groupID, false)
|
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) }
|
MultiDeviceProtocol.shared.getAllLinkedDevices(member.address.serialize()).map { Address.fromSerialized(it) }
|
||||||
}.toMutableSet()
|
}.toMutableSet()
|
||||||
val masterPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context)
|
val userMasterPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context)
|
||||||
if (masterPublicKey != null && recipients.contains(Address.fromSerialized(masterPublicKey))) {
|
if (userMasterPublicKey != null && destinations.contains(Address.fromSerialized(userMasterPublicKey))) {
|
||||||
recipients.remove(Address.fromSerialized(masterPublicKey))
|
destinations.remove(Address.fromSerialized(userMasterPublicKey))
|
||||||
}
|
}
|
||||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||||
if (userPublicKey != null && recipients.contains(Address.fromSerialized(userPublicKey))) {
|
if (userPublicKey != null && destinations.contains(Address.fromSerialized(userPublicKey))) {
|
||||||
recipients.remove(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)
|
val message = GroupUtil.createGroupLeaveMessage(context, recipient)
|
||||||
if (threadID < 0 || !message.isPresent) { return false }
|
if (threadID < 0 || !message.isPresent) { return false }
|
||||||
MessageSender.send(context, message.get(), threadID, false, null)
|
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 masterPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context)
|
||||||
val publicKeyToUse = masterPublicKey ?: TextSecurePreferences.getLocalNumber(context)
|
val publicKeyToRemove = masterPublicKey ?: TextSecurePreferences.getLocalNumber(context)
|
||||||
val groupDatabase = DatabaseFactory.getGroupDatabase(context)
|
val groupDatabase = DatabaseFactory.getGroupDatabase(context)
|
||||||
val groupID = recipient.address.toGroupString()
|
val groupID = recipient.address.toGroupString()
|
||||||
groupDatabase.setActive(groupID, false)
|
groupDatabase.setActive(groupID, false)
|
||||||
groupDatabase.remove(groupID, Address.fromSerialized(publicKeyToUse))
|
groupDatabase.remove(groupID, Address.fromSerialized(publicKeyToRemove))
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun establishSessionsWithMembersIfNeeded(context: Context, members: List<String>) {
|
fun establishSessionsWithMembersIfNeeded(context: Context, members: List<String>) {
|
||||||
|
// A closed group's members should never include slave devices
|
||||||
val allDevices = members.flatMap { member ->
|
val allDevices = members.flatMap { member ->
|
||||||
MultiDeviceProtocol.shared.getAllLinkedDevices(member)
|
MultiDeviceProtocol.shared.getAllLinkedDevices(member)
|
||||||
}.toMutableSet()
|
}.toMutableSet()
|
||||||
val masterPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context)
|
val userMasterPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context)
|
||||||
if (masterPublicKey != null && allDevices.contains(masterPublicKey)) {
|
if (userMasterPublicKey != null && allDevices.contains(userMasterPublicKey)) {
|
||||||
allDevices.remove(masterPublicKey)
|
allDevices.remove(userMasterPublicKey)
|
||||||
}
|
}
|
||||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||||
if (userPublicKey != null && allDevices.contains(userPublicKey)) {
|
if (userPublicKey != null && allDevices.contains(userPublicKey)) {
|
||||||
allDevices.remove(userPublicKey)
|
allDevices.remove(userPublicKey)
|
||||||
}
|
}
|
||||||
for (device in allDevices) {
|
for (device in allDevices) {
|
||||||
val address = SignalProtocolAddress(device, SignalServiceAddress.DEFAULT_DEVICE_ID)
|
val deviceAsAddress = SignalProtocolAddress(device, SignalServiceAddress.DEFAULT_DEVICE_ID)
|
||||||
val hasSession = TextSecureSessionStore(context).containsSession(address)
|
val hasSession = TextSecureSessionStore(context).containsSession(deviceAsAddress)
|
||||||
if (!hasSession) { sendSessionRequest(context, device) }
|
if (hasSession) { continue }
|
||||||
}
|
val sessionRequest = EphemeralMessage.createSessionRequest(device)
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun sendSessionRequest(context: Context, publicKey: String) {
|
|
||||||
val sessionRequest = EphemeralMessage.createSessionRequest(publicKey)
|
|
||||||
ApplicationContext.getInstance(context).jobManager.add(PushEphemeralMessageSendJob(sessionRequest))
|
ApplicationContext.getInstance(context).jobManager.add(PushEphemeralMessageSendJob(sessionRequest))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
@ -1,15 +1,122 @@
|
|||||||
package org.thoughtcrime.securesms.loki.protocol
|
package org.thoughtcrime.securesms.loki.protocol
|
||||||
|
|
||||||
import android.content.Context
|
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.database.DatabaseFactory
|
||||||
|
import org.thoughtcrime.securesms.loki.utilities.recipient
|
||||||
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage
|
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage
|
||||||
import org.thoughtcrime.securesms.sms.OutgoingTextMessage
|
import org.thoughtcrime.securesms.sms.OutgoingTextMessage
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
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.LokiMessageFriendRequestStatus
|
||||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus
|
import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus
|
||||||
|
|
||||||
object FriendRequestProtocol {
|
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
|
@JvmStatic
|
||||||
fun shouldUpdateFriendRequestStatusFromOutgoingTextMessage(context: Context, message: OutgoingTextMessage): Boolean {
|
fun shouldUpdateFriendRequestStatusFromOutgoingTextMessage(context: Context, message: OutgoingTextMessage): Boolean {
|
||||||
// The order of these checks matters
|
// The order of these checks matters
|
||||||
@ -43,4 +150,34 @@ object FriendRequestProtocol {
|
|||||||
threadDB.setFriendRequestStatus(threadID, LokiThreadFriendRequestStatus.REQUEST_SENDING)
|
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
|
package org.thoughtcrime.securesms.loki.protocol
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import org.thoughtcrime.securesms.ApplicationContext
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
import org.whispersystems.libsignal.loki.LokiSessionResetProtocol
|
import org.whispersystems.libsignal.loki.LokiSessionResetProtocol
|
||||||
import org.whispersystems.libsignal.loki.LokiSessionResetStatus
|
import org.whispersystems.libsignal.loki.LokiSessionResetStatus
|
||||||
@ -18,7 +19,8 @@ class LokiSessionResetImplementation(private val context: Context) : LokiSession
|
|||||||
|
|
||||||
override fun onNewSessionAdopted(hexEncodedPublicKey: String, oldSessionResetStatus: LokiSessionResetStatus) {
|
override fun onNewSessionAdopted(hexEncodedPublicKey: String, oldSessionResetStatus: LokiSessionResetStatus) {
|
||||||
if (oldSessionResetStatus == LokiSessionResetStatus.IN_PROGRESS) {
|
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
|
// TODO: Show session reset succeed message
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,6 @@ import javax.inject.Inject
|
|||||||
class MultiDeviceOpenGroupUpdateJob private constructor(parameters: Parameters) : BaseJob(parameters), InjectableType {
|
class MultiDeviceOpenGroupUpdateJob private constructor(parameters: Parameters) : BaseJob(parameters), InjectableType {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
const val KEY = "MultiDeviceOpenGroupUpdateJob"
|
const val KEY = "MultiDeviceOpenGroupUpdateJob"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,7 +34,7 @@ class MultiDeviceOpenGroupUpdateJob private constructor(parameters: Parameters)
|
|||||||
|
|
||||||
override fun getFactoryKey(): String { return KEY }
|
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)
|
@Throws(Exception::class)
|
||||||
public override fun onRun() {
|
public override fun onRun() {
|
||||||
|
@ -1,26 +1,28 @@
|
|||||||
package org.thoughtcrime.securesms.loki.protocol
|
package org.thoughtcrime.securesms.loki.protocol
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.util.Log
|
||||||
import org.thoughtcrime.securesms.ApplicationContext
|
import org.thoughtcrime.securesms.ApplicationContext
|
||||||
import org.thoughtcrime.securesms.database.Address
|
import org.thoughtcrime.securesms.database.Address
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
import org.thoughtcrime.securesms.jobs.PushMediaSendJob
|
import org.thoughtcrime.securesms.jobs.PushMediaSendJob
|
||||||
import org.thoughtcrime.securesms.jobs.PushSendJob
|
import org.thoughtcrime.securesms.jobs.PushSendJob
|
||||||
import org.thoughtcrime.securesms.jobs.PushTextSendJob
|
import org.thoughtcrime.securesms.jobs.PushTextSendJob
|
||||||
|
import org.thoughtcrime.securesms.loki.utilities.Broadcaster
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
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.api.fileserver.LokiFileServerAPI
|
||||||
import org.whispersystems.signalservice.loki.protocol.meta.SessionMetaProtocol
|
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.multidevice.MultiDeviceProtocol
|
||||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiMessageFriendRequestStatus
|
import org.whispersystems.signalservice.loki.protocol.todo.LokiMessageFriendRequestStatus
|
||||||
import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus
|
import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus
|
||||||
|
|
||||||
object MultiDeviceProtocol {
|
object MultiDeviceProtocol {
|
||||||
|
|
||||||
@JvmStatic
|
// TODO: Closed groups
|
||||||
fun sendUnlinkingRequest(context: Context, publicKey: String) {
|
|
||||||
val unlinkingRequest = EphemeralMessage.createUnlinkingRequest(publicKey)
|
|
||||||
ApplicationContext.getInstance(context).jobManager.add(PushEphemeralMessageSendJob(unlinkingRequest))
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class MessageType { Text, Media }
|
enum class MessageType { Text, Media }
|
||||||
|
|
||||||
@ -34,7 +36,6 @@ object MultiDeviceProtocol {
|
|||||||
sendMessagePush(context, recipient, messageID, MessageType.Media)
|
sendMessagePush(context, recipient, messageID, MessageType.Media)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Closed groups
|
|
||||||
private fun sendMessagePushToDevice(context: Context, recipient: Recipient, messageID: Long, messageType: MessageType): PushSendJob {
|
private fun sendMessagePushToDevice(context: Context, recipient: Recipient, messageID: Long, messageType: MessageType): PushSendJob {
|
||||||
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient)
|
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient)
|
||||||
val threadFRStatus = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID)
|
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) {
|
class PushEphemeralMessageSendJob private constructor(parameters: Parameters, private val message: EphemeralMessage) : BaseJob(parameters) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val KEY_MESSAGE = "message"
|
private const val KEY_MESSAGE = "message"
|
||||||
|
|
||||||
const val KEY = "PushBackgroundMessageSendJob"
|
const val KEY = "PushBackgroundMessageSendJob"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,14 +31,13 @@ class PushEphemeralMessageSendJob private constructor(parameters: Parameters, pr
|
|||||||
message)
|
message)
|
||||||
|
|
||||||
override fun serialize(): Data {
|
override fun serialize(): Data {
|
||||||
|
// TODO: Is this correct?
|
||||||
return Data.Builder()
|
return Data.Builder()
|
||||||
.putString(KEY_MESSAGE, message.serialize())
|
.putString(KEY_MESSAGE, message.serialize())
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getFactoryKey(): String {
|
override fun getFactoryKey(): String { return KEY }
|
||||||
return KEY
|
|
||||||
}
|
|
||||||
|
|
||||||
public override fun onRun() {
|
public override fun onRun() {
|
||||||
val recipient = message.get<String?>("recipient", null) ?: throw IllegalStateException()
|
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.ApplicationContext
|
||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
||||||
import org.thoughtcrime.securesms.crypto.PreKeyUtil
|
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.jobs.CleanPreKeysJob
|
||||||
|
import org.thoughtcrime.securesms.loki.utilities.recipient
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
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 {
|
object SessionManagementProtocol {
|
||||||
|
|
||||||
@ -24,8 +32,50 @@ object SessionManagementProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun sendSessionRestorationRequest(context: Context, publicKey: String) {
|
fun handlePreKeyBundleMessageIfNeeded(context: Context, content: SignalServiceContent) {
|
||||||
val sessionRestorationRequest = EphemeralMessage.createSessionRestorationRequest(publicKey)
|
val recipient = recipient(context, content.sender)
|
||||||
ApplicationContext.getInstance(context).jobManager.add(PushEphemeralMessageSendJob(sessionRestorationRequest))
|
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.Address
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
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
|
import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus
|
||||||
|
|
||||||
object SessionMetaProtocol {
|
object SessionMetaProtocol {
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun sendEphemeralMessage(context: Context, publicKey: String) {
|
fun handleProfileUpdateIfNeeded(context: Context, content: SignalServiceContent) {
|
||||||
val ephemeralMessage = EphemeralMessage.create(publicKey)
|
val rawDisplayName = content.senderDisplayName.orNull() ?: return
|
||||||
ApplicationContext.getInstance(context).jobManager.add(PushEphemeralMessageSendJob(ephemeralMessage))
|
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.jobs.MultiDeviceGroupUpdateJob
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
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.protocol.todo.LokiThreadFriendRequestStatus
|
||||||
import org.whispersystems.signalservice.loki.utilities.PublicKeyValidation
|
import org.whispersystems.signalservice.loki.utilities.PublicKeyValidation
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
object SyncMessagesProtocol {
|
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
|
@JvmStatic
|
||||||
fun syncAllContacts(context: Context) {
|
fun syncAllContacts(context: Context) {
|
||||||
ApplicationContext.getInstance(context).jobManager.add(MultiDeviceContactUpdateJob(context, true))
|
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 android.content.Context;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.crypto.SecurityEvent;
|
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.SignalServiceMessageSender;
|
||||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
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) {
|
@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) {
|
@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) {
|
@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
|
@Override
|
||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
if (TextSecurePreferences.setNeedsDatabaseResetFromUnlink(this)) {
|
if (TextSecurePreferences.getWasUnlinked(this)) {
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||||
builder.setTitle(R.string.dialog_device_unlink_title);
|
builder.setTitle(R.string.dialog_device_unlink_title);
|
||||||
builder.setMessage(R.string.dialog_device_unlink_message);
|
builder.setMessage(R.string.dialog_device_unlink_message);
|
||||||
@ -36,7 +36,7 @@ public class WelcomeActivity extends BaseActionBarActivity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDismiss(DialogInterface dialog) {
|
public void onDismiss(DialogInterface dialog) {
|
||||||
TextSecurePreferences.setNeedDatabaseResetFromUnlink(getBaseContext(), false);
|
TextSecurePreferences.setWasUnlinked(getBaseContext(), false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
builder.show();
|
builder.show();
|
||||||
|
@ -1264,12 +1264,12 @@ public class TextSecurePreferences {
|
|||||||
return getBooleanPreference(context, "database_reset", false);
|
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
|
// We do it this way so that it gets persisted in storage straight away
|
||||||
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean("database_reset_unpair", value).commit();
|
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);
|
return getBooleanPreference(context, "database_reset_unpair", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user